typescript Puppeteer:page.click和page.evaluate单击回退

yqlxgs2m  于 2023-11-20  发布在  TypeScript
关注(0)|答案(1)|浏览(186)

正如标题所述,page.click()的大多数情况下都可以工作。然而,有时,它点击而页面没有对操作做出React。有两种详细的情况:

  1. page.click()不工作并抛出错误。在这种情况下,我可以回退以正确评估单击。
  2. page.click()确实工作,它不会抛出错误。页面对单击没有React。在这种情况下,我不知道如何回退到另一个单击方法。
    有一个轻微的机会,评估点击不工作,以及。因此,我认为最好的解决方案是回落到对方时,未能点击。这是我的代码:
async clickElement(
    page: Page,
    projectName: string,
    title: string,
    selector: string,
    timeout = 1000,
    preventNavigation = false
  ): Promise<boolean> {
    Logger.log(selector, 'CSSClickStrategy.clickElement');

    // Ensure the selector is present before proceeding
    // it could be a navigation before awaiting the selector
    await Promise.race([
      page.waitForSelector(selector, { timeout, visible: true }),
      page.waitForNavigation({ timeout }),
    ]);
    //TODO: how to determine to switch between the two click methods
    // Add navigation prevention if required
    if (preventNavigation) {
      this.preventNavigationOnElement(page, selector);
      return this.evaluateClick(page, projectName, title, selector);
    } else {
      // const result = await this.normalClick(page, projectName, title, selector);
      const result = await this.evaluateClick(
        page,
        projectName,
        title,
        selector
      );
      if (!result) {
        // return await this.evaluateClick(page, projectName, title, selector);
        return await this.normalClick(page, projectName, title, selector);
      } else {
        return result;
      }
    }
  }

  private async preventNavigationOnElement(page: Page, selector: string) {
    await page.evaluate((sel) => {
      const element = document.querySelector(sel);
      if (element) {
        element.addEventListener('click', (e) => e.preventDefault());
      }
    }, selector);
  }

  private async evaluateClick(
    page: Page,
    projectName: string,
    title: string,
    selector: string,
    timeout = 10000
  ): Promise<boolean> {
    try {
      await Promise.race([
        page.evaluate((sel) => {
          const element = document.querySelector(sel) as HTMLElement;
          element?.click();
        }, selector),
        new Promise((_, reject) =>
          setTimeout(() => reject(new Error('Timeout exceeded')), timeout)
        ),
      ]);
      Logger.log(
        `Clicked using page.evaluate for selector: ${selector}`,
        'CSSClickStrategy.clickElement'
      );
      return true;
    } catch (error) {
      Logger.error(error.message, 'CSSClickStrategy.evaluateClick');
      return false;
    }
  }

  private async normalClick(
    page: Page,
    projectName: string,
    title: string,
    selector: string,
    timeout = 10000
  ): Promise<boolean> {
    try {
      await Promise.race([
        page.click(selector, { delay: 100 }),
        new Promise((_, reject) =>
          setTimeout(() => reject(new Error('Timeout exceeded')), timeout)
        ),
      ]);
      Logger.log(
        `Clicked using page.click for selector: ${selector}`,
        'CSSClickStrategy.clickElement'
      );
      return true;
    } catch (error) {
      Logger.error(error.message, 'CSSClickStrategy.clickElement');
      return false;
    }
  }

字符串
任何帮助都非常感谢。

ia2d9nvy

ia2d9nvy1#

我已经找到了一种基于某些条件的相互退回的方法。例如,我发现大多数时候评估点击都可以工作,但在第三方电子商务支付网关上不行。因此,我page.click在支付页面上使用www.example.com()。否则,默认使用评估点击。另一个发现是,在隐身上下文下有一个标签,evaluate单击不能正常工作。下面是我的解决方案代码,基于条件,并在遇到错误时相互回退:

async clickElement(
    page: Page,
    projectName: string,
    title: string,
    selector: string,
    timeout = 1000,
    preventNavigation = false
  ): Promise<boolean> {
    Logger.log(selector, 'CSSClickStrategy.clickElement');
    const domain = new URL(
      this.sharedService.getProjectDomain(projectName, {
        absolutePath: undefined,
        name: title,
      })
    ).hostname;

    // Ensure the selector is present and visible before proceeding
    await Promise.race([
      page.waitForSelector(selector, { timeout, visible: true }),
      page.waitForNavigation({ timeout }),
    ]);

    // Determine the click method based on conditions
    // only one page means checking datalayer; two pages mean checking with gtm preview mode
    // if the current page is not the same as the domain, then it's a third-party gateway
    const useNormalClick =
      (await page.browserContext().pages()).length === 1 ||
      !page.url().includes(domain);

    if (preventNavigation) {
      this.preventNavigationOnElement(page, selector);
    }

    if (useNormalClick) {
      return await this.attemptClick(
        page,
        projectName,
        title,
        selector,
        this.normalClick
      );
    } else {
      return await this.attemptClick(
        page,
        projectName,
        title,
        selector,
        this.evaluateClick
      );
    }
  }

  async attemptClick(
    page: Page,
    projectName: string,
    title: string,
    selector: string,
    clickMethod: (
      page: Page,
      projectName: string,
      title: string,
      selector: string
    ) => Promise<boolean>
  ) {
    const result = await clickMethod.call(
      this,
      page,
      projectName,
      title,
      selector
    );
    if (!result) {
      // Fallback to the other click method
      return await (clickMethod === this.normalClick
        ? this.evaluateClick
        : this.normalClick
      ).call(this, page, projectName, title, selector);
    }
    return result;
  }
 // other private methods are in the question content

字符串
以下是我的启动设置以供参考:

const browser = await puppeteer.launch({
      headless: headless === 'new' ? 'new' : false,
      defaultViewport: null,
      ignoreHTTPSErrors: true,
      args: [
        '--window-size=1440,900',
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',
        '--disable-accelerated-2d-canvas',
        '--disable-gpu',
      ],
    });

相关问题