cypress-io / cypress

Fast, easy and reliable testing for anything that runs in a browser.
https://cypress.io
MIT License
47.37k stars 3.2k forks source link

Component tests fail when angular-query is used in the component #30327

Open ekrapfl opened 1 month ago

ekrapfl commented 1 month ago

Current behavior

We are using Angular Query (TanStack Query's Angular port) in our project, and we have noticed that it can interfere with our Cypress component tests in some cases. To illustrate this, we have made a simple modification to the Cypress component testing sample app for standalone Angular. If you pull it down and run the test-component spec, you will see that none of the tests pass. It appears as though change detection is not running properly once the TanStack Query (TSQ) is injected in the component file. To see the tests work again, comment out the query property in test-component.component.ts.

Reproduction fork

image

Notes:

Desired behavior

I would expect that injecting a simple TSQ would not affect change detection in Cypress component tests.

Test code to reproduce

cypress-component-testing-apps Reproduction fork

Simple Angular Repro

Cypress Version

13.15

Node version

20.11.0

Operating System

MacOS 15.0

Debug Logs

No response

Other

No response

abdelsalamshahlol commented 1 month ago

I've encountered the same issues on my test project, leading to your report. However, I got all assertions to pass from your sample by firing change detection manually and using then(()=>...

  cy.mount(FailComponent, {
      providers: [provideAngularQuery(new QueryClient())],
    }).then(({fixture})=>{
       fixture.detectChanges()

      cy.get('.first').should('have.class', 'selected');

      cy.get('.second').click().then(()=>{
        fixture.detectChanges()
        cy.get('.second').should('have.class', 'selected');
      });

      cy.get('.third').click().then(()=>{
        fixture.detectChanges()
        cy.get('.third').should('have.class', 'selected');
      });

I know this doesn't solve the issue but it may help with the investigations.

ekrapfl commented 1 month ago

Yep, that is totally true. If I strategically fire change detection manually all over the place in my tests, it will work. It's like something has broken something with change detection itself, though, and it doesn't happen automatically anymore.

ekrapfl commented 2 weeks ago

Just curious, is the Cypress team looking into this at all?

jennifer-shehane commented 2 weeks ago

@ekrapfl We're not currently looking into this problem at the moment. Our project board is generally up to date on what we're working on: https://github.com/orgs/cypress-io/projects/13

ekrapfl commented 2 weeks ago

Thanks for the update, @jennifer-shehane. Is there any way to get that bumped up in priority at all? My company is a paying customer. Is there a support channel I should go through instead?

ekrapfl commented 2 weeks ago

I would be very interested in anyone's thoughts on a workaround for this as well. I have tried sprinkling tons of manual change detection checks into the tests, but nothing seems to jar it loose completely.

mschile commented 2 weeks ago

Not necessarily a workaround, but instead of sprinkling change detection checks everywhere in your tests, Cypress does allow you to overwrite built-in commands, so you could do something like:

Cypress.Commands.overwrite('should', function (originalFn, ...args) {
  cy.window().its('cdr').invoke('detectChanges').then(() => {
    return originalFn.apply(this, args)
  })
})

Now, whenever you do .should(...) in a test, it will first run detectChanges.

AtofStryker commented 1 week ago

I took a look into this recently, and for whatever reason, whenStable is never achieved when tanstack queries are used in the component, which means autoDetectChanges is never called inside Cypress. I'm hesitant to remove the line currently because I am not fully aware of the consequences, but you could try to either:

// ./cypress/support/component.ts
import { mount } from 'cypress/angular-signals'

// Augment the Cypress namespace to include type definitions for
// your custom command.
// Alternatively, can be defined in cypress/support/component.d.ts
// with a <reference path="./component" /> at the top of your spec.
declare global {
  namespace Cypress {
    interface Chainable {
      mount: typeof mount
    }
  }
}

Cypress.Commands.add('mount', function (...args) {
  mount(...args).then(({ fixture }) => {
    fixture.autoDetectChanges(true)
  })
})

@ekrapfl would you mind trying this work around? I am able to get all tests to pass in the test-component.component.cy.ts with this change. I created a PR against your fork to try https://github.com/cg-roling/cypress-component-testing-apps/pull/1

ekrapfl commented 1 week ago

Wow, great call! That does seem to work to fix all of the tests I had failing for this reason, but it also breaks some others that were previously working. I am thinking that I might be able to work around it for now by doing something like:

cy.mount(MyComponent).then(({ fixture }) => fixture.autoDetectChanges(true));

And I would only do that for the problematic components. Not ideal, but neither is the whole situation. Does that seem like potentially a good workaround for the moment?

mschile commented 1 week ago

@ekrapfl, yes, that seems like a good workaround. You would also try setting up a mount command that accepts a parameter to autoDetectChangesAfterMount.

ekrapfl commented 1 week ago

An interesting note for the Cypress mount command. I agree that there is some issue with whenStable while using TanStack Query. I was finding some similar oddities with Jasmine unit tests while using TanStack Query. But I found a very simple workaround in Jasmine tests: make sure to run compileComponents on the TestBed. Once I did that, whenStable seemed to work properly, as did any other quirks I was seeing. Normally, that shouldn't be necessary when running via ng test according to the docs, but in my case, that definitely fixed the issue.

I was curious if I could apply that change to the Cypress mount command and globally have it compileComponents, but I struggled to get that plugged in properly. Is that something Cypress would want to investigate to see if it fixes this kind of issue? Calling .compileComponents on the TestBed during mount, that is?

AtofStryker commented 1 week ago

I was curious if I could apply that change to the Cypress mount command and globally have it compileComponents, but I struggled to get that plugged in properly. Is that something Cypress would want to investigate to see if it fixes this kind of issue? Calling .compileComponents on the TestBed during mount, that is?

I don't see why we couldn't. It looks to be harmless according to the Angular docs, and we could just add the call in initTestBed. The only weirdness would be that the call my be async, which would make the mount function async. Either way, its likely worth looking into.