Wait for animation to finish with k6 browser

I am attempting to wait for a component to finish rendering an animation I have. It’s a splash screen, that when done, exposes the main home page. Here is the life cycle of the div in question

// When page loads and as animation is occurring
<div id="splash-screen">...</div>

// After the animation is finished
<div id="splash-screen" class="animate finished">...</div>

The splash screen and the root of the app are separate. So within my body tag, I have:

<body>
   <div id="splash-screen" class="animate finished">...</div>
   <div id="root" class="root">...</div>
</body>

I have attempted several things to wait for this specific div to appear, but I keep getting an error thrown. I NEED to wait for this animation to finish because the k6 browser has issues with selecting a button as the animation is occurring. I just want to be able to tell the test “Wait for this specific div (i.e. animation div) to finish, after it has appeared, continue.” Currently, I have to set it to sleep to get past the animation screen reliably. I feel like I am missing something so simple, but I am not sure what it is. Why is it not able to just wait for the div with ID and class to appear? Below I will post what I have tried and show the error presented.

// GoError: waiting for selector "//div[@id=\"splash-screen\"][@class=\"animate finished\"]": DOMException: Failed to execute 'evaluate' on 'Document': The node provided is '#document-fragment', which is not a valid context node type.
page.waitForSelector('//div[@id="splash-screen"][@class="animate finished"]', {
      state: 'visible',
      timeout: 10000,
});
// GoError: waiting for selector "//div[@id=\"splash-screen\"][@class=\"animate finished\"]": DOMException: Failed to execute 'evaluate' on 'Document': The node provided is '#document-fragment', which is not a valid context node type.
page.waitForSelector('//div[@class="animate finished"]', {
      state: 'visible',
      timeout: 10000,
});
//GoError: waiting for "//div[@id=\"splash-screen\"][@class=\"animate finished\"]": execution context changed; most likely because of a navigation
const splashScreen = page.locator('//div[@id="splash-screen"][@class="animate finished"]');
splashScreen.waitFor({ state: 'visible', timeout: 10000 });

// This one is weird because there is not navigation happening on the page. The animation just turns off after the class is assigned.
// GoError: waiting for "div#splash-screen.animate.finished": timed out after 10000ms
const splashScreen = page.locator('div#splash-screen.animate.finished');
splashScreen.waitFor({ state: 'visible', timeout: 10000 });
// Uncaught (in promise) GoError: waiting for selector "div#splash-screen.animate.finished": timed out after 10000ms
page.waitForSelector('div#splash-screen.animate.finished', {
      state: 'visible',
      timeout: 10000,
});

Any help would be greatly appreciated!!!

Hi @zakirefai,

Welcome to the forums!

I’ll need to replicate this with a test website and get back to you with some options on workarounds or whether you’ve hit a genuine issue.

In the meantime, is there a chance you can share some/all of your test script which is pointing to a publicly available website?

Cheers,
Ankur

What I have provided is mostly the whole test script. I have found a workaround. I can use sleep to make the test wait for the splash screen to disappear, but this is not ideal.

import { browser } from 'k6/experimental/browser';
import { sleep } from 'k6';

export const options = {
  scenarios: {
    browser: {
      executor: 'shared-iterations',
      options: {
        browser: {
          type: 'chromium',
        },
      },
    },
  },
  thresholds: {
    checks: ['rate==1.0'],
  },
};

export default async function () {
  const page = browser.newPage();

  try {
    await page.goto(<url>);
    sleep(3);

    // await page.waitForSelector('//div[@id="splash-screen"][@class="animate finished"]', {
    //   timeout: 10000,
    //   state: 'attached',
    // });

    // Find the button on the page
    const button = page.locator('[data-testid="dummy-id"]');
    button.waitFor({ state: 'visible', timeout: 10000 });

  } finally {
    page.close();
  }
}

I, unfortunately, cannot share the website as it is for our QA environment. If I ever get permission to share it I will reply to this post, but I don’t think it will get approved.

@ankur, I spoke with my team. We want to ensure that we help the community if we have caught an actual issue. I have been permitted to give you the QA URL, but it would have to be over a private chat/call. I can even do a walk-through during this time. Is it possible to do this?

Hi @zakirefai,

Thanks for the test script. I can’t match the behaviour that you’re seeing. I forgot to ask earlier, which version of k6 are you using?

I setup the following test page:

<!DOCTYPE html>
<html>
    <head>
        <title>Wait for animation to end</title>
    </head>
    <body>
        <div id="splash-screen">Splash screen... animating</div>
        <div id="root" class="root">Root of the website</div>
    </body>
    <script>
        function updateSplashScreen() {
            var splashScreen = document.getElementById('splash-screen');
            splashScreen.classList.add('animate', 'finished');
            splashScreen.textContent = 'Splash screen. Animation complete.';

            var button = document.createElement('button');
            button.textContent = 'Click Me';
            button.setAttribute('data-testid', 'dummy-id');
            button.addEventListener('click', function() {
                this.textContent = 'Clicked';
            });

            var rootDiv = document.getElementById('root');
            rootDiv.appendChild(button);
        }
        
        setTimeout(updateSplashScreen, 5000);
        </script>
</html>

I ran the following test, which worked for me when i ran it against k6 v0.49.0, v0.48.0 and v0.47.0:

import { browser } from 'k6/experimental/browser';
import { check } from 'k6';

export const options = {
  scenarios: {
    browser: {
      executor: 'shared-iterations',
      options: {
        browser: {
          type: 'chromium',
        },
      },
    },
  },
  thresholds: {
    checks: ['rate==1.0'],
  },
};

export default async function () {
  const page = browser.newPage();

  try {
    await page.goto('http://localhost:81/static/tmp/wait-animation.html');

    page.waitForSelector('//div[@id="splash-screen"][@class="animate finished"]', {
      timeout: 10000,
      state: 'attached',
    });

    // Find the button on the page
    const button = page.locator('[data-testid="dummy-id"]');
    button.waitFor({ state: 'visible', timeout: 10000 });

    check(button, {
      'button-before-click': b => b.textContent() == 'Click Me',
    });

    await button.click();

    check(button, {
      'button-after-click': b => b.textContent() == 'Clicked',
    });
  } finally {
    page.close();
  }
}

If we can get access to the QA URL that would be useful :pray:

Cheers,
Ankur

1 Like

Hi @zakirefai,

Thank you for raising this issue with us. I’ve taken a look at your script and test website and I’ve found that the browser module doesn’t work well when trying to query for selectors on websites that contain DocumentFragments. The details and proposed solution can be found in issue #1243. I can’t be sure which release of k6 it will be merged into, but it will either be in k6 v0.50.0 (a couple of weeks away) or v0.51.0 (some months away).

Cheers,
Ankur

Would it be possible to include this in the next release? My team and I are ramping up on load testing and we would appreciate this fix being available in a stable version.

It looks like it will make it into the next release (v0.50.0).

2 Likes