Steve
Steve

Reputation: 81

Using animejs with webdriver > ReferenceError: NodeList is not defined

Using webdriverio to create a SaaS test. I'd like to add mouse animation using animejs so I can record the test to produce a how-to video.

In my webdriverio test (addAdditionalUsers.spec.js), I have

await closeBtn.animateMouse(); //line 34

I am importing animateMouse from functions.js:

export async function animateMouse() {
    await browser.addCommand('animateMouse', async function() {
        await this.waitForExist();
        const rect = await browser.execute(elem => elem.getBoundingClientRect(), await this);
        const targetX = rect.x;
        const targetY = rect.y;
        anime({                             //line 32
            targets: '#webdriver-mouse',
            left: targetX + 'px',
            top: targetY + 'px',
            duration: 1000, // Duration of the animation in milliseconds
            easing: 'linear'
        })
    }, true);
}

When I ran the test I received an error:

ReferenceError: NodeList is not defined
    at toArray (/node_modules/animejs/lib/anime.js:321:20)
    at parseTargets (/node_modules/animejs/lib/anime.js:644:87)
    at getAnimatables (/node_modules/animejs/lib/anime.js:649:16)
    at createNewInstance (/node_modules/animejs/lib/anime.js:845:21)
    at anime (/node_modules/animejs/lib/anime.js:933:18)
    at Element.<anonymous> (/test/includes/functions.js:32:14)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
            at Context.<anonymous> (/test/specs/addAdditionalUsers.spec.js:34:9)

I would like animejs to move the mouse to the closeBtn element.

Update

After changing animateMouse() to VonCs version, there is an error

Evaluation failed: TypeError: Cannot read properties of null (reading 'style'), functions.js:32:9, addAdditionalUsers.spec.js:33:9). 

Line 33 of addAdditionalUsers.spec.js is await closeBtn.animateMouse();

Line 32 of functions.js is from Vonc's animateMouse() function:

await browser.execute((x, y) => {

addAdditionalUsers.spec.js contains import { createMouse } from '../../includes/mouse'; and createMouse(); on line 32.

/includes/mouse.js contains:

export function createMouse() {
    browser.execute(() => {
        var mouse = document.createElement('img');
        // console.log("test");
        mouse.setAttribute(
            'src',
            'data:image/png;base64,' +
            'iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAQAAACGG/bgAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAA' +
            'HsYAAB7GAZEt8iwAAAAHdElNRQfgAwgMIwdxU/i7AAABZklEQVQ4y43TsU4UURSH8W+XmYwkS2I0' +
            '9CRKpKGhsvIJjG9giQmliHFZlkUIGnEF7KTiCagpsYHWhoTQaiUUxLixYZb5KAAZZhbunu7O/PKf' +
            'e+fcA+/pqwb4DuximEqXhT4iI8dMpBWEsWsuGYdpZFttiLSSgTvhZ1W/SvfO1CvYdV1kPghV68a3' +
            '0zzUWZH5pBqEui7dnqlFmLoq0gxC1XfGZdoLal2kea8ahLoqKXNAJQBT2yJzwUTVt0bS6ANqy1ga' +
            'VCEq/oVTtjji4hQVhhnlYBH4WIJV9vlkXLm+10R8oJb79Jl1j9UdazJRGpkrmNkSF9SOz2T71s7M' +
            'SIfD2lmmfjGSRz3hK8l4w1P+bah/HJLN0sys2JSMZQB+jKo6KSc8vLlLn5ikzF4268Wg2+pPOWW6' +
            'ONcpr3PrXy9VfS473M/D7H+TLmrqsXtOGctvxvMv2oVNP+Av0uHbzbxyJaywyUjx8TlnPY2YxqkD' +
            'dAAAAABJRU5ErkJggg=='
        );
        mouse.id = 'webdriver-mouse';
        mouse.setAttribute(
            'style',
            'position: absolute; z-index: 999999; pointer-events: none; left:0; top:0'
        );
        // console.log(mouse);
        document.appendChild(mouse);
    });
}

and the my webdriver test contains createMouse(); on line 26.

#webdriver-mouse is created by createMouse(); okay.

addAdditonalUsers.spec.js contains:

import Studio from '../../pageobjects/studio.page'; 
import { createMouse } from '../../includes/mouse'; 
import { highlight, animateMouse } from '../../includes/functions';

describe('Locking an Element', () => {
    beforeEach(async () => {  
        // await browser.maximizeWindow()
        await browser.setWindowSize(1920, 1080);
        await browser.pause(3000);
        await browser.url('/login');
        await(await Studio.usernameField).click();
        await browser.keys('user@example,com')
        await(await Studio.passwordInput).click();
        await browser.keys('password');
        await(await Studio.loginSubmit).click();
        await browser.pause(6000);
    });
    afterEach(async () => {
        await browser.execute('window.localStorage.clear()');     
        await browser.deleteAllCookies();
        await browser.execute('sessionStorage.clear()');
        await browser.refresh();
        await browser.pause(3000);
    });
    it('Add Additional Users', async () => {
        createMouse(); //line 26

        // close the welcome screen
        await(await $('.cdk-overlay-container .cdk-overlay-backdrop')).waitForDisplayed({timeout:9000});
        await highlight();
        await animateMouse()
        const closeBtn = await $('.mat-dialog-container.cdk-dialog-container .close-btn-icon');
        await closeBtn.animateMouse(); // line 33
        await closeBtn.highlight();
        await browser.pause(1000);
        await $(closeBtn).click();
        await browser.pause(1000);

Upvotes: 5

Views: 517

Answers (1)

Keyboard Corporation
Keyboard Corporation

Reputation: 2785

When you do @VonC's version, the error thrown because it try to access a property of an object that is null because the element try to animate does not exist or is not yet available at the time when the animateMouse is called. The browser.execute runs scripts in the context of the browser and returns the result of the script. If the script does not return anything, browser.execute will return null.

So here, we need to make the animateMouse wait until the #webdriver-mouse is available before it starts the animation.

import { Tween, Easing } from '@tweenjs/tween.js';

export async function animateMouse() {
  await browser.addCommand('animateMouse', async function() {
      await this.waitForExist();
      const rect = await browser.execute(elem => elem.getBoundingClientRect(), await this);
      const targetX = rect.x;
      const targetY = rect.y;

      // Wait for the #webdriver-mouse element to be available
      await browser.waitUntil(async () => {
          const mouse = await browser.execute(() => document.querySelector('#webdriver-mouse'));
          return mouse !== null;
      });

      // Using Tween.js for animation
      await browser.execute((x, y) => {
          const mouse = document.querySelector('#webdriver-mouse');
          const start = { x: mouse.style.left, y: mouse.style.top };
          const end = { x: x + 'px', y: y + 'px' };

          new Tween.Tween(start)
              .to(end, 1000)
              .easing(Tween.Easing.Linear.None)
              .onUpdate(function() {
                mouse.style.left = this.x;
                mouse.style.top = this.y;
              })
              .start();

          function animate(time) {
              requestAnimationFrame(animate);
              Tween.update(time);
          }

          animate();
      }, targetX, targetY);
  }, true);
}

Edit

the mouse element is logged in the console fine. However, there is still the error Error: waitUntil condition timed out after 10000ms at Element. (.../includes/functions.js:32:7)

It seems that browser.execute is creating the element, but it's not visible to the script, so browser.waitUntil times out because it can't find the element.

As I see here, need to modify the createMouse to return a promise that resolves when the element has been created, then wait for this promise to resolve before calling animateMouse.

The below code expect to make animateMouse wait for the #webdriver-mouse to be created before trying to animate it, which should prevent the waitUntil from timing out.

In createMouse(),

export function createMouse() {
  return new Promise((resolve) => {
      browser.execute(() => {
          var mouse = document.createElement('img');
          mouse.setAttribute(
              'src',
              'data:image/png;base64,' +
              'iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAQAAACGG/bgAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAA' +
              'HsYAAB7GAZEt8iwAAAAHdElNRQfgAwgMIwdxU/i7AAABZklEQVQ4y43TsU4UURSH8W+XmYwkS2I0' +
              '9CRKpKGhsvIJjG9giQmliHFZlkUIGnEF7KTiCagpsYHWhoTQaiUUxLixYZb5KAAZZhbunu7O/PKf' +
              'e+fcA+/pqwb4DuximEqXhT4iI8dMpBWEsWsuGYdpZFttiLSSgTvhZ1W/SvfO1CvYdV1kPghV68a3' +
              '0zzUWZH5pBqEui7dnqlFmLoq0gxC1XfGZdoLal2kea8ahLoqKXNAJQBT2yJzwUTVt0bS6ANqy1ga' +
              'VCEq/oVTtjji4hQVhhnlYBH4WIJV9vlkXLm+10R8oJb79Jl1j9UdazJRGpkrmNkSF9SOz2T71s7M' +
              'SIfD2lmmfjGSRz3hK8l4w1P+bah/HJLN0sys2JSMZQB+jKo6KSc8vLlLn5ikzF4268Wg2+pPOWW6' +
              'ONcpr3PrXy9VfS473M/D7H+TLmrqsXtOGctvxvMv2oVNP+Av0uHbzbxyJaywyUjx8TlnPY2YxqkD' +
              'dAAAAABJRU5ErkJggg=='
          );
          mouse.id = 'webdriver-mouse';
          mouse.setAttribute(
              'style',
              'position: absolute; z-index: 999999; pointer-events: none; left:0; top:0'
          );
          document.appendChild(mouse);
          resolve();
      });
  });
}

In animateMouse(),

export async function animateMouse() {
  await browser.addCommand('animateMouse', async function() {
      await this.waitForExist();
      const rect = await browser.execute(elem => elem.getBoundingClientRect(), await this);
      const targetX = rect.x;
      const targetY = rect.y;

      // Wait for the #webdriver-mouse element to be created
      await createMouse();

      // Wait for the #webdriver-mouse element to be available
      await browser.waitUntil(async () => {
          const mouse = await browser.execute(() => document.querySelector('#webdriver-mouse'));
          return mouse !== null;
      });

      // Using Tween.js for animation
      await browser.execute((x, y) => {
        const mouse = document.querySelector('#webdriver-mouse');
        const start = { x: mouse.style.left, y: mouse.style.top };
        const end = { x: x + 'px', y: y + 'px' };

        new Tween.Tween(start)
            .to(end, 1000)
            .easing(Tween.Easing.Linear.None)
            .onUpdate(function() {
              mouse.style.left = this.x;
              mouse.style.top = this.y;
            })
            .start();

        function animate(time) {
            requestAnimationFrame(animate);
            Tween.update(time);
        }

        animate();
    }, targetX, targetY);
  }, true);
}

Upvotes: 2

Related Questions