0

I want to log messages in the Cypress terminal only, so that they don't appear in the screenshots in the Cypress browser Command logs and can be viewed in the CI logs instead.

I tried using Cypress.log(), but it logs messages to both the terminal and the browser spec, which is not what I want. This is currently what I'm using but it spams the screenshots with RequestId logs (I only want it in CI terminal)

beforeEach(() => {
  cy.intercept({ url: API_HOST + '/api/**', middleware: true }, req => {
    const requestId =
      // Apollo lower cases the headers before sending the request
      req.headers['x-request-id'] || req.headers['X-Request-Id'];
    if (requestId) {
       Cypress.log({
        name: 'RequestID:',
        message: requestId,
      });
    }
  });
});

I also tried using console.log() inside cy.intercept, but it only logs to the Chrome console and not the Node.js terminal, so the messages are not shown anywhere. (I noticed that if I console.log within the application elsewhere it added the console.log into the Node.js Terminal but specifically inside cy.intercept it doesn't log it)

Finally, I tried using cy.task,

beforeEach(() => {
  cy.intercept({ url: API_HOST + '/api/**', middleware: true }, req => {
    const requestId =
      // Apollo lower cases the headers before sending the request
      req.headers['x-request-id'] || req.headers['X-Request-Id'];
    if (requestId) {
      cy.task('log', `RequestId: ${requestId}`);
    }
  });
});

But I received this error


The command that returned the promise was:

  > cy.visit()

The cy command you invoked inside the promise was:

  > cy.task()

Because Cypress commands are already promise-like, you don't need to wrap them or return your own promise.

Cypress will resolve your command with whatever the final Cypress command yields.

The reason this is an error instead of a warning is because Cypress internally queues commands serially whereas Promises execute as soon as they are invoked. Attempting to reconcile this would prevent Cypress from ever resolving.".

How can I log messages in the Cypress terminal only without them appearing in the cypress browser command logs?

Kenneth Truong
  • 3,882
  • 3
  • 28
  • 47

4 Answers4

8

The problem is that cy.intercept() callback functions are asynchronous event handlers, and Cypress commands can also trigger events.

To avoid a potential cascading event, Cypress prohibits firing commands inside an event handler.

But you can record the event and act on it later in the test flow.

For instance, when a cy.intercept() needs to log to terminal, record it in a Cypress.env().

cy.intercept({ url: '**/api/*', middleware: true,  }, (req) => {
  const headerValue = req.headers['x-custom-headers']  
  if (headerValue) {
    const terminalLogs = Cypress.env('terminalLogs') || []
    terminalLogs.push(`requestId: ${headerValue}`)
    Cypress.env('terminalLogs', terminalLogs)
  }
}, {log: false})

Then, at some point appropriate to the test, for example afterEach(),

afterEach(() => {
  const terminalLogs = Cypress.env('terminalLogs') || []
  terminalLogs.forEach(log => {
    cy.task('log', log, {log:false})
  })
})

cypress.config.ts

const { defineConfig } = require('cypress')

module.exports = defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      on('task', {
        log(message) {
          console.log(message);
          return null;
        },
      })
      return config
    }
  },
})

Without waiting for cy.wait('@intercept-alias')

If you decide not to use a cy.wait('@intercept-alias'), the test can finish and shut down before the intercept has fired it's callback.

If the data capture is on the request callback, it's probably ok.

But on the reponse side, the capture can be delayed by the network and you should make sure Cypress.env('terminalLogs') is fired only after the response has been handled, e.g in an afterEach().

5

If you are having problems calling cy.task() inside the intercept handler, try using the alias/wait pattern and logging from the wait callback.

This is the general pattern:

cy.intercept(url, () => {
  // can't use task here
}).as('logSomething')

// trigger fetch

// wait for intercept
cy.wait('@logSomething').then(interception => {
  // all request & response properties available here
  cy.task('log', interception.response.body.id)
})

This is my code calling cy.task() without the same error

cy.intercept({ url: '**/api/*', middleware: true,  }, (req) => {
  const headerValue = req.headers['x-custom-headers']  
  if (headerValue) {
    cy.task('log', `requestId: ${headerValue}`)
  }
}, {log: false}).as('middleware')

// a non-middleware intercept, also fires the log task
cy.intercept('**/api/*', (req) => {
  cy.task('log', 'something else')
  req.reply({})
})

// trigger a fetch from the app window
cy.window().then(win => win.fetch('http://some-domain/api/1', {
  headers: {
    "x-custom-headers": "123",
  },
}))

cy.wait('@middleware')  // wait on the middleware 

I'm waiting on the middleware intercept just to make sure the test runs long enough, but that wouldn't affect the error you are getting.

user16695029
  • 3,365
  • 5
  • 21
  • Thanks for replying! Unfortunately I have a generic intercept in a beforeAll which I add the intercept. So I can't wait for a specific call because I want to log all requestIds i.e see this question (I basically want to log a specific header in all requests) https://stackoverflow.com/questions/73422976/how-do-i-log-a-specific-field-in-api-requests-within-cypress – Kenneth Truong Apr 29 '23 at 01:05
  • 1
    Actually, `cy.task('log', ...)` ***does*** work inside the intercept callback, and `Cypress.log()` ***doesn't*** log to the terminal (the Cypress nodejs process). So I think we could use more info - what exactly is your intercept call that is failing? – user16695029 Apr 29 '23 at 02:39
  • So the code has this `beforeAll(() => { cy.intercept(() => cy.task(...))` and that's throwing an error `Because Cypress commands are already promise-like, you don't need to wrap them or return your own promise` – Kenneth Truong Apr 29 '23 at 23:02
  • 2
    That's because you missed the brackets: `beforeAll(() => { cy.intercept(() => { cy.task() })` – Raglan Apr 30 '23 at 20:26
  • oh that was pseudocode. This is the full code (pasted) inside support/e2e.ts ``` beforeEach(() => { cy.intercept({ url: API_HOST + '/api/**', middleware: true }, req => { const requestId = // Apollo lower cases the headers before sending the request req.headers['x-request-id'] || req.headers['X-Request-Id']; if (requestId) { cy.task('log', `RequestId: ${requestId}`); } }); });``` – Kenneth Truong May 02 '23 at 04:48
  • 2
    That looks the same as I have - probably your error is not related to this piece of code. – user16695029 May 02 '23 at 04:55
  • good point I should have added the code I've tried in the question – Kenneth Truong May 02 '23 at 05:01
  • I wonder if its because you're using cy.window() to initialize a fetch which works for your code but because my tests loads a webpage and it throws an error from the cy.visit() (which triggers a few different fetches from loading the page) – Kenneth Truong May 02 '23 at 05:09
  • 1
    Why would that make a difference? If the cy.window() fetch didn't work, it would fail to catch the intercept at `cy.wait('@middleware)`. – user16695029 May 02 '23 at 05:11
1

You can do it using the cy.task

// cypress.config.ts

setupNodeEvents(on, config) {
  on('task', {
    log(message) {
      console.log(`\n${message}\n`);

      return null;
    },
  });

  return config;
},

then in your spec file:

// your.spec.ts
cy.task('log', ' ->>>>>>>> test terminal log <<<<<<<-');
Darko Riđić
  • 459
  • 3
  • 18
  • Unfortunately I already tried that. But because we're inside cy.intercept it complains about "Because Cypress commands are already promise-like, you don't need to wrap them or return your own promise". – Kenneth Truong Apr 28 '23 at 21:00
0

I wanted to provide an answer on another option that worked for me.

Since I was trying to log the headers for all the request I noticed that cypress-terminal-report had an option to log all the request headers in the terminal via these option (I couldn't figure out a way to filter out the unnecessary response header and request body but this got me into a better spot than I was before and additional info doesn't hurt in the terminal)

require('cypress-terminal-report/src/installLogsCollector')({
  xhr: {
    printHeaderData: true,
    printRequestData: true,
  },
});
van Huet
  • 139
  • 8
Kenneth Truong
  • 3,882
  • 3
  • 28
  • 47