I’m adding a couple of animations to an app that has Percy taking snapshots in the cypress tests.
I’m trying to figure out a way to wait for the animations to finish before taking the snapshot.
I’m aware that cypress already does that by default according to the docs. But it seems that it only waits until the element is safe for an ACTION, but not regular assertions.
Let’s use my logo animation for example. It slides from the left to center. The flaky tests capture the snapshot anywhere in its path.
Now, just for a test, I asked cypress to click the logo and then Percy works alright, the snapshot is only taken at the end of the animation.
For this case it’s ok, since my logo does not have an action related to it. But what if clicking it had a redirect, for example?
How can I tell cypress to wait without using an action?
Can you assert the slide has finished before the snapshot?
It would involve a element.getBoundingClientRect() and manually checking the position.
it('waits for logo to slide to center',
{viewportWidth: 1000}, // ensure fixed, appropriate viewport for the test
() => {
cy.get('#myLogo')
.should($logo => { // using should will retry until animation finishes
const left = $logo[0].getBoundingClientRect().left
expect(left).to.eq(450)
})
// snapshot now
}
)
I'm doing this with a drag and drop scenario, but finding the numbers have rounding errors.
I've added chai-almost to sort it out.
const chaiAlmost = require('chai-almost')
chai.use(chaiAlmost(1)) // allowed amount of variance in equality is 1
...
expect(left).to.almost.eq(450)
Simplest way is to add a hard cy.wait(animation-time-in-ms) before the snapshot.
It won't make the difference to the test runtime since you need the animation to finish before the test can complete.
Related
TL;DR: In a Laravel + InertiaJS + Vue 3 developed application, how can I achieve to have a transition between "pages" loaded within a persistent layout <main> section - for example, have that section animate (say, fade out) before loading the next page, then animate (fade) that new page in - when using standard Inertia routing for navigation? I have managed to do it on entering/showing the page, but have found no way to animate before navigation happens.
LONG(ish): The Way I'm trying to do it
Let's assume there is an application (developed with Laravel + InertiaJS + Vue 3).
I have an element in the markup of an Inertia persistent layout that is conditionally shown if a value is true (v-if="shouldAnimate") that is initially set to false when declared, and when onMounted fires, it sets that value to true which in turn triggers the animation to run (doesn't really matter how the animation works, but just in case, I have options to use either GSAP or anime.js).
Up to this point, all is good: every time I navigate to a page (using Inertia-adequate methods such as the Link component) the animation triggers and I am a happy guy.
BUT: I would very much like to be able to play another animation (the reverse of the previous one) before, say, navigation to the next page occurs. I have tried almost everything I can think of and have not been successful. Here's what got the closest to what I need:
I tried hooking into the InertiaJS event Inertia.on('before', ...): effectively, the event fires up right before navigation (checked with some good-old console log), so I tried firing up the animation at this point, only to find out that the Inertia event looks like it is destroying the page immediately before the animation has had time to play; no problem: I'll just event.preventDefault() it, run the animation and THEN, using a setTimeout timed to the length of the animation (300ms) I'll resume navigation, say, by using Inertia.visit.
Doesn't work. Somehow, the default behaviour is prevented (stops the navigation), the animation plays back, but when it comes to the "resume navigation part" I have had mixed results depending on what I use:
Code looks roughly like this:
let removeListener = Inertia.on('before', (event) => {
event.preventDefault()
// Play animation here
setTimeout(() => {
// SOME INERTIA ACTION DESCRIBED BELOW
}, 300)
})
Independently of whether I use Inertia.get(event.detail.visit.url) or Inertia.visit(event.detail.visit.url) what happens is the animation runs its course, and then the timer runs out and this whole code RUNS AGAIN AND AGAIN in intervals equal to the timer. I also tried to do this using the complete event of the animation to trigger the navigation but it behaves the same.
I know this is related to me being an ignorant about how both Inertia and events work, and I am sure there is a proper (correct? right?) way to achieve what I need, but either I have failed in using the correct terms to look for it, or I am approaching this the wrong way. Hopefully this information is enough to explain my issue.
Any help or pointer would be GREATLY appreciated, so thanks in advance.
I need to stop this carousel in order to perform validation on it
Tried to set autoplay=false
The autoplay function is based on setInterval() which is able to be controlled by Cypress.
See the Cypress documentation for clock
cy.clock() overrides native global functions related to time allowing them to be controlled synchronously via cy.tick() or the yielded clock object. This includes controlling:
setTimeout
clearTimeout
setInterval
clearInterval
Date Objects
So you should be able to freeze the carousel by putting the cy.clock() command at the top of your test, or before the cy.visit() page load.
In case you want to test the second slide and the third slide, etc, you can issue a cy.tick(5000) to move the autoplay on for one slide each time, since you configured autoplaySpeed: 5000.
Every time I render a new view it seems to have a default fade in animation where the opacity goes from 0 - 1 over about 200 milliseconds. Is there any way to turn this off so the view appears immediately?
Sample code that would trigger the default animation;
{(() => {
if (itemIsOpen) {
return (
<CardOverlay />
)
}
})()}
CardOverlay component fades in when I want it to immediately appear.
I've been struggling with the same thing for a while now, it's been driving me around the bend but I've finally found the cause of it. Not sure if it's the same thing that caused your problem (and not sure if it's still relevant for you) but I thought I'd share it incase someone runs into the same problem.
My issue was caused by Animated.spring() -- I was using it on a sub-component (a little button that makes an image appear on the page) but it seems that as soon as you add spring anywhere in your visual tree it affects everything. Even if you put it in componentDidMount on the inner component it still affects the page-load animation for me.
Time to learn a bit more about how the react Animated library works methinks!
I have a web page which can be janky to scroll if certain styles are applied. My question is how can I systematically test the effect of individual styles. I don't want to just manually scroll down the page each time, I want to perform some replicable action so I can easily compare the effect of two different stylesheets. Is it possible to record and replay a sequence?
In Chrome you can try using:
console.timeline('description');
// your code
console.timelineEnd('description');
You'll get a deprecation warning, but it works to record a timeline.
OR
You could use The Intern http://theintern.io/ to automate the repeated task you want to do, for instance scrolling down the page.
In that case, you would write a test case for each style change, scroll the page while measuring with the Frame Timing API
https://github.com/GoogleChrome/frame-timing-polyfill
Each test would expect the frame rate to be above a certain value.
I am using Selenium Webdriver(Ruby) to automate my web app and my web app has this carousel wherein my element continuously keeps moving in a loop.
By the time I locate that element and try to click it, element moves ahead.Hence I am not able to locate that element.
I tried finding and clicking that moving element by following code:
{
ele_button = driver.find_element(:xpath,"xpath")
sleep 10
ele_button.click
}
I thought that by 'sleep 10' I could make that element wait for 10 seconds and then click it.But this does not work and I am getting ElementNotVisibleError whenever I run my script.
Question:
Is it even possible to automate a moving element? If yes please provide me a solution.
Yes it is absolutely possible. I handled same scenario for carousel on my site. There are three ways:
Most carousel stop on mouse hover. So you may use it to stop the
carousel. Use Actions class to move over to the carousel. Once it
stops you may click on it.
If you want a specific slide, you can click on dots or any other navigator, like prev/nxt, to reach your slide and then click it.
The sure shot way to click your specific slide, without worrying about whether it is displayed or not is to use Javascript to click it (Which I had done in my case although I had also implemented 2nd way but I found javascript the simplest solution).
Why are the actions divided?
I would recommend the following:
driver.find_element(:xpath,"xpath").click()
so it will find the object and click on it.
Another thing you can do, as we doing in selenium with java that put the implicitlyWait according to the time on which your button came back after one rotation; now perform click just after implicitlyWait
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
// Suppose a rotation of button takes 30 sec
driver.findElement(By.xpath("/html/body/div[2]")).click();
// action performs on the element
In ruby you have to use this type of syntax
#driver.manage.timeouts.implicit_wait = 30