Skip to content

bug: buttons can't be clicked by role in Playwright in 7.4.1 #28241

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

Closed
3 tasks done
ptmkenny opened this issue Sep 27, 2023 · 5 comments
Closed
3 tasks done

bug: buttons can't be clicked by role in Playwright in 7.4.1 #28241

ptmkenny opened this issue Sep 27, 2023 · 5 comments
Labels
ionitron: needs reproduction a code reproduction is needed from the issue author

Comments

@ptmkenny
Copy link

Prerequisites

Ionic Framework Version

v7.x

Current Behavior

I use Playwright to test my Ionic React app. I click ion-button using locator.getByRole('button', { name: label }) and locator.getByRole('link', { name: label }). This works in 7.4.0 (and previous versions-- I tried 7.3.0, 7.2.0), but not 7.4.1.

In 7.4.1, I get an error:

Error: no locator visible before timeout Locator@ion-router-outlet div.ion-page:not(.ion-page-hidden) >> internal:role=button[name="Confirm email address"i],Locator@ion-router-outlet div.ion-page:not(.ion-page-hidden) >> internal:role=link[name="Confirm email address"i]

Expected Behavior

I expect to be able to use Playwright to click ion-button reliably.

Steps to Reproduce

Here are the complete functions I use to click ion-button with Playwright:

  async clickButtonIonPage(label: string): Promise<void> {
    const ionPage = await this.getLocatorIonPageVisible();
    await IonicPage.clickIonButtonBase(label, ionPage);
  }

  /**
   * Clicks an ion-button.
   *
   * ion-button can be identified by role (button or link).
   *
   * Playwright doesn't support 'role A or B' natively so use
   * a custom function waitForOneOf().
   *
   * @param label The label to click.
   * @param targetLocator The locator containing the button.
   */
  public static async clickIonButtonBase(
    label: string,
    targetLocator: Locator,
  ): Promise<void> {
    const [, locator] = await waitForOneOf([
      targetLocator.getByRole('button', { name: label }),
      targetLocator.getByRole('link', { name: label }),
    ]);
    await locator.click();
  }

type WaitForRes = [locatorIndex: number, locator: Locator];

// https://stackoverflow.com/a/74403846
async function waitForOneOf(locators: Locator[]): Promise<WaitForRes> {
  // timeToWait can be long because if it fails, the whole test fails anyway.
  const timeToWait = 5000;
  const res = await Promise.race([
    ...locators.map(async (locator, index): Promise<WaitForRes> => {
      // https://stackoverflow.com/a/76698580
      let timedOut = false as boolean;
      await locator
        .waitFor({ state: 'visible', timeout: timeToWait })
        .catch(() => {
          timedOut = true;
        });
      return [timedOut ? -1 : index, locator];
    }),
  ]);
  if (res[0] === -1) {
    throw new Error(`no locator visible before timeout ${locators.toString()}`);
  }
  return res;
}

  /**
   * Ensures that the page has transitioned before grabbing a selector.
   */
  private async ensurePageTransition() {
    // Todo: Playwright docs note that waitForTimeout() is debug only.
    // However, when navigating between pages, we need to wait to ensure
    // that Ionic routing is complete so that we get the right page.
    await this.page.waitForTimeout(this.minimumWaitTimeForPageTransitions);
  }

  /**
   * Returns a locator for the currently visible router page.
   *
   * react-router 5 keeps pages in the stack. This can cause Playwright
   * to fail because locators may see things in pages in the stack that
   * are not currently being displayed.
   *
   * @param className An optional class to add to limit the locator.
   * @returns A locator showing the currently displayed page.
   */
  async getLocatorIonPageVisible(className?: string) {
    await this.ensurePageTransition();
    if (className) {
      const ionPageVisible = this.page.locator(
        `${this.selectorIonPageVisible} .${className}`,
      );
      return ionPageVisible;
    }
    const ionPageVisible = this.page.locator(this.selectorIonPageVisible);
    return ionPageVisible;
  }

  /**
   * Selector for the currently displayed ion-page.
   *
   * Previously viewed ion-page have .ion-page-hidden.
   *
   * In addition, ion-app may have div.ion-page, so
   * require ion-router-outlet.
   */
  public readonly selectorIonPageVisible =
    'ion-router-outlet div.ion-page:not(.ion-page-hidden)';

Code Reproduction URL

No response

Ionic Info

Ionic:

Ionic CLI : 7.1.1 (/opt/homebrew/lib/node_modules/@ionic/cli)
Ionic Framework : @ionic/react 7.4.1

Capacitor:

Capacitor CLI : 5.4.1
@capacitor/android : 5.4.1
@capacitor/core : 5.4.1
@capacitor/ios : 5.4.1

Utility:

cordova-res : 0.15.4
native-run : 1.7.3

System:

NodeJS : v20.7.0 (/opt/homebrew/Cellar/node/20.7.0/bin/node)
npm : 10.1.0
OS : macOS Unknown

Additional Information

No response

@ionitron-bot ionitron-bot bot added the triage label Sep 27, 2023
@liamdebeasi liamdebeasi added the ionitron: needs reproduction a code reproduction is needed from the issue author label Sep 27, 2023
@ionitron-bot
Copy link

ionitron-bot bot commented Sep 27, 2023

Thanks for the issue! This issue has been labeled as needs reproduction. This label is added to issues that need a code reproduction.

Please reproduce this issue in an Ionic starter application and provide a way for us to access it (GitHub repo, StackBlitz, etc). Without a reliable code reproduction, it is unlikely we will be able to resolve the issue, leading to it being closed.

If you have already provided a code snippet and are seeing this message, it is likely that the code snippet was not enough for our team to reproduce the issue.

For a guide on how to create a good reproduction, see our Contributing Guide.

@ionitron-bot ionitron-bot bot removed the triage label Sep 27, 2023
@ptmkenny
Copy link
Author

I'm working on a reproduction, but I'm having a little trouble slimming down my app so it might take a bit.

Here's what I've found:

In 7.4.1, aria-hidden is applied where it shouldn't be. The buttons that Playwright fails to click all show aria-hidden is true on ancestor: ion-router-outlet in Chrome Dev Tools-> Elements -> Accessibility.

So it appears that this issue is a follow-up to #28183.

@ptmkenny
Copy link
Author

More info: Still working on the reproduction, but it appears that all my problems are related to <IonLoading>, which I use in dozens of places throughout the app. I have <IonLoading> wrapped in a <Loading> component, and when I swap out <IonLoading> for a plain text loader, aria-hidden is no longer misapplied.

@liamdebeasi
Copy link
Contributor

Hey there! I'm not able to reproduce this on my end, so I'm going to close this for now. However, if you are able to create a minimal reproduction feel free to reply here and I can take a look. Thanks!

@liamdebeasi liamdebeasi closed this as not planned Won't fix, can't repro, duplicate, stale Oct 12, 2023
Copy link

ionitron-bot bot commented Nov 11, 2023

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.

@ionitron-bot ionitron-bot bot locked and limited conversation to collaborators Nov 11, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
ionitron: needs reproduction a code reproduction is needed from the issue author
Projects
None yet
Development

No branches or pull requests

2 participants