playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[Feature] Make test execution speed (option slowMo) adjustable during test execution

Open klhex opened this issue 4 years ago • 15 comments

Currently, you can only artificially lower Playwright's execution speed for the entirety of a test execution, using the browserType.launch option "slowMo" (see https://playwright.dev/docs/api/class-browsertype#browsertypelaunchoptions):

image

However, in many debugging situations you're only interested in watching Playwright doing it slowly during specific parts of your test while the remainder of your test execution could very well be executed at normal (warp) speed in order to save your precious test development/debugging time (e.g. you're testing an app consisting of 6 pages and you only want to watch the last page being tested in slow motion).

Therefore, it would be very handy if you could slow Playwright temporarily down for specific parts of your test code only, for example via await page.slow(30) and then resetting it via await page.slow(0) or something comparable.

klhex avatar Mar 22 '21 13:03 klhex

That sounds great! Could be used with the Inspector if one has also set await page.paus() to then directly in the Inspector set the slow speed (without even having to use await page.slow(30). I.e. you could use that, but when you hit that location the Inspector has a field where you see the normal speed change to 30, but you can then also further change it if wanted to another speed direclty.

thernstig avatar Mar 22 '21 16:03 thernstig

Actually, API-wise, maybe something like page.debug( { slowMo: 60 } ) would be better in order to cater for future debug-related options.

klhex avatar Mar 22 '21 19:03 klhex

If so, then I assume page.pause() should be put into that as well. I'd maybe rather have page.debug.slowMo(60) then as I find it faster to type and easier for auto-completion. But I suppose all this is up to the Playwright peeps :)

thernstig avatar Mar 22 '21 20:03 thernstig

@thernstig Yes, indeed, in that case it would make sense to integrate page.pause() into page.debug(...) as well, good hint!

klhex avatar Mar 22 '21 20:03 klhex

Jep, for us, a slowMo per page + (optionally that you can set dynamically) would be what we are looking for.

LanderBeeuwsaert avatar Apr 12 '21 18:04 LanderBeeuwsaert

While this is collecting feedback, I'd like to discuss something related: it'd be amazing if I could have a set of configuration options which automatically get called when I call page.pause().

Use-case: I call pause so that I can experiment and figure out the best selector for an element. I will be poking around the page and the test fails because it hits the 30 second timeout. Right now, I either disable the timeout on the cli npx playwright test --timeout=0 or I set the test timeout in the test:

const { test, expect } = require('@playwright/test');

test('my test name here', async ({ page }) => {
  await page.goto('http://example.com');

  test.setTimeout(0);
  await page.pause();
});

Example implementation:

module.exports = {
   timeout: 30000,
   pauseOptions: {
      timeout: 0,
      slowMo: 200,
   },
};

tommyh avatar Jul 28 '21 17:07 tommyh

+1. the above would be awesome ^

raptoria avatar Sep 07 '21 15:09 raptoria

Do you have any updates deadlines for this?

ani-im avatar Mar 03 '22 12:03 ani-im

It seems like this affects typing into input fields, so a slowmo of 2000 makes typing extremely slow.

Overriding typing speed is not possible with this parameter:

*locator*.type(text, {
  delay: 10
})

Zoltanpetrik avatar Apr 12 '22 18:04 Zoltanpetrik

would be really helpful +1

alexn-s avatar Apr 27 '22 07:04 alexn-s

Definitely a must! +1 Currently getting trained in Playwright and my training lead me here.

ivanvison avatar May 13 '22 01:05 ivanvison

Since this issue is approaching is first birthday, I'm using this opportunity to ping @pavelfeldman, @arjunattam and @mxschmitt to highlight people's interest in this feature. :eyes: :pray:

klhex avatar May 13 '22 06:05 klhex

Here's a hack I wrote to get this functionality going without any changes required to the Playwright codebase.

For context, I wanted to "slow down" certain operations in the tests so I could record videos that didn't blaze through the operations so they could be used to show people for training purposes.

Waiting per-operation is easy in a test by just wrapping the request in a Promise that delays a certain amount of time, but I wanted to be able to enable/disable the delay with a small code change instead of having to change every line of the test to wait a certain amount of time.

Here's what I came up with (it's in TypeScript). It wraps the page.locator function with a version that replaces click and fill to be artificially slowed.

import { Locator, Page } from "@playwright/test";

// Return a "slow" page locator that waits before 'click' and 'fill' requests
function slowLocator(
  page: Page,
  waitInMs: number
): (...args: any[]) => Locator {
  // Grab original
  const l = page.locator.bind(page);

  // Return a new function that uses the original locator but remaps certain functions
  return (locatorArgs) => {
    const locator = l(locatorArgs);

    locator.click = async (args) => {
      await new Promise((r) => setTimeout(r, waitInMs));
      return l(locatorArgs).click(args);
    };

    locator.fill = async (args) => {
      await new Promise((r) => setTimeout(r, waitInMs));
      return l(locatorArgs).fill(args);
    };

    return locator;
  };
}

Now, I just put this at the start of my test:

test("sample test", async ({ page }) => {
  // Overwrite page.locator to give us back a version that waits 500ms before `click` and `fill`
  page.locator = slowLocator(page, 500);

  await page.locator('input).fill();
  await page.locator('button').click();
});

It should be pretty trivial to add delays to methods on the locator other than click and fill by following the pattern.

Now if you want the original/fast version of the locator, just comment out the one line that resets the page.locator and you get back your speedy tests. If you want to record a slowed-down video you can just uncomment the line (or add it in temporarily) to get a more relaxed version.

aardvarkk avatar May 25 '22 18:05 aardvarkk

page.locator = slowLocator(page, 500);

Can I get the same for expect assertions with tobevisible or tobeenabled combination

does anyone know if this feature has been added to the options when running test from command line?

luucamay avatar Sep 20 '22 16:09 luucamay

Here's a hack I wrote to get this functionality going without any changes required to the Playwright codebase.

For context, I wanted to "slow down" certain operations in the tests so I could record videos that didn't blaze through the operations so they could be used to show people for training purposes.

Waiting per-operation is easy in a test by just wrapping the request in a Promise that delays a certain amount of time, but I wanted to be able to enable/disable the delay with a small code change instead of having to change every line of the test to wait a certain amount of time.

Here's what I came up with (it's in TypeScript). It wraps the page.locator function with a version that replaces click and fill to be artificially slowed.

import { Locator, Page } from "@playwright/test";

// Return a "slow" page locator that waits before 'click' and 'fill' requests
function slowLocator(
  page: Page,
  waitInMs: number
): (...args: any[]) => Locator {
  // Grab original
  const l = page.locator.bind(page);

  // Return a new function that uses the original locator but remaps certain functions
  return (locatorArgs) => {
    const locator = l(locatorArgs);

    locator.click = async (args) => {
      await new Promise((r) => setTimeout(r, waitInMs));
      return l(locatorArgs).click(args);
    };

    locator.fill = async (args) => {
      await new Promise((r) => setTimeout(r, waitInMs));
      return l(locatorArgs).fill(args);
    };

    return locator;
  };
}

Now, I just put this at the start of my test:

test("sample test", async ({ page }) => {
  // Overwrite page.locator to give us back a version that waits 500ms before `click` and `fill`
  page.locator = slowLocator(page, 500);

  await page.locator('input).fill();
  await page.locator('button').click();
});

It should be pretty trivial to add delays to methods on the locator other than click and fill by following the pattern.

Now if you want the original/fast version of the locator, just comment out the one line that resets the page.locator and you get back your speedy tests. If you want to record a slowed-down video you can just uncomment the line (or add it in temporarily) to get a more relaxed version.

Do you know if there's a way to do this with the python bindings?

snackattas avatar Mar 23 '23 15:03 snackattas

+1 for this issue. I'm using getAttribute to get all the attributes of 90+ elements to verify if they are filled in or not, while the rest is just ordinary navigating and clicking. The getAttribute loop grinds to a halt with a slowmo of 1 second, while the rest could benefit from taking a second to be able to see what is going on. Being able to selectively pass a slowmo for a test step or certain sections would be useful rather than having to toggle it for a test.

ATARARHRB avatar May 01 '23 14:05 ATARARHRB

Here's a hack I wrote to get this functionality going without any changes required to the Playwright codebase.

For context, I wanted to "slow down" certain operations in the tests so I could record videos that didn't blaze through the operations so they could be used to show people for training purposes.

Waiting per-operation is easy in a test by just wrapping the request in a Promise that delays a certain amount of time, but I wanted to be able to enable/disable the delay with a small code change instead of having to change every line of the test to wait a certain amount of time.

Here's what I came up with (it's in TypeScript). It wraps the page.locator function with a version that replaces click and fill to be artificially slowed.

import { Locator, Page } from "@playwright/test";

// Return a "slow" page locator that waits before 'click' and 'fill' requests
function slowLocator(
  page: Page,
  waitInMs: number
): (...args: any[]) => Locator {
  // Grab original
  const l = page.locator.bind(page);

  // Return a new function that uses the original locator but remaps certain functions
  return (locatorArgs) => {
    const locator = l(locatorArgs);

    locator.click = async (args) => {
      await new Promise((r) => setTimeout(r, waitInMs));
      return l(locatorArgs).click(args);
    };

    locator.fill = async (args) => {
      await new Promise((r) => setTimeout(r, waitInMs));
      return l(locatorArgs).fill(args);
    };

    return locator;
  };
}

Now, I just put this at the start of my test:

test("sample test", async ({ page }) => {
  // Overwrite page.locator to give us back a version that waits 500ms before `click` and `fill`
  page.locator = slowLocator(page, 500);

  await page.locator('input).fill();
  await page.locator('button').click();
});

It should be pretty trivial to add delays to methods on the locator other than click and fill by following the pattern.

Now if you want the original/fast version of the locator, just comment out the one line that resets the page.locator and you get back your speedy tests. If you want to record a slowed-down video you can just uncomment the line (or add it in temporarily) to get a more relaxed version.

Is it possible to write similar wrapper for getByRole ?

MobileTester avatar Aug 04 '23 04:08 MobileTester

When running a test manually and watching the browser in headed mode, it would be very convenient to set the "speed" (slowMo) in the command line, rather than editing the test or creating a config file.

edelreal avatar Aug 30 '23 15:08 edelreal

does anyone know if this feature has been added to the options when running test from command line?

This is my workaround to automatically set slowMo when running in --headed mode (that is usually helpful):

export default defineConfig({
  use: {
    launchOptions: { 
      slowMo: isHeadedMode() ? 1000 : undefined
    }
  }
});

function isHeadedMode() {
  // important to use env var - for workers
  if (process.argv.includes('--headed')) process.env.HEADED_MODE = '1';
  return Boolean(process.env.HEADED_MODE);
}

vitalets avatar Sep 08 '23 05:09 vitalets

Has anyone solved this with Python?

jshet avatar Nov 10 '23 17:11 jshet

Bumping, this would be super useful. I currently have concurrent process happening. I have a MutationObserver that looks for DOM changes and then takes a screenshot via an exposed function. The screenshot however is too slow as the UI changes and updates the UI. I'd like a hook or setting that would be able to slow down all commands except the screenshot which is running in another thread.

basickarl avatar May 16 '24 14:05 basickarl