Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cy.hover() #10

Open
jennifer-shehane opened this issue Mar 31, 2015 · 71 comments
Open

Add cy.hover() #10

jennifer-shehane opened this issue Mar 31, 2015 · 71 comments
Labels
existing workaround pkg/driver This is due to an issue in the packages/driver directory stage: proposal 💡 No work has been done of this issue type: feature New feature that does not currently exist

Comments

@jennifer-shehane
Copy link
Member

jennifer-shehane commented Mar 31, 2015

Need to be able to hover in/out of DOM elements.

Example for hover
cy.get("@firstRow").hover()

I'm not sure what a stop hover command would look like.

Currently I would achieve this with mouseOver and mouseOut events. The hover command could follow a similar convention.

Other considerations:

  • In what direction does the mouse come from for hover
  • How long should the hover occur.
  • How do you "pause" a hover to allow for possible css animations.
  • Which actions cause you no longer to hover.
@jennifer-shehane jennifer-shehane added help wanted type: feature New feature that does not currently exist labels Mar 31, 2015
@jennifer-shehane
Copy link
Member Author

When I issue a .click() command, this should issue a .hover() over the element I've specified to click onto (in order to fully simulate mouse behavior).

@jennifer-shehane jennifer-shehane added the type: unexpected behavior User expected result, but got another label Jun 5, 2015
@seltar
Copy link

seltar commented Jul 2, 2015

I don't think it's possible to implement a hover function with support for the css pseudo selectors considering it's a trusted event. http://www.w3.org/TR/DOM-Level-3-Events/#trusted-events
The only way to simulate a hover to play css animations is by adding / removing a class.

A solution could be to modify and reinject the css to ensure that all :hover pseudo selectors are made available as a class.

@brian-mann
Copy link
Member

Correct, it is not possible to trigger the CSS pseudo selectors via JavaScript directly, but it is possible to re-create what CSS does on a hover using just JavaScript (without the user changing anything).

When browsers run in cross browser mode we simply use the OS to actually hover over an element by its coordinates, so this isn't an issue in that mode.

However when testing in your local environment, Cypress has to simulate all events, and thats where the problem comes in.

I experimented with a few different solutions nearly a year ago, and found one to work really well. It works like this - on hover find the element at the center of the coordinates, walk up the parent tree for every element we are currently hovering based on the CSS box model (not the DOM object model).

For each of those elements iterate through the css and figure out if a hover pseudo state applies. If it does, then inline that style directly on each element. This will achieve all CSS results in JavaScript.

However that's only half the battle.

The reason this command hasn't been finished is 2 reasons:

  1. Figuring out the parents by the CSS box model and not the DOM took awhile to get right - but now it is correct and it's already been implemented by cy.click - so this is no longer a barrier (but it used to be)
  2. There are other things that go with a "hover" besides just CSS styles. For instance: mouseover, mouseout need to be simulated as well (which is easy at this point). But the biggest issue here is semantics. For instance, at what point does the user stop hovering over an element? Deciding when to fire the mouseout is the real challenge.

I originally imagined cy.hover to actually be a callback function like this:

cy.get("button").hover(function(){
  cy.get("span").click()
  ...
  ...
})

However that puts a lot of work on the user, and its possible you can create impossibilities. But the idea was that by using a callback function the user could specify the semantics of "continue hovering until X".

That is probably overkill though. Instead I will just dumb down the method and if edge cases occur handle them when they arise.

Hover additionally needs to be automatically invoked under the hood to properly simulate user action. For instance any click should automatically first hover over the element, and I guess mouseout when the action is done?

Anyway, that's why hover hasn't been done yet. It was dependent on a lot of other commands to be figured out, although it is ready now to be solved.

@jennifer-shehane
Copy link
Member Author

I hope to be able to write assertions about pseudo-classes since this isn't readily accessible without the work outlined above by @brian-mann.

Example assertion

cy.get(“.content”).hover().should("have.attr", "style”, “cursor:alias”)

.hover() should return element chained to hover with the element having applied the styles specified within :hover pseudo-class.

@mlunoe
Copy link

mlunoe commented Mar 1, 2016

This helped me out to at least show my element to be able to click it:
https://docs.cypress.io/v1.0/docs/invoke#section-properties-that-are-functions-are-invoked

cy.get('.content').invoke('show').click();

@brian-mann
Copy link
Member

Good point. We're going to add a pivotal story to write the documentation for cy.hover which redirects you and provides examples how to easily solve this.

@jennifer-shehane
Copy link
Member Author

Using cy.hover() will now provide a detailed error message with a link for working around hover constraints in 0.15.0.

@nazar
Copy link

nazar commented May 13, 2016

The following workaround works for me:

Cypress.addChildCommand('triggerHover', function(elements) {

  elements.each((index, element) => {
    fireEvent(element, 'mouseover');
  });

  function fireEvent(element, event) {
    if (element.fireEvent) {
      element.fireEvent('on' + event);
    } else {
      var evObj = document.createEvent('Events');

      evObj.initEvent(event, true, false);

      element.dispatchEvent(evObj);
    }
  }

});

Which now allows me to:

          cy
            .get('.resultsHeading .icon-info-circle:first')
            .triggerHover()
            .get('.header-tooltips')
            .should('exist') 

HTH someone.

@brian-mann
Copy link
Member

Yup, this simulates the event which is likely good enough in situations where you only have a simple javascript bound event handler.

To make this solution more complete we would need to:

  1. Fill in the missing event coordinates such as clientX/Y properties which tell where the event is originating on the page. We calculate these for other Cypress commands such as cy.click which by default uses the exact center of the element, but this is configurable.
  2. Replicate handling the CSS object model - meaning the event should actually originate at the topmost element (which is how we handle cy.click as well).
  3. Still the hardest thing about hover is that you cannot invoke CSS pseudo properties from Javascript. There is no way to trigger the :hover styles on an element, which is likely what you're trying to do with hover. The only way to get around this is to go outside of the Javascript sandbox and use browser drivers aka the debugger protocol to fire native events - or parse all of the applied :hover rules and invoke them manually by inlining their styles on the element. Inlining styles will work but it has some edge cases. Creating a native event also has drawbacks (you can't have dev tools open, etc).

My thoughts are we still attempt to workaround hover purely in Javascript by inlining styles, but provide a {native: true} option which fires a native event. Once we do this, we can enable this option for all of the other commands too because.

I have on good authority been told that dev tools will eventually support multiplexed connections which means we'd no longer being booted off of the protocol whenever dev tools is opened by the user. This would make generating native events the preferred way in Cypress as opposed to event simulation.

@brian-mann brian-mann removed this from the 0.15.0 milestone Aug 31, 2016
@paulfalgout
Copy link
Contributor

Re the example above... for javascript hovers this works:

cy
  .get('.resultsHeading .icon-info-circle:first')
  //.triggerHover()
  .then(function($heading) { $heading.trigger('hover'); })
  .get('.header-tooltips')
  .should('exist') 

@brian-mann
Copy link
Member

brian-mann commented Oct 19, 2016

As per the above example - this will only work if you're using jquery to bind the hover event, since this is calling the trigger method on the jquery element.

Also you can write what you wrote a bit more succinctly like this:

EDIT: read my comment here for an updated example using cy.trigger(): #10 (comment)

Lastly you can ditch the should('exist') because that is the default assertion on all DOM commands.

https://docs.cypress.io/docs/finding-elements#section-existence

@brian-mann
Copy link
Member

We've added a recipe which explains how to simulate hover / get around this.

https://github.com/cypress-io/cypress-example-recipes/blob/master/cypress/integration/hover_hidden_elements.js

@jennifer-shehane jennifer-shehane added the pkg/driver This is due to an issue in the packages/driver directory label Mar 24, 2017
@guoyang007
Copy link

guoyang007 commented Jul 3, 2017

I simulate the hover by dispatching the event 'mousemove',and it worked for me.
the question I faced with is : the chart is painted by SVG based on the React framework, and the tooltip can be seen only when the mouse hover it .

my solution is

// create a event 'mousemove'
let e = new Event('mousemove', { bubbles: true, cancelable: true })

// give corresponding value to its target position
e.clientX=300;e.clientY=400

// dispatch the event
selector.dispatchEvent(e)

@jennifer-shehane jennifer-shehane removed their assignment Nov 14, 2017
@brian-mann
Copy link
Member

With cy.trigger() you can do things like...

cy.get('button').trigger('mouseover') // or mouseenter based on what your app is bound to

@NateDukatz
Copy link

@brian-mann I'm curious if you've seen any issues using .trigger('mouseover') with php/jqery elements? I am unable to make it work. It will go to the correct element but the element doesn't recognize it as a mouse hover.

@brian-mann
Copy link
Member

brian-mann commented Dec 1, 2017

Events all work the same way in Javascript. As long as you trigger the event that your element/plugin/whatever is bound to - it will fire.

It's possible your code is not bound to mouseover, or that you'll need to provide additional properties on the event to make it work. It all completely depends on how your code is written.

@NateDukatz
Copy link

NateDukatz commented Dec 1, 2017

@brian-mann I guess hoverIntent is what we are using:
https://github.com/briancherne/jquery-hoverIntent. Have you encountered this at all? Any ideas would be appreciated if you know off the top of your head.

@jennifer-shehane jennifer-shehane added the stage: proposal 💡 No work has been done of this issue label Dec 12, 2017
@tacb14
Copy link

tacb14 commented Apr 18, 2020

Just sharing a workaround that worked for my project, using the right click. I hope this can help some of you.
cy.get('button').rightclick()

@avallete
Copy link
Contributor

avallete commented May 9, 2020

Please use: #10 (comment)

@bestfei
Copy link

bestfei commented Jun 2, 2020

Just sharing a workaround that worked for my project, using the right click. I hope this can help some of you.
cy.get('button').rightclick()

it works.thx.
image

@farhan
Copy link

farhan commented Jun 16, 2020

This helped me out to at least show my element to be able to click it:
https://docs.cypress.io/v1.0/docs/invoke#section-properties-that-are-functions-are-invoked

cy.get('.content').invoke('show').click();

worked like a charm, thanks!

@rameshrc
Copy link

I have an application that is made of web components with lit-html. I could not make hover to work with the help of invoke or trigger along with experimental shadow dom as true. Any suggestions?

@dmtrKovalenko
Copy link
Contributor

It is possible to run a native system hover event using CDP. Find the ready-to-use hover command in this package https://github.com/dmtrKovalenko/cypress-real-events. Checkcy.realHover command.

@DannyFeliz
Copy link

It is possible to run a native system hover event using CDP. Find the ready-to-use hover command in this package dmtrKovalenko/cypress-real-events. Checkcy.realHover command.

This worked like a charm 🤗

@mysticdevx
Copy link

mysticdevx commented Dec 25, 2020

Three workarounds if .invoke('show') and .trigger('mouseover') doesn't work because of css :hover.

  1. .trigger('mouseenter');
  2. .rightclick();
  3. .nhover(); from here: https://github.com/avallete/cypress-nhover thanks to @avallete

@vishnuprabhu-95
Copy link

Awesome

@vegerot
Copy link

vegerot commented Jan 21, 2021

It is possible to run a native system hover event using CDP. Find the ready-to-use hover command in this package https://github.com/dmtrKovalenko/cypress-real-events. Checkcy.realHover command.

This is really cool. Unfortunately, I'm looking for a solution that works for Chrome and Firefox :(

@silversonicaxel
Copy link

@vegerot can't you just target these onHover tests just on Chrome?

@vegerot
Copy link

vegerot commented Feb 2, 2021

@silversonicaxel only if I didn't care about verifying my app on Firefox :p

@silversonicaxel
Copy link

Makes totally sense... I've a question by the way @vegerot
Are you maybe able to take snapshots of this realHover event?
I am trying to use Applitools combined with Cypress and this extra plugin, but I can't catch the mouse over state on a snapshot.

Thanks

@vegerot
Copy link

vegerot commented Feb 2, 2021

I haven't tried it, sorry

@dmtrKovalenko
Copy link
Contributor

@silversonicaxel I think this is impossible

@prashantabellad
Copy link

https://github.com/dmtrKovalenko/cypress-real-events. works nicely!

@matthias-dev
Copy link

matthias-dev commented May 19, 2021 via email

@DavidGWheeler
Copy link

https://github.com/dmtrKovalenko/cypress-real-events. works nicely!

Very nice! I hope this gets implemented for FF as well, but for now, this is excellent!

@tiec7
Copy link

tiec7 commented Nov 22, 2021

https://github.com/dmtrKovalenko/cypress-real-events. works nicely!

Very nice! I hope this gets implemented for FF as well, but for now, this is excellent!

I confirm this, I'm using Cypress for job and I have to replicate an hover on a menu.
I spent almost 3 hours, nothing worked but this, using cy.get(<element>).realHover() .

Have a try, no big installs, just yarn add cypress-real-events --dev, add a line in cypress/support/index.js and use it.

@SalahAdDin
Copy link

Three workarounds if .invoke('show') and .trigger('mouseover') doesn't work because of css :hover.

1. `.trigger('mouseenter');`

2. `.rightclick();`

3. `.nhover();` from here: https://github.com/avallete/cypress-nhover thanks to @avallete

I couldn't make it work.

@dmtrKovalenko
Copy link
Contributor

You can use cypress-real-events to test css as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
existing workaround pkg/driver This is due to an issue in the packages/driver directory stage: proposal 💡 No work has been done of this issue type: feature New feature that does not currently exist
Projects
None yet
Development

No branches or pull requests