cypress-io/cypress

Cypress' type() not compatible with react 15.6's onChange?

arthens opened this issue ยท 13 comments

  • Operating System: Mac os X and Linux
  • Cypress Version: 0.19.2 (app) and 0.13.1 (cli)
  • Browser/Browser Version: Chrome 58

Are you requesting a feature or reporting a bug?

Bug

Our cypress tests started to fail when we bumped react from 15.5.4 to 15.6.1. We debugged the problem and came to the conclusion that onChange doesn't fire as it should when the input field is being manipulated using .type('some text').

cli + headless chrome: broken
app + real chrome: broken
user interacting with the browser: working

React component:

<input
    className="input-text"
    onChange={() => {
        // this will executed with react 15.5.4, but not with 15.6.1
    }}
    value={this.props.value}
    type="text"
    data-stock-url />

and this is how we are manipulating it

cy.visit('https://...')
    .get('[data-stock-url]').type('https://google.com/stock/123')

onChange doesn't fire anymore. The problem went away when we downgraded back to 15.5.4.

Not sure if related, but react-dom's CHANGELOG says:

  • Fix issues with onChange not firing properly for some inputs. (@jquense in #8575)

which is suspicious. Is cypress' type() relying on an implementation detail that is not true anymore?

Cypress doesn't rely on any specific JS frameworks' implementation. It's firing events at the lowest possible level.

We'll take a look at this. In 0.20.0 we have many fixes going out for cy.type, so we'll first see if it's a new potential problem.

We use React internally on all of our projects. @chrisbreiding can you look at this?

The input logic in React now dedupe's change events so they don't fire more than once per value. It listens for both browser onChange/onInput events as well as sets on the DOM node value prop (when you update the value via javascript). This has the side effect of meaning that if you update the input's value manually input.value = 'foo' then dispatch a ChangeEvent with { target: input } React will register both the set and the event, see it's value is still `'foo', consider it a duplicate event and swallow it.

This works fine in normal cases because a "real" browser initiated event doesn't trigger sets on the element.value. You can bail out of this logic secretly by tagging the event you trigger with a simulated flag and react will always fire the event. https://github.com/jquense/react/blob/9a93af4411a8e880bbc05392ccf2b195c97502d1/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js#L128

I can confirm this problem is happening.

We've been able to reproduce and fix it. We're going to release an 0.19.4 patch with just this fix in it.

Thank you @brian-mann

Thank you for your quick work. :)

Thanks for the timely info, @jquense! That helped a lot.

Fixed in 0.19.4.

@brian-mann i'm having the same issue in a non-related project. Could you share the way you fixed this? I'm guessing you switched from input radio to <button> or <a> tags? Or did you manage to keep the radios and get the onChange event to fire on each click? Thanks in advance.

@brian-mann btw if you used my simulated workaround I'd bring your use case up to on React team. That has been removed in v16.

We did use the simulated hack just to get out the patch immediately (because we created several tests for it and they all pass)

However we thought about it later and have a "real" fix being implemented that's not React specific, and should actually fix the problem permanently...

This is from my notes:

screen shot 2017-06-28 at 11 21 38 am

To clarify I don't think the react team needs to make this change. We determined the exact situation where Cypress deviates from the way a real browser behaves. A real browser will NOT call the value setter when changes the value from a default action. It will just "magically" update.

Since we call the setter (this is the difference). However as long as we call the original setter (that 3rd party code cannot possibly get notified of) we'll essentially reproduce the real browser behavior without needing any 3rd party library to change the way it works.

sounds great :)