Testing a code who used event instanceof KeyboardEvent, I've seen some strange behavior, digging up, it seems that there is some strange inconsistencies between the events triggered and also into the way they are sent to the eventListener.
Consistent behavior:
.type.trigger({..., type: 'keydown'}).get('#element').then(element.dispatchEvent(new KeyboardEvent('keydown', ...)))Should all send a KeyboardEvent type event.
And the eventListener:
element.addEventListener('keydown', (event) => (event instanceof KeyboardEvent));
Should always return true.
I've tested three different methods: cy.type, cy.trigger, cy.get('#element').then( element.dispatchEvent).
And two different eventListener for each test:
The issue also seem to concern 'mousedown' and MouseEvent, except that .click seem to send MouseEvent to one of the two listener. But .trigger doesn't.
I've added the tests for the MouseEvent to the MWE repo.
Hey @avallete, I'm trying to narrow down exactly what the issue is from what you provided. Does the below summarize the totality of the issue?
When using .type(), .trigger() or dispatching an event directly from an element returned from a Cypress command:
mousedown event that is triggered not an instanceof MouseEventkeydown event that is triggered in Cypress not an instanceof KeyboardEventPlease clarify if I have missed something else. Thanks.
Hey @avallete, I'm trying to narrow down exactly what the issue is from what you provided. Does the below summarize the totality of the issue?
When using
.type(),.trigger()or dispatching an event directly from an element returned from a Cypress command:
- The
mousedownevent that is triggered not aninstanceof MouseEvent- The
keydownevent that is triggered in Cypress not aninstanceof KeyboardEventPlease clarify if I have missed something else. Thanks.
I'll just add from my last comment that:
When using the .click() Cypress command, the event that is triggered is not an instanceof MouseEvent.
And also the strange behavior is that between eventListener in different places into the code (directly put into cypress it(){...} or into <script>...</script>) seem to receive not the same values and instanceof seem to behave in different ways between these two scopes.
But yeah @jennifer-shehane , you have summarized it way better than I did, thank's.
Investigating a little bit on this issue, I've been able to found what seem to be the root of the behaviour I describe on this issue:
Replacing the win['Event'] by win['KeyboardEvent'] on this line, make the .type function to properly send an event which is instanceof KeyboardEvent into the tests.
But looking on the comment on top of this line, this fix may lead to some regression on Chrome < 63.
I tried it with Cypress 4.12.1 + 'Event' => 'KeyboardEvent'. But something interesting happened.
console.log says it's a KeyboardEvent, but the instanceof KeyboardEvent is false.
Here are the screenshots.

Something similar happens to MouseEvent.

It's a bit hard to figure out what made the difference.
I had opened this issue so long ago that I had completely forgot about it.
Related to your comment @sainthkh I've refactored my MEW to:
instanceof (inside the running test, inside the html web page script)instanceof is not true in any of the context (since it's the attended behaviour)If you don't want to clone the MWE repository, here is the videos of the running tests to see what's wrong.
KeyboardEvent tests:

MouseEvent tests:

FYI, the expected behaviour for both KeyboardEvent and MouseEvent can be summarize into three assertions who are the following ones:
instanceof (MouseEvent or KeyboardEvent, depending on the test).<script> tag of the page, should see an event which is 'instanceof' (MouseEvent or KeyboardEvent, depending on the test). And so, set the text 'isKeyboardEvent|isMouseEvent' to true inside the '#result' div..click(), .trigger(), .type(), .get(element).then(elem => elem.dispatchEvent())) the behaviour should be consistent between all the raised events.I hope this addition may help to understand more clearly what's going on here.
Finally, I understood what's going on.
You provided us 9 tests for each event. I read them and learned that the last 3 are duplicates of the above 6. So, all we have to check was those 6.
When we run the tests as-is with 4.12.0, only the test no. 3 passes. Other 5 fail. With the change to 'Event' to 'KeyboardEvent' you mentioned above, the test no.4 passes.
Now, we have 4 tests to fix.
When I saw the console log, the thing made me curious was this:
trigerred event show from static/html file !
received key: '65'
is instanceof KeyboardEvent ? : (false)
event type: (keydown)
event constructor: function KeyboardEvent() { [native code] }
event object:
KeyboardEvent聽{isTrusted: false, key: "", code: "", location: 0, ctrlKey: false,聽鈥
instanceof KeyboardEvent is false. But the object is KeyboardEvent.
This problem happens with the test no. 1 and 6. And I finally learned why.
It's because they use different KeyboardEvent.
To run the test application, Cypress uses iframe. When we write test code with KeyboardEvent, KeyboardEvent itself came from the test runner. While event object is the instance of the "application(inside iframe)" KeyboardEvent.
So, the test 1 and 6 should be written like below:
it('should trigger KeyboardEvent with .type inside Cypress event listener', (done) => {
cy.visit('fixtures/issue-5650-2.html')
console.log('test: should trigger KeyboardEvent with .type inside Cypress event listener')
cy.window().then((win) => {
cy.get('#test-input').then((jQueryElement) => {
let elemHtml = jQueryElement.get(0)
elemHtml.addEventListener('keydown', (event) => {
console.log(`trigerred event show from cypressListener file !
received key: '${event.keyCode}'
is instanceof KeyboardEvent ? : (${event instanceof KeyboardEvent})
event type: (${event.type})
event constructor: ${event.constructor}
event object:
`, event)
expect(event instanceof win['KeyboardEvent']).to.be.true
done()
})
})
})
cy.get('#test-input').type('A')
})
it('should trigger KeyboardEvent with .get.then .dispatchEvent on htmlElement inside html script event listener', () => {
cy.visit('fixtures/issue-5650-2.html')
console.log('test: should trigger KeyboardEvent with .get.then .dispatchEvent on htmlElement inside html script event listener')
cy.window().then((win) => {
cy.get('#test-input').then((jQueryElement) => {
let elemHtml = jQueryElement.get(0)
let kbEvent = new win['KeyboardEvent']('keydown', {
keyCode: 65,
which: 65,
shiftKey: false,
ctrlKey: false,
})
elemHtml.dispatchEvent(kbEvent)
})
})
cy.get('#result').contains('isKeyboardEvent: true')
})
Check that I used cy.window() to bring iframe KeyboardEvent.
Finally, tests 2 and 4 are not fixed. The problem is here:
cy.trigger doesn't take the type of event class into account.
Before I go fix cy.trigger(), I want to ask this question. When does this consistency necessary? Can I get some real world use cases?
I'm asking this because to solve this problem, we need to add a big table of event name and event object types based on this.
Hi there,
As you mentioned, the last 3 tests are effectively a combination of the check in both (html script and tests runner) contexts. Which as you say, if all the above tests work, should also work. I do realize know that it may be confusing, sorry for that.
Nice spot about the iframe and the use of win['KeyboardEvent']. It may be worthy to add this trick somewhere into the documentation since this problem should occurs for any instanceof or === checks on window instantiated objects.
On the question about 'why it is necessary ?':
.type and .click wasn't instanceof the proper event. I do believe that the .trigger issue may even be handled in another issue..trigger('keydown') or .type should produce the same kind of event, and that, according to the MDN, those events should have to proper types (as the ones who would be normally triggered by an user of the tested application.).There are 3 problems in this issue:
Event -> `KeyboardEvent: Actually, this is also the solution for the #6125. I need to investigate what's the real problem in that code. And it'll be fixed with it. => #8255cy.window() issue: I think it should be written in FAQ, cy.window(), and/or The Test Runner. => cypress-io/cypress-documentation#3073.trigger issue: it should be treated separately. @sainthkh This seemed to be a use case that someone encountered in their app, where they were checking instanceof MouseEvent in their app code, so that cypress didn't behave the same. https://github.com/cypress-io/cypress/issues/8137
But yah, as you mentioned, this trigger thing may be a separate issue. Feel free to reopen that issue if it's the case.
Also, maybe related??? https://github.com/cypress-io/cypress/issues/3686
@jennifer-shehane I guess so. After #8255 is done, I'll research it more deeply.
I am not very experienced in the cypress codebase yet. But shouldn't the action "type" emit a KeyboardEvent instead of an HTMLEvent?
https://github.com/cypress-io/cypress/blob/093f3e7505096e24b2d49d53dbf2e9b3a31dd6fe/packages/driver/src/cy/commands/actions/type.js#L222
That way getModifierState() could work correctly?
The modifieres still need to be set on the event I assume.
We currently have this problem in the project where we handle modifiers and it fails cause the method is not there. And including a workaround for cypress in the application code is kinda awkward.
Maybe I am wrong here but this seems to be connected to this issue.
Ah I now also read all of #8255 So I assume my comment is obsolete then. Sorry for the noise
Before I go fix
cy.trigger(), I want to ask this question. When does this consistency necessary? Can I get some real world use cases?
I'm running into this issue because I have some Rust code which uses typed bindings to DOM APIs. The bindings provide a number of type definitions generated from WebIDL, and it's possible to cast between JS types. By default those casts use instanceof checks before returning to catch common bugs.
I have event handler helpers that take a typed callback and perform the conversion under the hood for the user. Elsewhere there are definitions mapping strings like "keydown" to types so that the callbacks always have the correct type. This works quite nicely "in production" so far but breaks when using cypress to enter text due to this issue.
I've had mixed results with removing the checks as it seems the bindings may rely on prototypes for doing their property lookups. The easiest fix that'd allow me to use cypress would be to emit an event with the correct prototype in its chain.
EDIT: I should also note that https://github.com/cypress-io/cypress/pull/8255 will address the most common cases.
The code for this is done in cypress-io/cypress#8305, but has yet to be released.
We'll update this issue and reference the changelog when it's released.
Released in 5.1.0.
This comment thread has been locked. If you are still experiencing this issue after upgrading to
Cypress v5.1.0, please open a new issue.