0

I am performing a test on a dropdown animated menu using Playwright. The test purpose is to know if the last menu item appears in the viewport. The menu is higher than the viewport so the user needs to scroll the page in order to see it.

I have two issues:

  1. It takes time till the menu's last item is seen because the animation takes time.
  2. In order to know if the last menu item is in the viewport I use the following function. the issue is that it takes about 1.5 - 2 seconds to receive the item's position.

At the moment I am using waitForTimeout But I want to know how I can replace them with methods that will recognize that the previous stage is ended.

The functions:

async function openMobileMenu( page ) {
    await page.waitForSelector( '[aria-label="Menu Toggle"]' );

    await page.locator( '[aria-label="Menu Toggle"]' ).click();

    await page.locator( '.nav-menu--dropdown.nav-menu__container .submenu-class-for-playwright-test' ).first().click();

    await page.locator( '.nav-menu--dropdown.nav-menu__container .subsubmenu-class-for-playwright-test' ).first().click();

    // Estimated timeout needed for the menu to open.  
    await page.waitForTimeout( 1500 );  // This is the first time out I want to replace.

    // Scroll to the bottom of the page.
    await page.evaluate( () => window.scrollBy( { top: 2000, behaviour: 'instant' } ) );

    // Scroll to the bottom of the sticky menu.
    await page.evaluate( () => {
        const mobileMenu = document.querySelector( '.nav-menu--dropdown.nav-menu__container' );
        mobileMenu.scrollBy( { top: 2000, behaviour: 'instant' } );
    } );

    // Estimated timeout needed scroll down to the bottom of the screen.
    await page.waitForTimeout( 1500 );   // This is the second time out I want to replace.
}

//This is how I calculate if the last menu item is in the viewport.
    async function isItemInViewport( page, lastMenuItem ) {
        return await page.evaluate( ( lastMenuItem ) => {
            let isVisible = false;

        const element = document.querySelector( lastMenuItem );
        if ( element ) {
            const rect = element.getBoundingClientRect();
            if ( rect.top >= 0 && rect.left >= 0 ) {
                const vw = Math.max( document.documentElement.clientWidth || 0, window.innerWidth || 0 );
                const vh = Math.max( document.documentElement.clientHeight || 0, window.innerHeight || 0 );
                if ( rect.right <= vw && rect.bottom <= vh ) {
                    isVisible = true;
                }
            }
        }
        return isVisible;
    }, lastMenuItem );
}

The test:

await openMobileMenu( page );

const lastMenuItem = '.nav-menu--dropdown.nav-menu__container .lastitem-class-for-playwright-test';

// Assertion
await expect( await isItemInViewport( page, lastMenuItem ) ).toBeTruthy();

I have tried using waitForFunction and also adding a timeout to the scroll event but it didn't help.

This is what I have tried so far:

async function openMobileMenu( page, lastMenuItem ) {
    await page.waitForSelector( '[aria-label="Menu Toggle"]' );

    const menuToggle = await page.locator( '[aria-label="Menu Toggle"]' );

    await menuToggle.click();

    await page.locator( '.-menu--dropdown.nav-menu__container .submenu-class-for-playwright-test' ).first().click();

    const subSubMenuClass = '.nav-menu--dropdown.nav-menu__container .subsubmenu-class-for-playwright-test';

    const subSubMenu = page.waitForSelector( subSubMenuClass );

    await subSubMenu.waitForElementState( 'stable' );  // receive this error: subSubMenu.waitForElementState is not a function

    await page.locator( subSubMenuClass ).first().click();

    // Estimated timeout needed for the menu to open.
    // await page.waitForTimeout( 1500 );
    // await subSubMenu.waitFor( { state: 'attached' } );
    await page.waitForLoadState( 'networkidle' );  // this wont help because the page is already fully loaded

    // Scroll to the bottom of the page.
    await page.evaluate( () => window.scrollBy( { top: 2000, behaviour: 'instant' } ) );
DavSev
  • 1,005
  • 4
  • 22
  • 47
  • 1
    Is this something that can help https://stackoverflow.com/questions/71937343/playwright-how-to-wait-until-there-is-no-animation-on-the-page/71938338#71938338 – Gaj Julije Nov 20 '22 at 20:38
  • Thank for your answer, I tried, the test doesn't fall but I get a false answer. it looks like I have to wait some time after the last item in the menu is visible on the screen before I can calculate its position. I added what I have tried so far at the end of the question. – DavSev Nov 21 '22 at 05:21

1 Answers1

1

You could try and disable your animations using an utils.ts and see if that helps, so something like this in your utils:

export async function disableAnimations( page: Page ) {
  await page.addStyleTag({
    content: `
      *, *::before, *::after {
        animation-duration: 0s !important;
        transition-duration: 0s !important;
      }
    `,
  });
}

And in your Test you just call disableAnimations(page)

Other than that you could try out the await lastMenuItem.scrollIntoViewIfNeeded() function from playwright and see if that helps in your case.

Basti
  • 449
  • 2
  • 13