Skip to content

[Feature] evaluate timeout  #13253

Description

Hello,

This is half feature request / half bug report, depends on how you read it :D
I noticed evaluate() do not have a timeout and this can be problematic when executing on a busy thread that will never let go.

# http://localhost:3000/page-crash.html
<!DOCTYPE html>
<html>

<head> </head>

<body>
  <script>
    txt = "a";
    while (1) {
      txt = txt += "a";  
    }
  </script>
</body>

</html>
const playwright = require('playwright');

(async () => {
  console.log('launching');
  const browser = await playwright.chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();

  page.on('crash', () => {
    console.log('Page crashed');
  });

  console.log('before goto');
  try {
    res = await page.goto('http://localhost:3000/page-crash.html', {
      waitUntil: "domcontentloaded",
      timeout: 2000,
    });
  } catch (e) {
    // ignore timeout error
  }
  console.log('after goto');

  await page.evaluate(() => {
    console.log('hello');
  });
  console.log('after evaluate');

  await browser.close();
  console.log('close');

})()

Will Display

~ node index.js
launching
before goto
after goto
# wait 1-2minutes
Page crashed

# nothing else until SIGINT

Possible Resolutions

  • Add a timeout to evaluate so that it at least throw a timeout error
  • Evaluate could be stopped by page crash
  • Prevent page from consuming all the memory

Activity

  1. kachkaev commented on Aug 1, 2022

    @kachkaev
    Contributor

    I faced a similar issue recently. One of my page.evaluate calls was crashing a Chrome tab and my Node.js app paused indefinitely. Essentially, await page.evaluate(/* */) was pending forever.

    Here is my workaround:

    const evaluateTimeout = 30_000;
    
    const page = /* ... */;
    
    try {
      const result = await Promise.race([
        new Promise((resolve) => setTimeout(resolve, evaluateTimeout)),
        page.evaluate(async () => {
          return /* ... generateResult .. */;
        }),
      ]);
    
      if (!result) {
        throw new Error("page.evaluate must have crashed");
      }
    
      console.log(result);
    } finally {
      await page.close();
    }

    The readibility of this code is quite poor but it's not the only problem here. If the timeout promise needs to be lengthy, the whole Node.js process will get slow. It exits only after evaluateTimeout ms.

    It’d be great to be able to do this:

    page.evaluate(pageFunction, arg, { timeout: 30_000 })

    Alternatively, my problem could be solved if page.evaluate utilised page.on('crash', ...) internally.

  2. ivan-kleshnin commented on Jun 26, 2023

    @ivan-kleshnin

    evaluate does support a timeout option, but evaluateAll, unfortunately, does not: #23352

  3. sergtimosh commented on Aug 5, 2023

    @sergtimosh

    evaluate does support a timeout option, but evaluateAll, unfortunately, does not: #23352

    page.evaluate() doesn't support timeout too

  4. ivan-kleshnin commented on Aug 6, 2023

    @ivan-kleshnin

    page.evaluate() doesn't support timeout too

    👉 https://playwright.dev/docs/api/class-locator#locator-evaluate

    options Object (optional)
    timeout number (optional)# 👈
    Maximum time in milliseconds. Defaults to 0 - no timeout. The default value can be changed via actionTimeout option in the config, or by using the browserContext.setDefaultTimeout() or page.setDefaultTimeout() methods.

  5. sergtimosh commented on Aug 6, 2023

    @sergtimosh

    Ivan Kleshnin (@ivan-kleshnin), yes locator.evaluate has timeout but page.evaluate, hasn't
    Locator's interface evaluate function:

    * const tweets = page.locator('.tweet .retweets');
       * expect(await tweets.evaluate(node => node.innerText)).toBe('10 retweets');
       * ```
       *
       * @param pageFunction Function to be evaluated in the page context.
       * @param arg Optional argument to pass to `pageFunction`.
       * @param options
       */
      evaluate<R, Arg, E extends SVGElement | HTMLElement = SVGElement | HTMLElement>(pageFunction: PageFunctionOn<E, Arg, R>, arg: Arg, options?: {
        timeout?: number;
      }): Promise<R>;
    

    Same function for page interface:

     * const bodyHandle = await page.evaluate('document.body');
       * const html = await page.evaluate<string, HTMLElement>(([body, suffix]) => body.innerHTML + suffix, [bodyHandle, 'hello']);
       * await bodyHandle.dispose();
       * ```
       *
       * @param pageFunction Function to be evaluated in the page context.
       * @param arg Optional argument to pass to `pageFunction`.
       */
      evaluate<R, Arg>(pageFunction: PageFunction<Arg, R>, arg: Arg): Promise<R>;
    

    Also, if I try to use locator.evaluate() to evaluate a certain function that can fail with a timeout, the timeout still fails to stop evaluation in case of function failure and the test fails by global test timeout(120000 in my example)

    example

    const reports = await getActualReports();
                const testUserTestReports = reports.filter(r => r.Username === process.env.USER && r.Title.includes(testPrefix.trim()));
                for (const report of testUserTestReports) {
                    try {
                        const body = page.locator('body');
                        await body.evaluate(
                            async (el, report) => {
                                return await window.analyticsAutomations.deleteReport(report.ReportId);
                            },
                            report,
                            { timeout: 2000 }
                        );
                    } catch (e) {
                        console.log('Report cleanup failed. Maybe report was already deleted by the parallel process');
                    }
                }
    

    Test output

    Report cleanup failed. Maybe report was already deleted by the parallel process
      1) [chromium] › Analytics/Reports_List/reports_list_view.spec.ts:21:9 › Reports List View @analytics › Verify Reports List Fields for Newly Created Basic Report 
    
        Test timeout of 120000ms exceeded.
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions