Cypress: After type, framework's internal state doesn't have time to update

Created on 30 Nov 2017  Â·  5Comments  Â·  Source: cypress-io/cypress

  • Operating System: Linux
  • Cypress Version: 1.1.2
  • Browser Version: Electron

Is this a Feature or Bug?

bug

Current behavior:

Test is adding one todo to the list, then types long text into the input box. But the input box somehow only shows last part of the long text (see image). The text typed into the box is supposed to be "Learn how to test with Cypress.io". The box has only "h Cypress.io"

Desired behavior:

  • Have the expected text in the input box.
  • Give better error message. In this case we are comparing the central data store object, but we have to guess what happens. It would be nice if the error message actually showed values in the STDERR of the application.

Instead of

How to reproduce:

Test code:


Additional Info (images, stack traces, etc)

Repo: https://github.com/cypress-io/cypress-example-recipes folder examples/blogs__vue-vuex-rest

Dashboard result: https://dashboard.cypress.io/#/projects/6p53jw/runs/feffade1-893c-4327-8059-6c53920bcc32/screenshots

Test: https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/blogs__vue-vuex-rest/cypress/integration/store-spec.js#L160

  it('can add a todo, type and compare entire store', () => {
    const title = 'a random todo'
    enterTodo(title)

    const text = 'learn how to test with Cypress.io'
    cy
      .get('.todoapp')
      .find('.new-todo')
      .type(text)
      .trigger('change')

    getStore().should('deep.equal', {
      loading: false,
      todos: [
        {
          title,
          completed: false,
          id: '1'
        }
      ],
      newTodo: text
    })
  })

test-input

What I think happens

The test first adds a Todo object that goes to the server, then Vue component refreshes. I think the typing does NOT WAIT for the vue component refresh. Thus when the Vue component refreshes it deletes part of the text cy.type(text) entered.

  • user could wait for Vue component refresh, for example using cy.input... should be empty assertion
  • Cypress test runner could watch the input element and detect "stray" changes that were triggered by the outside code. Then it would know that something else just created a race condition. Consider example
cy.get('input').type('a very very very long string', {delay: 100})
setTimeout(function setInput () {
  document.querySelector('input').value = 'foo'
}, 500)

Im this case we will start typing a very ... with 100ms delay after each letter, but 500ms after the input will be set to foo by the timer. Cypress should catch this problem - I think this might be a reason for flake in many web apps - Cypress moves to the next step faster than the web apps update themselves ;)

wontfix

All 5 comments

Confirmed. Added a test that delays the response from the server, so the web component updates while we are slowly typing into the input box.

  it('starts typing after delayed server response', () => {
    // this will force new todo item to be added only after a delay
    cy.server()
    cy.route({
      method: 'POST',
      url: '/todos',
      delay: 3000,
      response: {}
    })

    const title = 'first todo'
    enterTodo(title)

    const newTitleText = 'this is a second todo title, slowly typed'
    cy
      .get('.todoapp')
      .find('.new-todo')
      .type(newTitleText, { delay: 100 })
      .trigger('change')

    cy.screenshot('typing after delay')
  })

Result - only last part of the new text

typing after delay

Yes this is possibly "part" of the problem. There are situations where cy.type does not respect changes to selectionStart and selectionEnd which is covered by another issue.

I had opened another issue awhile ago which would be a breaking change here: https://github.com/cypress-io/cypress/issues/566

What that describes is that we should change cy.type to synchronously type all of the characters. It would make async digests impossible to fuss with the typed text and remove all race conditions. It wouldn't match "reality" but it would remove these kinds of hard to understand problems.

My guess here though is that you should be able to reproduce this by banging on the keyboard as fast as possible since Cypress types at approximately 120wpm.

If you can't repro that way, then there may be a somewhere where we're not respecting changes.

yeah, it is the problem in this particular repo, I bet I could recreate the problem by banging on the keyboard quickly when using a remote server with noticeable lag. For now I added assertion to make sure new todo updates the DOM before proceeding

export const getNewTodoInput = () => getTodoApp().find('.new-todo')

export const enterTodo = (text = 'example todo') => {
  getNewTodoInput().type(`${text}{enter}`)
  // we need to make sure the store and the vue component
  // get updated and the DOM is updated.
  // quick check - the new text appears at the last position
  getTodoItems().last().should('contain', text)
}

But in general, I think we should detect input update and treat it as an error, same way we do with "detached DOM element" error

Right, we could automatically wait that the input has the value we expect
it to after typing - but the problem here are text masks.

The value could be mutated on each keystroke to align itself to something
like a formatted date or a formatted phone number.

We could add in an optional param that says like: "synchronize" and then
it'll wait until the value matches... but... yeah it's tough.

I think the problem really is that the value of the <input> WILL be
changing correctly, but the "component" registering it is lagging behind
due to its own internal digest cycle.

On Thu, Nov 30, 2017 at 10:55 AM, Gleb Bahmutov notifications@github.com
wrote:

yeah, it is the problem in this particular repo, I bet I could recreate
the problem by banging on the keyboard quickly when using a remote server
with noticeable lag. For now I added assertion to make sure new todo
updates the DOM before proceeding

export const getNewTodoInput = () => getTodoApp().find('.new-todo')
export const enterTodo = (text = 'example todo') => {
getNewTodoInput().type(${text}{enter})
// we need to make sure the store and the vue component
// get updated and the DOM is updated.
// quick check - the new text appears at the last position
getTodoItems().last().should('contain', text)
}

But in general, I think we should detect input update and treat it as an
error, same way we do with "detached DOM element" error

—
You are receiving this because you were assigned.
Reply to this email directly, view it on GitHub
https://github.com/cypress-io/cypress/issues/984#issuecomment-348231152,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABNc8JJLQbGVMiGen8WTG3KwL5FNXZQmks5s7tAEgaJpZM4Qwsv8
.

it's unlikely there is a good way to fix this since we shouldn't error on unexpected changes to the input value, and we also don't know exactly how long to wait for a framework's internal state to settle. I'm going to label this with wont fix

Was this page helpful?
0 / 5 - 0 ratings

Related issues

egucciar picture egucciar  Â·  3Comments

Francismb picture Francismb  Â·  3Comments

jennifer-shehane picture jennifer-shehane  Â·  3Comments

jennifer-shehane picture jennifer-shehane  Â·  3Comments

dkreft picture dkreft  Â·  3Comments