diff --git a/docs/src/api/class-clock.md b/docs/src/api/class-clock.md
index 31115db41b..bf8b9ac59a 100644
--- a/docs/src/api/class-clock.md
+++ b/docs/src/api/class-clock.md
@@ -6,11 +6,88 @@ Accurately simulating time-dependent behavior is essential for verifying the cor
Note that clock is installed for the entire [BrowserContext], so the time
in all the pages and iframes is controlled by the same clock.
-## async method: Clock.installFakeTimers
+## async method: Clock.fastForward
+* since: v1.45
+
+Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and
+reopening it later, after given time.
+
+**Usage**
+
+```js
+await page.clock.fastForward(1000);
+await page.clock.fastForward('30:00');
+```
+
+```python async
+await page.clock.fast_forward(1000)
+await page.clock.fast_forward("30:00")
+```
+
+```python sync
+page.clock.fast_forward(1000)
+page.clock.fast_forward("30:00")
+```
+
+```java
+page.clock().fastForward(1000);
+page.clock().fastForward("30:00");
+```
+
+```csharp
+await page.Clock.FastForwardAsync(1000);
+await page.Clock.FastForwardAsync("30:00");
+```
+
+### param: Clock.fastForward.ticks
+* since: v1.45
+- `ticks` <[int]|[string]>
+
+Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
+
+## async method: Clock.fastForwardTo
+* since: v1.45
+
+Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and
+reopening it at the specified time.
+
+**Usage**
+
+```js
+await page.clock.fastForwardTo(new Date('2020-02-02'));
+await page.clock.fastForwardTo('2020-02-02');
+```
+
+```python async
+await page.clock.fast_forward_to(datetime.datetime(2020, 2, 2))
+await page.clock.fast_forward_to("2020-02-02")
+```
+
+```python sync
+page.clock.fast_forward_to(datetime.datetime(2020, 2, 2))
+page.clock.fast_forward_to("2020-02-02")
+```
+
+```java
+page.clock().fastForwardTo(Instant.parse("2020-02-02"));
+page.clock().fastForwardTo("2020-02-02");
+```
+
+```csharp
+await page.Clock.FastForwardToAsync(DateTime.Parse("2020-02-02"));
+await page.Clock.FastForwardToAsync("2020-02-02");
+```
+
+### param: Clock.fastForwardTo.time
+* since: v1.45
+- `time` <[int]|[string]|[Date]>
+
+## async method: Clock.install
* since: v1.45
Install fake implementations for the following time-related functions:
+* `Date`
* `setTimeout`
* `clearTimeout`
* `setInterval`
@@ -21,41 +98,18 @@ Install fake implementations for the following time-related functions:
* `cancelIdleCallback`
* `performance`
-Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and control the behavior of time-dependent functions. See [`method: Clock.runFor`] and [`method: Clock.skipTime`] for more information.
+Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and control the behavior of time-dependent functions. See [`method: Clock.runFor`] and [`method: Clock.fastForward`] for more information.
-### param: Clock.installFakeTimers.time
+### option: Clock.install.time
* since: v1.45
-- `time` <[int]|[Date]>
-
-Install fake timers with the specified base time.
-
-### option: Clock.installFakeTimers.loopLimit
-* since: v1.45
-- `loopLimit` <[int]>
-
-The maximum number of timers that will be run in [`method: Clock.runAllTimers`]. Defaults to `1000`.
-
-## async method: Clock.runAllTimers
-* since: v1.45
-- returns: <[int]>
-
-Runs all pending timers until there are none remaining. If new timers are added while it is executing they will be run as well.
-Fake timers must be installed.
-Returns fake milliseconds since the unix epoch.
-
-**Details**
-
-This makes it easier to run asynchronous tests to completion without worrying about the number of timers they use, or the delays in those timers.
-It runs a maximum of [`option: loopLimit`] times after which it assumes there is an infinite loop of timers and throws an error.
+- `time` <[int]|[string]|[Date]>
+Time to initialize with, current system time by default.
## async method: Clock.runFor
* since: v1.45
-- returns: <[int]>
-Advance the clock, firing callbacks if necessary. Returns fake milliseconds since the unix epoch.
-Fake timers must be installed.
-Returns fake milliseconds since the unix epoch.
+Advance the clock, firing all the time-related callbacks.
**Usage**
@@ -66,12 +120,12 @@ await page.clock.runFor('30:00');
```python async
await page.clock.run_for(1000);
-await page.clock.run_for('30:00')
+await page.clock.run_for("30:00")
```
```python sync
page.clock.run_for(1000);
-page.clock.run_for('30:00')
+page.clock.run_for("30:00")
```
```java
@@ -84,84 +138,104 @@ await page.Clock.RunForAsync(1000);
await page.Clock.RunForAsync("30:00");
```
-### param: Clock.runFor.time
+### param: Clock.runFor.ticks
* since: v1.45
-- `time` <[int]|[string]>
+- `ticks` <[int]|[string]>
Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
-## async method: Clock.runToLastTimer
-* since: v1.45
-- returns: <[int]>
-
-This takes note of the last scheduled timer when it is run, and advances the clock to that time firing callbacks as necessary.
-If new timers are added while it is executing they will be run only if they would occur before this time.
-This is useful when you want to run a test to completion, but the test recursively sets timers that would cause runAll to trigger an infinite loop warning.
-Fake timers must be installed.
-Returns fake milliseconds since the unix epoch.
-
-
-## async method: Clock.runToNextTimer
-* since: v1.45
-- returns: <[int]>
-
-Advances the clock to the moment of the first scheduled timer, firing it.
-Fake timers must be installed.
-Returns fake milliseconds since the unix epoch.
-
-
-## async method: Clock.setTime
+## async method: Clock.pause
* since: v1.45
-Set the clock to the specified time.
+Pause timers. Once this method is called, no timers are fired unless [`method: Clock.runFor`], [`method: Clock.fastForward`], [`method: Clock.fastForwardTo`] or [`method: Clock.resume`] is called.
-When fake timers are installed, only fires timers at most once. This can be used to simulate the JS engine (such as a browser)
-being put to sleep and resumed later, skipping intermediary timers.
-
-### param: Clock.setTime.time
+## async method: Clock.resume
* since: v1.45
-- `time` <[int]|[Date]>
+Resumes timers. Once this method is called, time resumes flowing, timers are fired as usual.
-## async method: Clock.skipTime
+## async method: Clock.setFixedTime
* since: v1.45
-- returns: <[int]>
-Advance the clock by jumping forward in time, equivalent to running [`method: Clock.setTime`] with the new target time.
-
-When fake timers are installed, [`method: Clock.skipTime`] only fires due timers at most once, while [`method: Clock.runFor`] fires all the timers up to the current time.
-Returns fake milliseconds since the unix epoch.
+Makes `Date.now` and `new Date()` return fixed fake time at all times,
+keeps all the timers running.
**Usage**
```js
-await page.clock.skipTime(1000);
-await page.clock.skipTime('30:00');
+await page.clock.setFixedTime(Date.now());
+await page.clock.setFixedTime(new Date('2020-02-02'));
+await page.clock.setFixedTime('2020-02-02');
```
```python async
-await page.clock.skipTime(1000);
-await page.clock.skipTime('30:00')
+await page.clock.set_fixed_time(datetime.datetime.now())
+await page.clock.set_fixed_time(datetime.datetime(2020, 2, 2))
+await page.clock.set_fixed_time("2020-02-02")
```
```python sync
-page.clock.skipTime(1000);
-page.clock.skipTime('30:00')
+page.clock.set_fixed_time(datetime.datetime.now())
+page.clock.set_fixed_time(datetime.datetime(2020, 2, 2))
+page.clock.set_fixed_time("2020-02-02")
```
```java
-page.clock().skipTime(1000);
-page.clock().skipTime("30:00");
+page.clock().setFixedTime(Instant.now());
+page.clock().setFixedTime(Instant.parse("2020-02-02"));
+page.clock().setFixedTime("2020-02-02");
```
```csharp
-await page.Clock.SkipTimeAsync(1000);
-await page.Clock.SkipTimeAsync("30:00");
+await page.Clock.SetFixedTimeAsync(DateTime.Now);
+await page.Clock.SetFixedTimeAsync(new DateTime(2020, 2, 2));
+await page.Clock.SetFixedTimeAsync("2020-02-02");
```
-### param: Clock.skipTime.time
+### param: Clock.setFixedTime.time
* since: v1.45
-- `time` <[int]|[string]>
+- `time` <[int]|[string]|[Date]>
-Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
+Time to be set.
+
+## async method: Clock.setSystemTime
+* since: v1.45
+
+Sets current system time but does not trigger any timers, unlike [`method: Clock.fastForwardTo`].
+
+**Usage**
+
+```js
+await page.clock.setSystemTime(Date.now());
+await page.clock.setSystemTime(new Date('2020-02-02'));
+await page.clock.setSystemTime('2020-02-02');
+```
+
+```python async
+await page.clock.set_system_time(datetime.datetime.now())
+await page.clock.set_system_time(datetime.datetime(2020, 2, 2))
+await page.clock.set_system_time("2020-02-02")
+```
+
+```python sync
+page.clock.set_system_time(datetime.datetime.now())
+page.clock.set_system_time(datetime.datetime(2020, 2, 2))
+page.clock.set_system_time("2020-02-02")
+```
+
+```java
+page.clock().setSystemTime(Instant.now());
+page.clock().setSystemTime(Instant.parse("2020-02-02"));
+page.clock().setSystemTime("2020-02-02");
+```
+
+```csharp
+await page.Clock.SetSystemTimeAsync(DateTime.Now);
+await page.Clock.SetSystemTimeAsync(new DateTime(2020, 2, 2));
+await page.Clock.SetSystemTimeAsync("2020-02-02");
+```
+
+### param: Clock.setSystemTime.time
+* since: v1.45
+- `time` <[int]|[string]|[Date]>
diff --git a/docs/src/clock.md b/docs/src/clock.md
index 5299145916..82fba434ec 100644
--- a/docs/src/clock.md
+++ b/docs/src/clock.md
@@ -17,228 +17,324 @@ Accurately simulating time-dependent behavior is essential for verifying the cor
- `cancelAnimationFrame`
- `requestIdleCallback`
- `cancelIdleCallback`
+ - `performance`
-By default, the clock starts at the unix epoch (timestamp of 0). You can override it using the `now` option.
+## Test with predefined time
-```js
-await page.clock.setTime(new Date('2020-02-02'));
-await page.clock.installFakeTimers(new Date('2020-02-02'));
-```
-
-## Mock Date.now
-
-Most of the time, you only need to fake `Date.now` and no other time-related functions.
-That way the time flows naturally, but `Date.now` returns a fixed value.
+Often you only need to fake `Date.now` while keeping the timers going.
+That way the time flows naturally, but `Date.now` always returns a fixed value.
```html
```
```js
-await page.clock.setTime(new Date('2024-02-02T10:00:00'));
+await page.clock.setFixedTime(new Date('2024-02-02T10:00:00'));
await page.goto('http://localhost:3333');
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM');
-await page.clock.setTime(new Date('2024-02-02T10:30:00'));
+await page.clock.setFixedTime(new Date('2024-02-02T10:30:00'));
+// We know that the page has a timer that updates the time every second.
+await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM');
+```
+
+## Consistent time and timers
+
+Sometimes your timers depend on `Date.now` and are confused when the `Date.now` value does not change over time.
+In this case, you can install the clock and fast forward to the time of interest when testing.
+
+```html
+
+
+```
+
+```js
+// Initialize clock with some time before the test time and let the page load
+// naturally. `Date.now` will progress as the timers fire.
+await page.clock.install({ time: new Date('2024-02-02T08:00:00') });
+await page.goto('http://localhost:3333');
+
+// Take control over time flow.
+await page.clock.pause();
+// Pretend that the user closed the laptop lid and opened it again at 10am.
+await page.clock.fastForwardTo(new Date('2024-02-02T10:00:00'));
+
+// Assert the page state.
+await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM');
+
+// Close the laptop lid again and open it at 10:30am.
+await page.clock.fastForward('30:00');
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM');
```
```python async
-page.clock.set_time(datetime.datetime(2024, 2, 2, 10, 0, 0, tzinfo=datetime.timezone.pst))
-await page.goto('http://localhost:3333')
-locator = page.get_by_test_id('current-time')
-await expect(locator).to_have_text('2/2/2024, 10:00:00 AM')
+# Initialize clock with some time before the test time and let the page load
+# naturally. `Date.now` will progress as the timers fire.
+await page.clock.install(time=datetime.datetime(2024, 2, 2, 8, 0, 0))
+await page.goto("http://localhost:3333")
-page.clock.set_time(datetime.datetime(2024, 2, 2, 10, 30, 0, tzinfo=datetime.timezone.pst))
-await expect(locator).to_have_text('2/2/2024, 10:30:00 AM')
+# Take control over time flow.
+await page.clock.pause()
+# Pretend that the user closed the laptop lid and opened it again at 10am.
+await page.clock.fast_forward_to(datetime.datetime(2024, 2, 2, 10, 0, 0))
+
+# Assert the page state.
+await expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:00:00 AM")
+
+# Close the laptop lid again and open it at 10:30am.
+await page.clock.fast_forward("30:00")
+await expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM")
```
```python sync
-page.clock.set_time(datetime.datetime(2024, 2, 2, 10, 0, 0, tzinfo=datetime.timezone.pst))
-page.goto('http://localhost:3333')
-locator = page.get_by_test_id('current-time')
-expect(locator).to_have_text('2/2/2024, 10:00:00 AM')
+# Initialize clock with some time before the test time and let the page load
+# naturally. `Date.now` will progress as the timers fire.
+page.clock.install(time=datetime.datetime(2024, 2, 2, 8, 0, 0))
+page.goto("http://localhost:3333")
-page.clock.set_time(datetime.datetime(2024, 2, 2, 10, 30, 0, tzinfo=datetime.timezone.pst))
-expect(locator).to_have_text('2/2/2024, 10:30:00 AM')
+# Take control over time flow.
+page.clock.pause()
+# Pretend that the user closed the laptop lid and opened it again at 10am.
+page.clock.fast_forward_to(datetime.datetime(2024, 2, 2, 10, 0, 0))
+
+# Assert the page state.
+expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:00:00 AM")
+
+# Close the laptop lid again and open it at 10:30am.
+page.clock.fast_forward("30:00")
+expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM")
```
```java
-page.clock().setTime(Instant.parse("2024-02-02T10:00:00"));
+// Initialize clock with some time before the test time and let the page load
+// naturally. `Date.now` will progress as the timers fire.
+page.clock().install(new Clock.InstallOptions().setTime(Instant.parse("2024-02-02T08:00:00")));
page.navigate("http://localhost:3333");
Locator locator = page.getByTestId("current-time");
+
+// Take control over time flow.
+page.clock().pause();
+// Pretend that the user closed the laptop lid and opened it again at 10am.
+page.clock().fastForwardTo(Instant.parse("2024-02-02T10:00:00"));
+
+// Assert the page state.
assertThat(locator).hasText("2/2/2024, 10:00:00 AM");
-page.clock().setTime(Instant.parse("2024-02-02T10:30:00"));
+// Close the laptop lid again and open it at 10:30am.
+page.clock().fastForward("30:00");
assertThat(locator).hasText("2/2/2024, 10:30:00 AM");
```
```csharp
-// Initialize clock with a specific time, only fake Date.now.
-await page.Clock.SetTimeAsync(new DateTime(2024, 2, 2, 10, 0, 0, DateTimeKind.Pst));
-await page.GotoAsync("http://localhost:3333");
-var locator = page.GetByTestId("current-time");
-await Expect(locator).ToHaveTextAsync("2/2/2024, 10:00:00 AM");
+// Initialize clock with some time before the test time and let the page load naturally.
+// `Date.now` will progress as the timers fire.
+await Page.Clock.InstallAsync(new
+{
+ Time = new DateTime(2024, 2, 2, 8, 0, 0)
+});
+await Page.GotoAsync("http://localhost:3333");
-await page.Clock.SetTimeAsync(new DateTime(2024, 2, 2, 10, 30, 0, DateTimeKind.Pst));
-await Expect(locator).ToHaveTextAsync("2/2/2024, 10:30:00 AM");
+// Take control over time flow.
+await Page.Clock.PauseAsync();
+// Pretend that the user closed the laptop lid and opened it again at 10am.
+await Page.Clock.FastForwardToAsync(new DateTime(2024, 2, 2, 10, 0, 0));
+
+// Assert the page state.
+await Expect(Page.GetByTestId("current-time")).ToHaveText("2/2/2024, 10:00:00 AM");
+
+// Close the laptop lid again and open it at 10:30am.
+await Page.Clock.FastForwardAsync("30:00");
+await Expect(Page.GetByTestId("current-time")).ToHaveText("2/2/2024, 10:30:00 AM");
```
-## Mock Date.now consistent with the timers
+## Test inactivity monitoring
-Sometimes your timers depend on `Date.now` and are confused when the time stands still.
-In cases like this you need to ensure that `Date.now` and timers are consistent.
-You can achieve this by installing the fake timers.
-
-```html
-
-
-```
+Inactivity monitoring is a common feature in web applications that logs out users after a period of inactivity.
+Testing this feature can be tricky because you need to wait for a long time to see the effect.
+With the help of the clock, you can speed up time and test this feature quickly.
```js
-// Initialize clock with a specific time, take full control over time.
-await page.clock.installFakeTimers(new Date('2024-02-02T10:00:00'));
+// Initial time does not matter for the test, so we can pick current time.
+await page.clock.install();
await page.goto('http://localhost:3333');
-await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM');
+// Interact with the page
+await page.getByRole('button').click();
-// Fast forward time 30 minutes without firing intermediate timers, as if the user
-// closed and opened the lid of the laptop.
-await page.clock.skipTime('30:00');
-await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM');
+// Fast forward time 5 minutes as if the user did not do anything.
+// Fast forward is like closing the laptop lid and opening it after 5 minutes.
+// All the timers due will fire once immediately, as in the real browser.
+await page.clock.fastForward('5:00');
+
+// Check that the user was logged out automatically.
+await expect(page.getByText('You have been logged out due to inactivity.')).toBeVisible();
```
```python async
-# Initialize clock with a specific time, take full control over time.
-await page.clock.install_fake_timers(
- datetime.datetime(2024, 2, 2, 10, 0, 0, tzinfo=datetime.timezone.pst)
-)
-await page.goto('http://localhost:3333')
-locator = page.get_by_test_id('current-time')
-await expect(locator).to_have_text('2/2/2024, 10:00:00 AM')
+# Initial time does not matter for the test, so we can pick current time.
+await page.clock.install()
+await page.goto("http://localhost:3333")
+# Interact with the page
+await page.get_by_role("button").click()
-# Fast forward time 30 minutes without firing intermediate timers, as if the user
-# closed and opened the lid of the laptop.
-await page.clock.skip_time('30:00')
-await expect(locator).to_have_text('2/2/2024, 10:30:00 AM')
+# Fast forward time 5 minutes as if the user did not do anything.
+# Fast forward is like closing the laptop lid and opening it after 5 minutes.
+# All the timers due will fire once immediately, as in the real browser.
+await page.clock.fast_forward("5:00")
+
+# Check that the user was logged out automatically.
+await expect(page.getByText("You have been logged out due to inactivity.")).toBeVisible()
```
```python sync
-# Initialize clock with a specific time, take full control over time.
-page.clock.install_fake_timers(
- datetime.datetime(2024, 2, 2, 10, 0, 0, tzinfo=datetime.timezone.pst)
-)
-page.goto('http://localhost:3333')
-locator = page.get_by_test_id('current-time')
-expect(locator).to_have_text('2/2/2024, 10:00:00 AM')
+# Initial time does not matter for the test, so we can pick current time.
+page.clock.install()
+page.goto("http://localhost:3333")
+# Interact with the page
+page.get_by_role("button").click()
-# Fast forward time 30 minutes without firing intermediate timers, as if the user
-# closed and opened the lid of the laptop.
-page.clock.skip_time('30:00')
-expect(locator).to_have_text('2/2/2024, 10:30:00 AM')
+# Fast forward time 5 minutes as if the user did not do anything.
+# Fast forward is like closing the laptop lid and opening it after 5 minutes.
+# All the timers due will fire once immediately, as in the real browser.
+page.clock.fast_forward("5:00")
+
+# Check that the user was logged out automatically.
+expect(page.get_by_text("You have been logged out due to inactivity.")).to_be_visible()
```
```java
-// Initialize clock with a specific time, take full control over time.
-page.clock().installFakeTimers(Instant.parse("2024-02-02T10:00:00"));
+// Initial time does not matter for the test, so we can pick current time.
+page.clock().install();
page.navigate("http://localhost:3333");
-Locator locator = page.getByTestId("current-time");
-assertThat(locator).hasText("2/2/2024, 10:00:00 AM")
+Locator locator = page.getByRole("button");
-// Fast forward time 30 minutes without firing intermediate timers, as if the user
-// closed and opened the lid of the laptop.
-page.clock().skipTime("30:00");
-assertThat(locator).hasText("2/2/2024, 10:30:00 AM");
+// Interact with the page
+locator.click();
+
+// Fast forward time 5 minutes as if the user did not do anything.
+// Fast forward is like closing the laptop lid and opening it after 5 minutes.
+// All the timers due will fire once immediately, as in the real browser.
+page.clock().fastForward("5:00");
+
+// Check that the user was logged out automatically.
+assertThat(page.getByText("You have been logged out due to inactivity.")).isVisible();
```
```csharp
-// Initialize clock with a specific time, take full control over time.
-await page.Clock.InstallFakeTimersAsync(
- new DateTime(2024, 2, 2, 10, 0, 0, DateTimeKind.Pst)
-);
+// Initial time does not matter for the test, so we can pick current time.
+await Page.Clock.InstallAsync();
await page.GotoAsync("http://localhost:3333");
-var locator = page.GetByTestId("current-time");
-await Expect(locator).ToHaveTextAsync("2/2/2024, 10:00:00 AM");
-// Fast forward time 30 minutes without firing intermediate timers, as if the user
-// closed and opened the lid of the laptop.
-await page.Clock.SkipTimeAsync("30:00");
-await Expect(locator).ToHaveTextAsync("2/2/2024, 10:30:00 AM");
+// Interact with the page
+await page.GetByRole("button").ClickAsync();
+
+// Fast forward time 5 minutes as if the user did not do anything.
+// Fast forward is like closing the laptop lid and opening it after 5 minutes.
+// All the timers due will fire once immediately, as in the real browser.
+await Page.Clock.FastForwardAsync("5:00");
+
+// Check that the user was logged out automatically.
+await Expect(Page.GetByText("You have been logged out due to inactivity.")).ToBeVisibleAsync();
```
-## Tick through time manually
+## Tick through time manually, firing all the timers consistently
-In rare cases, you may want to tick through time manually, firing all timers and animation frames in the process to achieve a fine-grained
-control over the passage of time.
+In rare cases, you may want to tick through time manually, firing all timers and
+animation frames in the process to achieve a fine-grained control over the passage of time.
```html
```
```js
-// Initialize clock with a specific time, take full control over time.
-await page.clock.installFakeTimers(new Date('2024-02-02T10:00:00'));
+// Initialize clock with a specific time, let the page load naturally.
+await page.clock.install({ time: new Date('2024-02-02T08:00:00') });
await page.goto('http://localhost:3333');
+// Pause the time flow, stop the timers, you now have manual control
+// over the page time.
+await page.clock.pause();
+await page.clock.fastForwardTo(new Date('2024-02-02T10:00:00'));
+await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM');
+
// Tick through time manually, firing all timers in the process.
// In this case, time will be updated in the screen 2 times.
await page.clock.runFor(2000);
-await expect(locator).to_have_text('2/2/2024, 10:00:02 AM');
+await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:02 AM');
```
```python async
-# Initialize clock with a specific time, take full control over time.
-await page.clock.install_fake_timers(
- datetime.datetime(2024, 2, 2, 10, 0, 0, tzinfo=datetime.timezone.pst),
+# Initialize clock with a specific time, let the page load naturally.
+await page.clock.install(time=
+ datetime.datetime(2024, 2, 2, 8, 0, 0, tzinfo=datetime.timezone.pst),
)
-await page.goto('http://localhost:3333')
-locator = page.get_by_test_id('current-time')
+await page.goto("http://localhost:3333")
+locator = page.get_by_test_id("current-time")
+
+# Pause the time flow, stop the timers, you now have manual control
+# over the page time.
+await page.clock.pause()
+await page.clock.fast_forward_to(datetime.datetime(2024, 2, 2, 10, 0, 0))
+await expect(locator).to_have_text("2/2/2024, 10:00:00 AM")
# Tick through time manually, firing all timers in the process.
# In this case, time will be updated in the screen 2 times.
await page.clock.run_for(2000)
-await expect(locator).to_have_text('2/2/2024, 10:00:02 AM')
+await expect(locator).to_have_text("2/2/2024, 10:00:02 AM")
```
```python sync
-# Initialize clock with a specific time, take full control over time.
-page.clock.install_fake_timers(
- datetime.datetime(2024, 2, 2, 10, 0, 0, tzinfo=datetime.timezone.pst),
+# Initialize clock with a specific time, let the page load naturally.
+page.clock.install(
+ time=datetime.datetime(2024, 2, 2, 8, 0, 0, tzinfo=datetime.timezone.pst),
)
-page.goto('http://localhost:3333')
-locator = page.get_by_test_id('current-time')
+page.goto("http://localhost:3333")
+locator = page.get_by_test_id("current-time")
+
+# Pause the time flow, stop the timers, you now have manual control
+# over the page time.
+page.clock.pause()
+page.clock.fast_forward_to(datetime.datetime(2024, 2, 2, 10, 0, 0))
+expect(locator).to_have_text("2/2/2024, 10:00:00 AM")
# Tick through time manually, firing all timers in the process.
# In this case, time will be updated in the screen 2 times.
page.clock.run_for(2000)
-expect(locator).to_have_text('2/2/2024, 10:00:02 AM')
+expect(locator).to_have_text("2/2/2024, 10:00:02 AM")
```
```java
-// Initialize clock with a specific time, take full control over time.
-page.clock().installFakeTimers(Instant.parse("2024-02-02T10:00:00"));
+// Initialize clock with a specific time, let the page load naturally.
+page.clock().install(new Clock.InstallOptions()
+ .setTime(Instant.parse("2024-02-02T08:00:00")));
page.navigate("http://localhost:3333");
Locator locator = page.getByTestId("current-time");
+// Pause the time flow, stop the timers, you now have manual control
+// over the page time.
+page.clock().pause();
+page.clock().fastForwardTo(Instant.parse("2024-02-02T10:00:00"));
+assertThat(locator).hasText("2/2/2024, 10:00:00 AM");
+
// Tick through time manually, firing all timers in the process.
// In this case, time will be updated in the screen 2 times.
page.clock().runFor(2000);
@@ -246,15 +342,22 @@ assertThat(locator).hasText("2/2/2024, 10:00:02 AM");
```
```csharp
-// Initialize clock with a specific time, take full control over time.
-await page.Clock.InstallFakeTimersAsync(
- new DateTime(2024, 2, 2, 10, 0, 0, DateTimeKind.Pst)
-);
+// Initialize clock with a specific time, let the page load naturally.
+await Page.Clock.InstallAsync(new
+{
+ Time = new DateTime(2024, 2, 2, 8, 0, 0, DateTimeKind.Pst)
+});
await page.GotoAsync("http://localhost:3333");
var locator = page.GetByTestId("current-time");
+// Pause the time flow, stop the timers, you now have manual control
+// over the page time.
+await Page.Clock.PauseAsync();
+await Page.Clock.FastForwardToAsync(new DateTime(2024, 2, 2, 10, 0, 0));
+await Expect(locator).ToHaveTextAsync("2/2/2024, 10:00:00 AM");
+
// Tick through time manually, firing all timers in the process.
// In this case, time will be updated in the screen 2 times.
-await page.Clock.RunForAsync(2000);
+await Page.Clock.RunForAsync(2000);
await Expect(locator).ToHaveTextAsync("2/2/2024, 10:00:02 AM");
```
diff --git a/packages/playwright-core/src/client/browser.ts b/packages/playwright-core/src/client/browser.ts
index ed903b674e..c1a9b0259d 100644
--- a/packages/playwright-core/src/client/browser.ts
+++ b/packages/playwright-core/src/client/browser.ts
@@ -85,8 +85,12 @@ export class Browser extends ChannelOwner implements ap
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
const context = BrowserContext.from(response.context);
await this._browserType._didCreateContext(context, contextOptions, this._options, options.logger || this._logger);
- if (!forReuse && !!process.env.PW_FREEZE_TIME)
- await this._wrapApiCall(async () => { await context.clock.installFakeTimers(new Date(0)); }, true);
+ if (!forReuse && !!process.env.PW_FREEZE_TIME) {
+ await this._wrapApiCall(async () => {
+ await context.clock.install({ time: 0 });
+ await context.clock.pause();
+ }, true);
+ }
return context;
}
diff --git a/packages/playwright-core/src/client/clock.ts b/packages/playwright-core/src/client/clock.ts
index 792e36a135..f4d2133b40 100644
--- a/packages/playwright-core/src/client/clock.ts
+++ b/packages/playwright-core/src/client/clock.ts
@@ -24,44 +24,50 @@ export class Clock implements api.Clock {
this._browserContext = browserContext;
}
- async installFakeTimers(time: number | Date, options: { loopLimit?: number } = {}) {
- const timeMs = time instanceof Date ? time.getTime() : time;
- await this._browserContext._channel.clockInstallFakeTimers({ time: timeMs, loopLimit: options.loopLimit });
+ async install(options: { time?: number | string | Date } = { }) {
+ await this._browserContext._channel.clockInstall(options.time !== undefined ? parseTime(options.time) : {});
}
- async runAllTimers(): Promise {
- const result = await this._browserContext._channel.clockRunAllTimers();
- return result.fakeTime;
+ async fastForward(ticks: number | string) {
+ await this._browserContext._channel.clockFastForward(parseTicks(ticks));
}
- async runFor(time: number | string): Promise {
- const result = await this._browserContext._channel.clockRunFor({
- timeNumber: typeof time === 'number' ? time : undefined,
- timeString: typeof time === 'string' ? time : undefined
- });
- return result.fakeTime;
+ async fastForwardTo(time: number | string | Date) {
+ await this._browserContext._channel.clockFastForwardTo(parseTime(time));
}
- async runToLastTimer(): Promise {
- const result = await this._browserContext._channel.clockRunToLastTimer();
- return result.fakeTime;
+ async pause() {
+ await this._browserContext._channel.clockPause({});
}
- async runToNextTimer(): Promise {
- const result = await this._browserContext._channel.clockRunToNextTimer();
- return result.fakeTime;
+ async resume() {
+ await this._browserContext._channel.clockResume({});
}
- async setTime(time: number | Date) {
- const timeMs = time instanceof Date ? time.getTime() : time;
- await this._browserContext._channel.clockSetTime({ time: timeMs });
+ async runFor(ticks: number | string) {
+ await this._browserContext._channel.clockRunFor(parseTicks(ticks));
}
- async skipTime(time: number | string) {
- const result = await this._browserContext._channel.clockSkipTime({
- timeNumber: typeof time === 'number' ? time : undefined,
- timeString: typeof time === 'string' ? time : undefined
- });
- return result.fakeTime;
+ async setFixedTime(time: string | number | Date) {
+ await this._browserContext._channel.clockSetFixedTime(parseTime(time));
+ }
+
+ async setSystemTime(time: string | number | Date) {
+ await this._browserContext._channel.clockSetSystemTime(parseTime(time));
}
}
+
+function parseTime(time: string | number | Date): { timeNumber?: number, timeString?: string } {
+ if (typeof time === 'number')
+ return { timeNumber: time };
+ if (typeof time === 'string')
+ return { timeString: time };
+ return { timeNumber: time.getTime() };
+}
+
+function parseTicks(ticks: string | number): { ticksNumber?: number, ticksString?: string } {
+ return {
+ ticksNumber: typeof ticks === 'number' ? ticks : undefined,
+ ticksString: typeof ticks === 'string' ? ticks : undefined
+ };
+}
diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts
index 0a60a92ebb..169b7574df 100644
--- a/packages/playwright-core/src/protocol/validator.ts
+++ b/packages/playwright-core/src/protocol/validator.ts
@@ -963,41 +963,40 @@ scheme.BrowserContextUpdateSubscriptionParams = tObject({
enabled: tBoolean,
});
scheme.BrowserContextUpdateSubscriptionResult = tOptional(tObject({}));
-scheme.BrowserContextClockInstallFakeTimersParams = tObject({
- time: tNumber,
- loopLimit: tOptional(tNumber),
+scheme.BrowserContextClockFastForwardParams = tObject({
+ ticksNumber: tOptional(tNumber),
+ ticksString: tOptional(tString),
});
-scheme.BrowserContextClockInstallFakeTimersResult = tOptional(tObject({}));
-scheme.BrowserContextClockRunAllTimersParams = tOptional(tObject({}));
-scheme.BrowserContextClockRunAllTimersResult = tObject({
- fakeTime: tNumber,
+scheme.BrowserContextClockFastForwardResult = tOptional(tObject({}));
+scheme.BrowserContextClockFastForwardToParams = tObject({
+ timeNumber: tOptional(tNumber),
+ timeString: tOptional(tString),
});
+scheme.BrowserContextClockFastForwardToResult = tOptional(tObject({}));
+scheme.BrowserContextClockInstallParams = tObject({
+ timeNumber: tOptional(tNumber),
+ timeString: tOptional(tString),
+});
+scheme.BrowserContextClockInstallResult = tOptional(tObject({}));
+scheme.BrowserContextClockPauseParams = tOptional(tObject({}));
+scheme.BrowserContextClockPauseResult = tOptional(tObject({}));
+scheme.BrowserContextClockResumeParams = tOptional(tObject({}));
+scheme.BrowserContextClockResumeResult = tOptional(tObject({}));
scheme.BrowserContextClockRunForParams = tObject({
+ ticksNumber: tOptional(tNumber),
+ ticksString: tOptional(tString),
+});
+scheme.BrowserContextClockRunForResult = tOptional(tObject({}));
+scheme.BrowserContextClockSetFixedTimeParams = tObject({
timeNumber: tOptional(tNumber),
timeString: tOptional(tString),
});
-scheme.BrowserContextClockRunForResult = tObject({
- fakeTime: tNumber,
-});
-scheme.BrowserContextClockRunToLastTimerParams = tOptional(tObject({}));
-scheme.BrowserContextClockRunToLastTimerResult = tObject({
- fakeTime: tNumber,
-});
-scheme.BrowserContextClockRunToNextTimerParams = tOptional(tObject({}));
-scheme.BrowserContextClockRunToNextTimerResult = tObject({
- fakeTime: tNumber,
-});
-scheme.BrowserContextClockSetTimeParams = tObject({
- time: tNumber,
-});
-scheme.BrowserContextClockSetTimeResult = tOptional(tObject({}));
-scheme.BrowserContextClockSkipTimeParams = tObject({
+scheme.BrowserContextClockSetFixedTimeResult = tOptional(tObject({}));
+scheme.BrowserContextClockSetSystemTimeParams = tObject({
timeNumber: tOptional(tNumber),
timeString: tOptional(tString),
});
-scheme.BrowserContextClockSkipTimeResult = tObject({
- fakeTime: tNumber,
-});
+scheme.BrowserContextClockSetSystemTimeResult = tOptional(tObject({}));
scheme.PageInitializer = tObject({
mainFrame: tChannel(['Frame']),
viewportSize: tOptional(tObject({
diff --git a/packages/playwright-core/src/server/clock.ts b/packages/playwright-core/src/server/clock.ts
index dd7f194a1d..3cbb0b1b0a 100644
--- a/packages/playwright-core/src/server/clock.ts
+++ b/packages/playwright-core/src/server/clock.ts
@@ -16,81 +16,74 @@
import type { BrowserContext } from './browserContext';
import * as clockSource from '../generated/clockSource';
+import { isJavaScriptErrorInEvaluate } from './javascript';
export class Clock {
private _browserContext: BrowserContext;
- private _scriptInjected = false;
- private _clockInstalled = false;
- private _now = 0;
+ private _scriptInstalled = false;
constructor(browserContext: BrowserContext) {
this._browserContext = browserContext;
}
- async installFakeTimers(time: number, loopLimit: number | undefined) {
- await this._injectScriptIfNeeded();
- await this._addAndEvaluate(`(() => {
- globalThis.__pwClock.clock?.uninstall();
- globalThis.__pwClock.clock = globalThis.__pwClock.install(${JSON.stringify({ now: time, loopLimit })});
- })();`);
- this._now = time;
- this._clockInstalled = true;
+ async fastForward(ticks: number | string) {
+ await this._installIfNeeded();
+ const ticksMillis = parseTicks(ticks);
+ await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('fastForward', ${Date.now()}, ${ticksMillis})`);
+ await this._evaluateInFrames(`globalThis.__pwClock.controller.fastForward(${ticksMillis})`);
}
- async runToNextTimer(): Promise {
- this._assertInstalled();
- this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.next()`);
- return this._now;
+ async fastForwardTo(ticks: number | string) {
+ await this._installIfNeeded();
+ const timeMillis = parseTime(ticks);
+ await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('fastForwardTo', ${Date.now()}, ${timeMillis})`);
+ await this._evaluateInFrames(`globalThis.__pwClock.controller.fastForwardTo(${timeMillis})`);
}
- async runAllTimers(): Promise {
- this._assertInstalled();
- this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.runAll()`);
- return this._now;
+ async install(time: number | string | undefined) {
+ await this._installIfNeeded();
+ const timeMillis = time !== undefined ? parseTime(time) : Date.now();
+ await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('install', ${Date.now()}, ${timeMillis})`);
+ await this._evaluateInFrames(`globalThis.__pwClock.controller.install(${timeMillis})`);
}
- async runToLastTimer(): Promise {
- this._assertInstalled();
- this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.runToLast()`);
- return this._now;
+ async pause() {
+ await this._installIfNeeded();
+ await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('pause', ${Date.now()})`);
+ await this._evaluateInFrames(`globalThis.__pwClock.controller.pause()`);
}
- async setTime(time: number) {
- if (this._clockInstalled) {
- const jump = time - this._now;
- if (jump < 0)
- throw new Error('Unable to set time into the past when fake timers are installed');
- await this._addAndEvaluate(`globalThis.__pwClock.clock.jump(${jump})`);
- this._now = time;
- return this._now;
- }
-
- await this._injectScriptIfNeeded();
- await this._addAndEvaluate(`(() => {
- globalThis.__pwClock.clock?.uninstall();
- globalThis.__pwClock.clock = globalThis.__pwClock.install(${JSON.stringify({ now: time, toFake: ['Date'] })});
- })();`);
- this._now = time;
- return this._now;
+ async resume() {
+ await this._installIfNeeded();
+ await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('resume', ${Date.now()})`);
+ await this._evaluateInFrames(`globalThis.__pwClock.controller.resume()`);
}
- async skipTime(time: number | string) {
- const delta = parseTime(time);
- await this.setTime(this._now + delta);
- return this._now;
+ async setFixedTime(time: string | number) {
+ await this._installIfNeeded();
+ const timeMillis = parseTime(time);
+ await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('setFixedTime', ${Date.now()}, ${timeMillis})`);
+ await this._evaluateInFrames(`globalThis.__pwClock.controller.setFixedTime(${timeMillis})`);
}
- async runFor(time: number | string): Promise {
- this._assertInstalled();
- await this._browserContext.addInitScript(`globalThis.__pwClock.clock.recordTick(${JSON.stringify(time)})`);
- this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.tick(${JSON.stringify(time)})`);
- return this._now;
+ async setSystemTime(time: string | number) {
+ await this._installIfNeeded();
+ const timeMillis = parseTime(time);
+ await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('setSystemTime', ${Date.now()}, ${timeMillis})`);
+ await this._evaluateInFrames(`globalThis.__pwClock.controller.setSystemTime(${timeMillis})`);
}
- private async _injectScriptIfNeeded() {
- if (this._scriptInjected)
+ async runFor(ticks: number | string) {
+ await this._installIfNeeded();
+ const ticksMillis = parseTicks(ticks);
+ await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('runFor', ${Date.now()}, ${ticksMillis})`);
+ await this._evaluateInFrames(`globalThis.__pwClock.controller.runFor(${ticksMillis})`);
+ }
+
+ private async _installIfNeeded() {
+ if (this._scriptInstalled)
return;
- this._scriptInjected = true;
+ this._scriptInstalled = true;
const script = `(() => {
const module = {};
${clockSource.source}
@@ -106,37 +99,56 @@ export class Clock {
private async _evaluateInFrames(script: string) {
const frames = this._browserContext.pages().map(page => page.frames()).flat();
- const results = await Promise.all(frames.map(frame => frame.evaluateExpression(script)));
+ const results = await Promise.all(frames.map(async frame => {
+ try {
+ await frame.nonStallingEvaluateInExistingContext(script, false, 'main');
+ } catch (e) {
+ if (isJavaScriptErrorInEvaluate(e))
+ throw e;
+ }
+ }));
return results[0];
}
-
- private _assertInstalled() {
- if (!this._clockInstalled)
- throw new Error('Clock is not installed');
- }
}
-// Taken from sinonjs/fake-timerss-src.
-function parseTime(time: string | number): number {
- if (typeof time === 'number')
- return time;
- if (!time)
+/**
+ * Parse strings like '01:10:00' (meaning 1 hour, 10 minutes, 0 seconds) into
+ * number of milliseconds. This is used to support human-readable strings passed
+ * to clock.tick()
+ */
+function parseTicks(value: number | string): number {
+ if (typeof value === 'number')
+ return value;
+ if (!value)
return 0;
+ const str = value;
- const strings = time.split(':');
+ const strings = str.split(':');
const l = strings.length;
let i = l;
let ms = 0;
let parsed;
- if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(time))
- throw new Error(`tick only understands numbers, 'm:s' and 'h:m:s'. Each part must be two digits`);
+ if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) {
+ throw new Error(
+ `Clock only understands numbers, 'mm:ss' and 'hh:mm:ss'`,
+ );
+ }
while (i--) {
parsed = parseInt(strings[i], 10);
if (parsed >= 60)
- throw new Error(`Invalid time ${time}`);
+ throw new Error(`Invalid time ${str}`);
ms += parsed * Math.pow(60, l - i - 1);
}
+
return ms * 1000;
}
+
+function parseTime(epoch: string | number | undefined): number {
+ if (!epoch)
+ return 0;
+ if (typeof epoch === 'number')
+ return epoch;
+ return new Date(epoch).getTime();
+}
diff --git a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts
index dd1f61f57b..86b9a5576c 100644
--- a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts
+++ b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts
@@ -312,32 +312,36 @@ export class BrowserContextDispatcher extends Dispatcher {
- await this._context.clock.installFakeTimers(params.time, params.loopLimit);
+ async clockFastForward(params: channels.BrowserContextClockFastForwardParams, metadata?: CallMetadata | undefined): Promise {
+ await this._context.clock.fastForward(params.ticksString ?? params.ticksNumber ?? 0);
}
- async clockRunAllTimers(params: channels.BrowserContextClockRunAllTimersParams, metadata?: CallMetadata | undefined): Promise {
- return { fakeTime: await this._context.clock.runAllTimers() };
+ async clockFastForwardTo(params: channels.BrowserContextClockFastForwardToParams, metadata?: CallMetadata | undefined): Promise {
+ await this._context.clock.fastForwardTo(params.timeString ?? params.timeNumber ?? 0);
}
- async clockRunToLastTimer(params: channels.BrowserContextClockRunToLastTimerParams, metadata?: CallMetadata | undefined): Promise {
- return { fakeTime: await this._context.clock.runToLastTimer() };
+ async clockInstall(params: channels.BrowserContextClockInstallParams, metadata?: CallMetadata | undefined): Promise {
+ await this._context.clock.install(params.timeString ?? params.timeNumber ?? undefined);
}
- async clockRunToNextTimer(params: channels.BrowserContextClockRunToNextTimerParams, metadata?: CallMetadata | undefined): Promise {
- return { fakeTime: await this._context.clock.runToNextTimer() };
+ async clockPause(params: channels.BrowserContextClockPauseParams, metadata?: CallMetadata | undefined): Promise {
+ await this._context.clock.pause();
}
- async clockSetTime(params: channels.BrowserContextClockSetTimeParams, metadata?: CallMetadata | undefined): Promise {
- await this._context.clock.setTime(params.time);
- }
-
- async clockSkipTime(params: channels.BrowserContextClockSkipTimeParams, metadata?: CallMetadata | undefined): Promise {
- return { fakeTime: await this._context.clock.skipTime(params.timeString || params.timeNumber || 0) };
+ async clockResume(params: channels.BrowserContextClockResumeParams, metadata?: CallMetadata | undefined): Promise {
+ await this._context.clock.resume();
}
async clockRunFor(params: channels.BrowserContextClockRunForParams, metadata?: CallMetadata | undefined): Promise {
- return { fakeTime: await this._context.clock.runFor(params.timeString || params.timeNumber || 0) };
+ await this._context.clock.runFor(params.ticksString ?? params.ticksNumber ?? 0);
+ }
+
+ async clockSetFixedTime(params: channels.BrowserContextClockSetFixedTimeParams, metadata?: CallMetadata | undefined): Promise {
+ await this._context.clock.setFixedTime(params.timeString ?? params.timeNumber ?? 0);
+ }
+
+ async clockSetSystemTime(params: channels.BrowserContextClockSetSystemTimeParams, metadata?: CallMetadata | undefined): Promise {
+ await this._context.clock.setSystemTime(params.timeString ?? params.timeNumber ?? 0);
}
async updateSubscription(params: channels.BrowserContextUpdateSubscriptionParams): Promise {
diff --git a/packages/playwright-core/src/server/injected/clock.ts b/packages/playwright-core/src/server/injected/clock.ts
index 508d7a59e5..5f6d4cf613 100644
--- a/packages/playwright-core/src/server/injected/clock.ts
+++ b/packages/playwright-core/src/server/injected/clock.ts
@@ -26,7 +26,6 @@ export type ClockMethods = {
export type ClockConfig = {
now?: number | Date;
- loopLimit?: number;
};
export type InstallConfig = ClockConfig & {
@@ -53,26 +52,39 @@ type Timer = {
};
interface Embedder {
- postTask(task: () => void): void;
- postTaskPeriodically(task: () => void, delay: number): () => void;
+ dateNow(): number;
+ performanceNow(): DOMHighResTimeStamp;
+ setTimeout(task: () => void, timeout?: number): () => void;
+ setInterval(task: () => void, delay: number): () => void;
}
+type Time = {
+ // ms since Epoch
+ time: number;
+ // Ticks since the session began (ala performance.now)
+ ticks: number;
+ // Whether fixed time was set.
+ isFixedTime: boolean;
+ // Origin time since Epoch when session started.
+ origin: number;
+};
+
+type LogEntryType = 'fastForward' | 'fastForwardTo' | 'install' | 'pause' | 'resume' | 'runFor' | 'setFixedTime' | 'setSystemTime';
+
export class ClockController {
- readonly timeOrigin: number;
- private _now: { time: number, ticks: number, timeFrozen: boolean };
- private _loopLimit: number;
+ readonly _now: Time;
private _duringTick = false;
private _timers = new Map();
private _uniqueTimerId = idCounterStart;
private _embedder: Embedder;
readonly disposables: (() => void)[] = [];
+ private _log: { type: LogEntryType, time: number, param?: number }[] = [];
+ private _realTime: { startTicks: number, lastSyncTicks: number } | undefined;
+ private _currentRealTimeTimer: { callAt: number, dispose: () => void } | undefined;
- constructor(embedder: Embedder, startDate: Date | number | undefined, loopLimit: number = 1000) {
- const start = Math.floor(getEpoch(startDate));
- this.timeOrigin = start;
- this._now = { time: start, ticks: 0, timeFrozen: false };
+ constructor(embedder: Embedder) {
+ this._now = { time: 0, isFixedTime: false, ticks: 0, origin: -1 };
this._embedder = embedder;
- this._loopLimit = loopLimit;
}
uninstall() {
@@ -81,109 +93,147 @@ export class ClockController {
}
now(): number {
+ this._replayLogOnce();
return this._now.time;
}
- setTime(now: Date | number, options: { freeze?: boolean } = {}) {
- this._now.time = getEpoch(now);
- this._now.timeFrozen = !!options.freeze;
+ install(time: number) {
+ this._replayLogOnce();
+ this._innerSetTime(time);
+ }
+
+ setSystemTime(time: number) {
+ this._replayLogOnce();
+ this._innerSetTime(time);
+ }
+
+ setFixedTime(time: number) {
+ this._replayLogOnce();
+ this._innerSetFixedTime(time);
}
performanceNow(): DOMHighResTimeStamp {
+ this._replayLogOnce();
return this._now.ticks;
}
+ private _innerSetTime(time: number) {
+ this._now.time = time;
+ this._now.isFixedTime = false;
+ if (this._now.origin < 0)
+ this._now.origin = this._now.time;
+ }
+
+ private _innerSetFixedTime(time: number) {
+ this._innerSetTime(time);
+ this._now.isFixedTime = true;
+ }
+
private _advanceNow(toTicks: number) {
- if (!this._now.timeFrozen)
+ if (!this._now.isFixedTime)
this._now.time += toTicks - this._now.ticks;
this._now.ticks = toTicks;
}
- private async _doTick(msFloat: number): Promise {
- if (msFloat < 0)
- throw new TypeError('Negative ticks are not supported');
+ async log(type: LogEntryType, time: number, param?: number) {
+ this._log.push({ type, time, param });
+ }
+
+ async runFor(ticks: number) {
+ this._replayLogOnce();
+ if (ticks < 0)
+ throw new TypeError('Negative ticks are not supported');
+ await this._runTo(this._now.ticks + ticks);
+ }
+
+ private async _runTo(tickTo: number) {
+ if (this._now.ticks > tickTo)
+ return;
- const ms = Math.floor(msFloat);
- const tickTo = this._now.ticks + ms;
- let tickFrom = this._now.ticks;
- let previous = this._now.ticks;
let firstException: Error | undefined;
- let timer = this._firstTimerInRange(tickFrom, tickTo);
- while (timer && tickFrom <= tickTo) {
- tickFrom = timer.callAt;
- const error = await this._callTimer(timer).catch(e => e);
- firstException = firstException || error;
- timer = this._firstTimerInRange(previous, tickTo);
- previous = tickFrom;
+ while (true) {
+ const result = await this._callFirstTimer(tickTo);
+ if (!result.timerFound)
+ break;
+ firstException = firstException || result.error;
}
this._advanceNow(tickTo);
if (firstException)
throw firstException;
-
- return this._now.ticks;
}
- async recordTick(tickValue: string | number) {
- const msFloat = parseTime(tickValue);
- this._advanceNow(this._now.ticks + msFloat);
+ pause() {
+ this._replayLogOnce();
+ this._innerPause();
}
- async tick(tickValue: string | number): Promise {
- return await this._doTick(parseTime(tickValue));
+ private _innerPause() {
+ this._realTime = undefined;
+ this._updateRealTimeTimer();
}
- async next(): Promise {
- const timer = this._firstTimer();
- if (!timer)
- return this._now.ticks;
- await this._callTimer(timer);
- return this._now.ticks;
+ resume() {
+ this._replayLogOnce();
+ this._innerResume();
}
- async runToFrame(): Promise {
- return this.tick(this.getTimeToNextFrame());
+ private _innerResume() {
+ const now = this._embedder.performanceNow();
+ this._realTime = { startTicks: now, lastSyncTicks: now };
+ this._updateRealTimeTimer();
}
- async runAll(): Promise {
- for (let i = 0; i < this._loopLimit; i++) {
- const numTimers = this._timers.size;
- if (numTimers === 0)
- return this._now.ticks;
-
- await this.next();
+ private _updateRealTimeTimer() {
+ if (!this._realTime) {
+ this._currentRealTimeTimer?.dispose();
+ this._currentRealTimeTimer = undefined;
+ return;
}
- const excessJob = this._firstTimer();
- if (!excessJob)
- return this._now.ticks;
- throw this._getInfiniteLoopError(excessJob);
+ const firstTimer = this._firstTimer();
+
+ // Either run the next timer or move time in 100ms chunks.
+ const callAt = Math.min(firstTimer ? firstTimer.callAt : this._now.ticks + maxTimeout, this._now.ticks + 100);
+ if (this._currentRealTimeTimer && this._currentRealTimeTimer.callAt < callAt)
+ return;
+
+ if (this._currentRealTimeTimer) {
+ this._currentRealTimeTimer.dispose();
+ this._currentRealTimeTimer = undefined;
+ }
+
+ this._currentRealTimeTimer = {
+ callAt,
+ dispose: this._embedder.setTimeout(() => {
+ const now = Math.ceil(this._embedder.performanceNow());
+ this._currentRealTimeTimer = undefined;
+ const sinceLastSync = now - this._realTime!.lastSyncTicks;
+ this._realTime!.lastSyncTicks = now;
+ // eslint-disable-next-line no-console
+ this._runTo(this._now.ticks + sinceLastSync).catch(e => console.error(e)).then(() => this._updateRealTimeTimer());
+ }, callAt - this._now.ticks),
+ };
}
- async runToLast(): Promise {
- const timer = this._lastTimer();
- if (!timer)
- return this._now.ticks;
- return await this.tick(timer.callAt - this._now.ticks);
- }
-
- reset() {
- this._timers.clear();
- this._now = { time: this.timeOrigin, ticks: 0, timeFrozen: false };
- }
-
- async jump(tickValue: string | number): Promise {
- const msFloat = parseTime(tickValue);
- const ms = Math.floor(msFloat);
-
+ async fastForward(ticks: number) {
+ this._replayLogOnce();
+ const ms = ticks | 0;
for (const timer of this._timers.values()) {
if (this._now.ticks + ms > timer.callAt)
timer.callAt = this._now.ticks + ms;
}
- return await this.tick(ms);
+ await this.runFor(ms);
+ }
+
+ async fastForwardTo(time: number) {
+ this._replayLogOnce();
+ const ticks = time - this._now.time;
+ await this.fastForward(ticks);
}
addTimer(options: { func: TimerHandler, type: TimerType, delay?: number | string, args?: any[] }): number {
+ this._replayLogOnce();
if (options.func === undefined)
throw new Error('Callback must be provided to timer calls');
@@ -204,56 +254,56 @@ export class ClockController {
error: new Error(),
};
this._timers.set(timer.id, timer);
+ if (this._realTime)
+ this._updateRealTimeTimer();
return timer.id;
}
- private _firstTimerInRange(from: number, to: number): Timer | null {
- let firstTimer: Timer | null = null;
- for (const timer of this._timers.values()) {
- const isInRange = inRange(from, to, timer);
- if (isInRange && (!firstTimer || compareTimers(firstTimer, timer) === 1))
- firstTimer = timer;
- }
- return firstTimer;
- }
-
countTimers() {
return this._timers.size;
}
- private _firstTimer(): Timer | null {
+ private _firstTimer(beforeTick?: number): Timer | null {
let firstTimer: Timer | null = null;
for (const timer of this._timers.values()) {
- if (!firstTimer || compareTimers(firstTimer, timer) === 1)
+ const isInRange = beforeTick === undefined || timer.callAt <= beforeTick;
+ if (isInRange && (!firstTimer || compareTimers(firstTimer, timer) === 1))
firstTimer = timer;
}
return firstTimer;
}
- private _lastTimer(): Timer | null {
- let lastTimer: Timer | null = null;
+ private _takeFirstTimer(beforeTick?: number): Timer | null {
+ const timer = this._firstTimer(beforeTick);
+ if (!timer)
+ return null;
- for (const timer of this._timers.values()) {
- if (!lastTimer || compareTimers(lastTimer, timer) === -1)
- lastTimer = timer;
- }
- return lastTimer;
- }
-
- private async _callTimer(timer: Timer) {
this._advanceNow(timer.callAt);
if (timer.type === TimerType.Interval)
this._timers.get(timer.id)!.callAt += timer.delay;
else
this._timers.delete(timer.id);
+ return timer;
+ }
+
+ private async _callFirstTimer(beforeTick: number): Promise<{ timerFound: boolean, error?: Error }> {
+ const timer = this._takeFirstTimer(beforeTick);
+ if (!timer)
+ return { timerFound: false };
this._duringTick = true;
try {
if (typeof timer.func !== 'function') {
- (() => { eval(timer.func); })();
- return;
+ let error: Error | undefined;
+ try {
+ (() => { eval(timer.func); })();
+ } catch (e) {
+ error = e;
+ }
+ await new Promise(f => this._embedder.setTimeout(f));
+ return { timerFound: true, error };
}
let args = timer.args;
@@ -262,67 +312,26 @@ export class ClockController {
else if (timer.type === TimerType.IdleCallback)
args = [{ didTimeout: false, timeRemaining: () => 0 }];
- timer.func.apply(null, args);
- await new Promise(f => this._embedder.postTask(f));
+ let error: Error | undefined;
+ try {
+ timer.func.apply(null, args);
+ } catch (e) {
+ error = e;
+ }
+ await new Promise(f => this._embedder.setTimeout(f));
+ return { timerFound: true, error };
} finally {
this._duringTick = false;
}
}
- private _getInfiniteLoopError(job: Timer) {
- const infiniteLoopError = new Error(
- `Aborting after running ${this._loopLimit} timers, assuming an infinite loop!`,
- );
-
- if (!job.error)
- return infiniteLoopError;
-
- // pattern never matched in Node
- const computedTargetPattern = /target\.*[<|(|[].*?[>|\]|)]\s*/;
- const clockMethodPattern = new RegExp(
- String(Object.keys(this).join('|')),
- );
-
- let matchedLineIndex = -1;
- job.error.stack!.split('\n').some((line, i) => {
- // If we've matched a computed target line (e.g. setTimeout) then we
- // don't need to look any further. Return true to stop iterating.
- const matchedComputedTarget = line.match(computedTargetPattern);
- /* istanbul ignore if */
- if (matchedComputedTarget) {
- matchedLineIndex = i;
- return true;
- }
-
- // If we've matched a clock method line, then there may still be
- // others further down the trace. Return false to keep iterating.
- const matchedClockMethod = line.match(clockMethodPattern);
- if (matchedClockMethod) {
- matchedLineIndex = i;
- return false;
- }
-
- // If we haven't matched anything on this line, but we matched
- // previously and set the matched line index, then we can stop.
- // If we haven't matched previously, then we should keep iterating.
- return matchedLineIndex >= 0;
- });
-
- const funcName = typeof job.func === 'function' ? job.func.name : 'anonymous';
- const stack = `${infiniteLoopError}\n${job.type || 'Microtask'} - ${funcName}\n${job.error.stack!
- .split('\n')
- .slice(matchedLineIndex + 1)
- .join('\n')}`;
-
- infiniteLoopError.stack = stack;
- return infiniteLoopError;
- }
-
getTimeToNextFrame() {
return 16 - this._now.ticks % 16;
}
clearTimer(timerId: number, type: TimerType) {
+ this._replayLogOnce();
+
if (!timerId) {
// null appears to be allowed in most browsers, and appears to be
// relied upon by some libraries, like Bootstrap carousel
@@ -356,64 +365,49 @@ export class ClockController {
}
}
- advanceAutomatically(advanceTimeDelta: number = 20): () => void {
- return this._embedder.postTaskPeriodically(
- () => this._doTick(advanceTimeDelta!),
- advanceTimeDelta,
- );
+ private _replayLogOnce() {
+ if (!this._log.length)
+ return;
+
+ let lastLogTime = -1;
+ let isPaused = false;
+
+ for (const { type, time, param } of this._log) {
+ if (!isPaused && lastLogTime !== -1)
+ this._advanceNow(this._now.ticks + time - lastLogTime);
+ lastLogTime = time;
+
+ if (type === 'install') {
+ this._innerSetTime(param!);
+ } else if (type === 'fastForward' || type === 'runFor') {
+ this._advanceNow(this._now.ticks + param!);
+ } else if (type === 'fastForwardTo') {
+ this._innerSetTime(param!);
+ } else if (type === 'pause') {
+ this._innerPause();
+ isPaused = true;
+ } else if (type === 'resume') {
+ this._innerResume();
+ isPaused = false;
+ } else if (type === 'setFixedTime') {
+ this._innerSetFixedTime(param!);
+ } else if (type === 'setSystemTime') {
+ this._innerSetTime(param!);
+ }
+ }
+
+ if (!isPaused && lastLogTime > 0)
+ this._advanceNow(this._now.ticks + this._embedder.dateNow() - lastLogTime);
+
+ this._log.length = 0;
}
}
-function getEpoch(epoch: Date | number | undefined): number {
- if (!epoch)
- return 0;
- if (typeof epoch !== 'number')
- return epoch.getTime();
- return epoch;
-}
-
-function inRange(from: number, to: number, timer: Timer): boolean {
- return timer && timer.callAt >= from && timer.callAt <= to;
-}
-
-/**
- * Parse strings like '01:10:00' (meaning 1 hour, 10 minutes, 0 seconds) into
- * number of milliseconds. This is used to support human-readable strings passed
- * to clock.tick()
- */
-function parseTime(value: number | string): number {
- if (typeof value === 'number')
- return value;
- if (!value)
- return 0;
- const str = value;
-
- const strings = str.split(':');
- const l = strings.length;
- let i = l;
- let ms = 0;
- let parsed;
-
- if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) {
- throw new Error(
- `Clock only understands numbers, 'mm:ss' and 'hh:mm:ss'`,
- );
- }
-
- while (i--) {
- parsed = parseInt(strings[i], 10);
- if (parsed >= 60)
- throw new Error(`Invalid time ${str}`);
- ms += parsed * Math.pow(60, l - i - 1);
- }
-
- return ms * 1000;
-}
-
function mirrorDateProperties(target: any, source: typeof Date): DateConstructor & Date {
- let prop;
- for (prop of Object.keys(source) as (keyof DateConstructor)[])
- target[prop] = source[prop];
+ for (const prop in source) {
+ if (source.hasOwnProperty(prop))
+ target[prop] = (source as any)[prop];
+ }
target.toString = () => source.toString();
target.prototype = source.prototype;
target.parse = source.parse;
@@ -485,7 +479,7 @@ function createIntl(clock: ClockController, NativeIntl: typeof Intl): typeof Int
* All properties of Intl are non-enumerable, so we need
* to do a bit of work to get them out.
*/
- for (const key of Object.keys(NativeIntl) as (keyof typeof Intl)[])
+ for (const key of Object.getOwnPropertyNames(NativeIntl) as (keyof typeof Intl)[])
ClockIntl[key] = NativeIntl[key];
ClockIntl.DateTimeFormat = function(...args: any[]) {
@@ -644,8 +638,8 @@ function getClearHandler(type: TimerType) {
function fakePerformance(clock: ClockController, performance: Performance): Performance {
const result: any = {
now: () => clock.performanceNow(),
- timeOrigin: clock.timeOrigin,
};
+ result.__defineGetter__('timeOrigin', () => clock._now.origin || 0);
// eslint-disable-next-line no-proto
for (const key of Object.keys((performance as any).__proto__)) {
if (key === 'now' || key === 'timeOrigin')
@@ -658,19 +652,22 @@ function fakePerformance(clock: ClockController, performance: Performance): Perf
return result;
}
-export function createClock(globalObject: WindowOrWorkerGlobalScope, config: ClockConfig = {}): { clock: ClockController, api: ClockMethods, originals: ClockMethods } {
+export function createClock(globalObject: WindowOrWorkerGlobalScope): { clock: ClockController, api: ClockMethods, originals: ClockMethods } {
const originals = platformOriginals(globalObject);
- const embedder = {
- postTask: (task: () => void) => {
- originals.bound.setTimeout(task, 0);
+ const embedder: Embedder = {
+ dateNow: () => originals.raw.Date.now(),
+ performanceNow: () => originals.raw.performance!.now(),
+ setTimeout: (task: () => void, timeout?: number) => {
+ const timerId = originals.bound.setTimeout(task, timeout);
+ return () => originals.bound.clearTimeout(timerId);
},
- postTaskPeriodically: (task: () => void, delay: number) => {
- const intervalId = globalObject.setInterval(task, delay);
+ setInterval: (task: () => void, delay: number) => {
+ const intervalId = originals.bound.setInterval(task, delay);
return () => originals.bound.clearInterval(intervalId);
},
};
- const clock = new ClockController(embedder, config.now, config.loopLimit);
+ const clock = new ClockController(embedder);
const api = createApi(clock, originals.bound);
return { clock, api, originals: originals.raw };
}
@@ -682,7 +679,7 @@ export function install(globalObject: WindowOrWorkerGlobalScope, config: Install
throw new TypeError(`Can't install fake timers twice on the same global object.`);
}
- const { clock, api, originals } = createClock(globalObject, config);
+ const { clock, api, originals } = createClock(globalObject);
const toFake = config.toFake?.length ? config.toFake : Object.keys(originals) as (keyof ClockMethods)[];
for (const method of toFake) {
@@ -706,11 +703,10 @@ export function install(globalObject: WindowOrWorkerGlobalScope, config: Install
}
export function inject(globalObject: WindowOrWorkerGlobalScope) {
+ const { clock: controller } = install(globalObject);
+ controller.resume();
return {
- install: (config: InstallConfig) => {
- const { clock } = install(globalObject, config);
- return clock;
- },
+ controller,
builtin: platformOriginals(globalObject).bound,
};
}
diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts
index 10dd00f198..7fe30e2ef2 100644
--- a/packages/playwright-core/types/types.d.ts
+++ b/packages/playwright-core/types/types.d.ts
@@ -17247,8 +17247,40 @@ export interface BrowserServer {
* controlled by the same clock.
*/
export interface Clock {
+ /**
+ * Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user
+ * closing the laptop lid for a while and reopening it later, after given time.
+ *
+ * **Usage**
+ *
+ * ```js
+ * await page.clock.fastForward(1000);
+ * await page.clock.fastForward('30:00');
+ * ```
+ *
+ * @param ticks Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are
+ * "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
+ */
+ fastForward(ticks: number|string): Promise;
+
+ /**
+ * Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user
+ * closing the laptop lid for a while and reopening it at the specified time.
+ *
+ * **Usage**
+ *
+ * ```js
+ * await page.clock.fastForwardTo(new Date('2020-02-02'));
+ * await page.clock.fastForwardTo('2020-02-02');
+ * ```
+ *
+ * @param time
+ */
+ fastForwardTo(time: number|string|Date): Promise;
+
/**
* Install fake implementations for the following time-related functions:
+ * - `Date`
* - `setTimeout`
* - `clearTimeout`
* - `setInterval`
@@ -17261,34 +17293,33 @@ export interface Clock {
*
* Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers,
* and control the behavior of time-dependent functions. See
- * [clock.runFor(time)](https://playwright.dev/docs/api/class-clock#clock-run-for) and
- * [clock.skipTime(time)](https://playwright.dev/docs/api/class-clock#clock-skip-time) for more information.
- * @param time Install fake timers with the specified base time.
+ * [clock.runFor(ticks)](https://playwright.dev/docs/api/class-clock#clock-run-for) and
+ * [clock.fastForward(ticks)](https://playwright.dev/docs/api/class-clock#clock-fast-forward) for more information.
* @param options
*/
- installFakeTimers(time: number|Date, options?: {
+ install(options?: {
/**
- * The maximum number of timers that will be run in
- * [clock.runAllTimers()](https://playwright.dev/docs/api/class-clock#clock-run-all-timers). Defaults to `1000`.
+ * Time to initialize with, current system time by default.
*/
- loopLimit?: number;
+ time?: number|string|Date;
}): Promise;
/**
- * Runs all pending timers until there are none remaining. If new timers are added while it is executing they will be
- * run as well. Fake timers must be installed. Returns fake milliseconds since the unix epoch.
- *
- * **Details**
- *
- * This makes it easier to run asynchronous tests to completion without worrying about the number of timers they use,
- * or the delays in those timers. It runs a maximum of `loopLimit` times after which it assumes there is an infinite
- * loop of timers and throws an error.
+ * Pause timers. Once this method is called, no timers are fired unless
+ * [clock.runFor(ticks)](https://playwright.dev/docs/api/class-clock#clock-run-for),
+ * [clock.fastForward(ticks)](https://playwright.dev/docs/api/class-clock#clock-fast-forward),
+ * [clock.fastForwardTo(time)](https://playwright.dev/docs/api/class-clock#clock-fast-forward-to) or
+ * [clock.resume()](https://playwright.dev/docs/api/class-clock#clock-resume) is called.
*/
- runAllTimers(): Promise;
+ pause(): Promise;
/**
- * Advance the clock, firing callbacks if necessary. Returns fake milliseconds since the unix epoch. Fake timers must
- * be installed. Returns fake milliseconds since the unix epoch.
+ * Resumes timers. Once this method is called, time resumes flowing, timers are fired as usual.
+ */
+ resume(): Promise;
+
+ /**
+ * Advance the clock, firing all the time-related callbacks.
*
* **Usage**
*
@@ -17297,55 +17328,41 @@ export interface Clock {
* await page.clock.runFor('30:00');
* ```
*
- * @param time Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are
+ * @param ticks Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are
* "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
*/
- runFor(time: number|string): Promise;
+ runFor(ticks: number|string): Promise;
/**
- * This takes note of the last scheduled timer when it is run, and advances the clock to that time firing callbacks as
- * necessary. If new timers are added while it is executing they will be run only if they would occur before this
- * time. This is useful when you want to run a test to completion, but the test recursively sets timers that would
- * cause runAll to trigger an infinite loop warning. Fake timers must be installed. Returns fake milliseconds since
- * the unix epoch.
- */
- runToLastTimer(): Promise;
-
- /**
- * Advances the clock to the moment of the first scheduled timer, firing it. Fake timers must be installed. Returns
- * fake milliseconds since the unix epoch.
- */
- runToNextTimer(): Promise;
-
- /**
- * Set the clock to the specified time.
- *
- * When fake timers are installed, only fires timers at most once. This can be used to simulate the JS engine (such as
- * a browser) being put to sleep and resumed later, skipping intermediary timers.
- * @param time
- */
- setTime(time: number|Date): Promise;
-
- /**
- * Advance the clock by jumping forward in time, equivalent to running
- * [clock.setTime(time)](https://playwright.dev/docs/api/class-clock#clock-set-time) with the new target time.
- *
- * When fake timers are installed, [clock.skipTime(time)](https://playwright.dev/docs/api/class-clock#clock-skip-time)
- * only fires due timers at most once, while
- * [clock.runFor(time)](https://playwright.dev/docs/api/class-clock#clock-run-for) fires all the timers up to the
- * current time. Returns fake milliseconds since the unix epoch.
+ * Makes `Date.now` and `new Date()` return fixed fake time at all times, keeps all the timers running.
*
* **Usage**
*
* ```js
- * await page.clock.skipTime(1000);
- * await page.clock.skipTime('30:00');
+ * await page.clock.setFixedTime(Date.now());
+ * await page.clock.setFixedTime(new Date('2020-02-02'));
+ * await page.clock.setFixedTime('2020-02-02');
* ```
*
- * @param time Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are
- * "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
+ * @param time Time to be set.
*/
- skipTime(time: number|string): Promise;
+ setFixedTime(time: number|string|Date): Promise;
+
+ /**
+ * Sets current system time but does not trigger any timers, unlike
+ * [clock.fastForwardTo(time)](https://playwright.dev/docs/api/class-clock#clock-fast-forward-to).
+ *
+ * **Usage**
+ *
+ * ```js
+ * await page.clock.setSystemTime(Date.now());
+ * await page.clock.setSystemTime(new Date('2020-02-02'));
+ * await page.clock.setSystemTime('2020-02-02');
+ * ```
+ *
+ * @param time
+ */
+ setSystemTime(time: number|string|Date): Promise;
}
/**
diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts
index c8523e8de5..85bd3a8ffa 100644
--- a/packages/protocol/src/channels.ts
+++ b/packages/protocol/src/channels.ts
@@ -1460,13 +1460,14 @@ export interface BrowserContextChannel extends BrowserContextEventTarget, EventT
harExport(params: BrowserContextHarExportParams, metadata?: CallMetadata): Promise;
createTempFile(params: BrowserContextCreateTempFileParams, metadata?: CallMetadata): Promise;
updateSubscription(params: BrowserContextUpdateSubscriptionParams, metadata?: CallMetadata): Promise;
- clockInstallFakeTimers(params: BrowserContextClockInstallFakeTimersParams, metadata?: CallMetadata): Promise;
- clockRunAllTimers(params?: BrowserContextClockRunAllTimersParams, metadata?: CallMetadata): Promise;
+ clockFastForward(params: BrowserContextClockFastForwardParams, metadata?: CallMetadata): Promise;
+ clockFastForwardTo(params: BrowserContextClockFastForwardToParams, metadata?: CallMetadata): Promise;
+ clockInstall(params: BrowserContextClockInstallParams, metadata?: CallMetadata): Promise;
+ clockPause(params?: BrowserContextClockPauseParams, metadata?: CallMetadata): Promise;
+ clockResume(params?: BrowserContextClockResumeParams, metadata?: CallMetadata): Promise;
clockRunFor(params: BrowserContextClockRunForParams, metadata?: CallMetadata): Promise;
- clockRunToLastTimer(params?: BrowserContextClockRunToLastTimerParams, metadata?: CallMetadata): Promise;
- clockRunToNextTimer(params?: BrowserContextClockRunToNextTimerParams, metadata?: CallMetadata): Promise;
- clockSetTime(params: BrowserContextClockSetTimeParams, metadata?: CallMetadata): Promise;
- clockSkipTime(params: BrowserContextClockSkipTimeParams, metadata?: CallMetadata): Promise;
+ clockSetFixedTime(params: BrowserContextClockSetFixedTimeParams, metadata?: CallMetadata): Promise;
+ clockSetSystemTime(params: BrowserContextClockSetSystemTimeParams, metadata?: CallMetadata): Promise;
}
export type BrowserContextBindingCallEvent = {
binding: BindingCallChannel,
@@ -1755,58 +1756,66 @@ export type BrowserContextUpdateSubscriptionOptions = {
};
export type BrowserContextUpdateSubscriptionResult = void;
-export type BrowserContextClockInstallFakeTimersParams = {
- time: number,
- loopLimit?: number,
+export type BrowserContextClockFastForwardParams = {
+ ticksNumber?: number,
+ ticksString?: string,
};
-export type BrowserContextClockInstallFakeTimersOptions = {
- loopLimit?: number,
+export type BrowserContextClockFastForwardOptions = {
+ ticksNumber?: number,
+ ticksString?: string,
};
-export type BrowserContextClockInstallFakeTimersResult = void;
-export type BrowserContextClockRunAllTimersParams = {};
-export type BrowserContextClockRunAllTimersOptions = {};
-export type BrowserContextClockRunAllTimersResult = {
- fakeTime: number,
-};
-export type BrowserContextClockRunForParams = {
+export type BrowserContextClockFastForwardResult = void;
+export type BrowserContextClockFastForwardToParams = {
timeNumber?: number,
timeString?: string,
};
+export type BrowserContextClockFastForwardToOptions = {
+ timeNumber?: number,
+ timeString?: string,
+};
+export type BrowserContextClockFastForwardToResult = void;
+export type BrowserContextClockInstallParams = {
+ timeNumber?: number,
+ timeString?: string,
+};
+export type BrowserContextClockInstallOptions = {
+ timeNumber?: number,
+ timeString?: string,
+};
+export type BrowserContextClockInstallResult = void;
+export type BrowserContextClockPauseParams = {};
+export type BrowserContextClockPauseOptions = {};
+export type BrowserContextClockPauseResult = void;
+export type BrowserContextClockResumeParams = {};
+export type BrowserContextClockResumeOptions = {};
+export type BrowserContextClockResumeResult = void;
+export type BrowserContextClockRunForParams = {
+ ticksNumber?: number,
+ ticksString?: string,
+};
export type BrowserContextClockRunForOptions = {
+ ticksNumber?: number,
+ ticksString?: string,
+};
+export type BrowserContextClockRunForResult = void;
+export type BrowserContextClockSetFixedTimeParams = {
timeNumber?: number,
timeString?: string,
};
-export type BrowserContextClockRunForResult = {
- fakeTime: number,
-};
-export type BrowserContextClockRunToLastTimerParams = {};
-export type BrowserContextClockRunToLastTimerOptions = {};
-export type BrowserContextClockRunToLastTimerResult = {
- fakeTime: number,
-};
-export type BrowserContextClockRunToNextTimerParams = {};
-export type BrowserContextClockRunToNextTimerOptions = {};
-export type BrowserContextClockRunToNextTimerResult = {
- fakeTime: number,
-};
-export type BrowserContextClockSetTimeParams = {
- time: number,
-};
-export type BrowserContextClockSetTimeOptions = {
-
-};
-export type BrowserContextClockSetTimeResult = void;
-export type BrowserContextClockSkipTimeParams = {
+export type BrowserContextClockSetFixedTimeOptions = {
timeNumber?: number,
timeString?: string,
};
-export type BrowserContextClockSkipTimeOptions = {
+export type BrowserContextClockSetFixedTimeResult = void;
+export type BrowserContextClockSetSystemTimeParams = {
timeNumber?: number,
timeString?: string,
};
-export type BrowserContextClockSkipTimeResult = {
- fakeTime: number,
+export type BrowserContextClockSetSystemTimeOptions = {
+ timeNumber?: number,
+ timeString?: string,
};
+export type BrowserContextClockSetSystemTimeResult = void;
export interface BrowserContextEvents {
'bindingCall': BrowserContextBindingCallEvent;
diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml
index 7f6bd3bfe1..65fc2db4de 100644
--- a/packages/protocol/src/protocol.yml
+++ b/packages/protocol/src/protocol.yml
@@ -1204,40 +1204,39 @@ BrowserContext:
- requestFailed
enabled: boolean
- clockInstallFakeTimers:
+ clockFastForward:
parameters:
- time: number
- loopLimit: number?
+ ticksNumber: number?
+ ticksString: string?
- clockRunAllTimers:
- returns:
- fakeTime: number
+ clockFastForwardTo:
+ parameters:
+ timeNumber: number?
+ timeString: string?
+
+ clockInstall:
+ parameters:
+ timeNumber: number?
+ timeString: string?
+
+ clockPause:
+
+ clockResume:
clockRunFor:
parameters:
- timeNumber: number?
- timeString: string?
- returns:
- fakeTime: number
+ ticksNumber: number?
+ ticksString: string?
- clockRunToLastTimer:
- returns:
- fakeTime: number
-
- clockRunToNextTimer:
- returns:
- fakeTime: number
-
- clockSetTime:
- parameters:
- time: number
-
- clockSkipTime:
+ clockSetFixedTime:
+ parameters:
+ timeNumber: number?
+ timeString: string?
+
+ clockSetSystemTime:
parameters:
timeNumber: number?
timeString: string?
- returns:
- fakeTime: number
events:
diff --git a/tests/library/clock.spec.ts b/tests/library/clock.spec.ts
index 554b49d1a7..a77356c172 100644
--- a/tests/library/clock.spec.ts
+++ b/tests/library/clock.spec.ts
@@ -18,8 +18,9 @@ import { test, expect } from '@playwright/test';
import { createClock as rawCreateClock, install as rawInstall } from '../../packages/playwright-core/src/server/injected/clock';
import type { InstallConfig, ClockController, ClockMethods } from '../../packages/playwright-core/src/server/injected/clock';
-const createClock = (now?: Date | number, loopLimit?: number): ClockController & ClockMethods => {
- const { clock, api } = rawCreateClock(globalThis, { now, loopLimit });
+const createClock = (now?: number): ClockController & ClockMethods => {
+ const { clock, api } = rawCreateClock(globalThis);
+ clock.setSystemTime(now || 0);
for (const key of Object.keys(api))
clock[key] = api[key];
return clock as ClockController & ClockMethods;
@@ -27,26 +28,25 @@ const createClock = (now?: Date | number, loopLimit?: number): ClockController &
type ClockFixtures = {
clock: ClockController & ClockMethods;
- now: Date | number | undefined;
- loopLimit: number | undefined;
- install: (config?: InstallConfig) => ClockController & ClockMethods;
+ now: number | undefined;
+ install: (now?: number) => ClockController & ClockMethods;
installEx: (config?: InstallConfig) => { clock: ClockController, api: ClockMethods, originals: ClockMethods };
};
const it = test.extend({
- clock: async ({ now, loopLimit }, use) => {
- const clock = createClock(now, loopLimit);
+ clock: async ({ now }, use) => {
+ const clock = createClock(now);
await use(clock);
},
now: undefined,
- loopLimit: undefined,
-
install: async ({}, use) => {
let clockObject: ClockController & ClockMethods;
- const install = (config?: InstallConfig) => {
- const { clock, api } = rawInstall(globalThis, config);
+ const install = (now?: number) => {
+ const { clock, api } = rawInstall(globalThis);
+ if (now)
+ clock.setSystemTime(now);
for (const key of Object.keys(api))
clock[key] = api[key];
clockObject = clock as ClockController & ClockMethods;
@@ -98,7 +98,7 @@ it.describe('setTimeout', () => {
clock1.setTimeout(stubs[0], 100);
clock2.setTimeout(stubs[1], 100);
- await clock2.tick(200);
+ await clock2.runFor(200);
expect(stubs[0].called).toBeFalsy();
expect(stubs[1].called).toBeTruthy();
@@ -110,7 +110,7 @@ it.describe('setTimeout', () => {
evalCalled = true;
// @ts-expect-error
}, '10');
- await clock.tick(10);
+ await clock.runFor(10);
expect(evalCalled).toBeTruthy();
});
@@ -120,7 +120,7 @@ it.describe('setTimeout', () => {
evalCalled = true;
// @ts-expect-error
}, 'string');
- await clock.tick(10);
+ await clock.runFor(10);
expect(evalCalled).toBeTruthy();
});
@@ -128,67 +128,52 @@ it.describe('setTimeout', () => {
it('passes setTimeout parameters', async ({ clock }) => {
const stub = createStub();
clock.setTimeout(stub, 2, 'the first', 'the second');
- await clock.tick(3);
+ await clock.runFor(3);
expect(stub.calledWithExactly('the first', 'the second')).toBeTruthy();
});
it('calls correct timeout on recursive tick', async ({ clock }) => {
const stub = createStub();
const recurseCallback = () => {
- void clock.tick(100);
+ void clock.runFor(100);
};
clock.setTimeout(recurseCallback, 50);
clock.setTimeout(stub, 100);
- await clock.tick(50);
+ await clock.runFor(50);
expect(stub.called).toBeTruthy();
});
it('does not depend on this', async ({ clock }) => {
const stub = createStub();
clock.setTimeout(stub, 100);
- await clock.tick(100);
+ await clock.runFor(100);
expect(stub.called).toBeTruthy();
});
it('is not influenced by forward system clock changes', async ({ clock }) => {
const stub = createStub();
clock.setTimeout(stub, 5000);
- await clock.tick(1000);
- clock.setTime(new clock.Date().getTime() + 1000);
- await clock.tick(3990);
+ await clock.runFor(1000);
+ clock.setSystemTime(new clock.Date().getTime() + 1000);
+ await clock.runFor(3990);
expect(stub.callCount).toBe(0);
- await clock.tick(20);
+ await clock.runFor(20);
expect(stub.callCount).toBe(1);
});
it('is not influenced by backward system clock changes', async ({ clock }) => {
const stub = createStub();
clock.setTimeout(stub, 5000);
- await clock.tick(1000);
- clock.setTime(new clock.Date().getTime() - 1000);
- await clock.tick(3990);
+ await clock.runFor(1000);
+ clock.setSystemTime(new clock.Date().getTime() - 1000);
+ await clock.runFor(3990);
expect(stub.callCount).toBe(0);
- await clock.tick(20);
+ await clock.runFor(20);
expect(stub.callCount).toBe(1);
});
- it('handles Infinity and negative Infinity correctly', async ({ clock }) => {
- const calls = [];
- clock.setTimeout(() => {
- calls.push('NaN');
- }, NaN);
- clock.setTimeout(() => {
- calls.push('Infinity');
- }, Number.POSITIVE_INFINITY);
- clock.setTimeout(() => {
- calls.push('-Infinity');
- }, Number.NEGATIVE_INFINITY);
- await clock.runAll();
- expect(calls).toEqual(['NaN', 'Infinity', '-Infinity']);
- });
-
it.describe('use of eval when not in node', () => {
it.beforeEach(() => {
globalThis.evalCalled = false;
@@ -200,7 +185,7 @@ it.describe('setTimeout', () => {
it('evals non-function callbacks', async ({ clock }) => {
clock.setTimeout('globalThis.evalCalled = true', 10);
- await clock.tick(10);
+ await clock.runFor(10);
expect(globalThis.evalCalled).toBeTruthy();
});
@@ -209,7 +194,7 @@ it.describe('setTimeout', () => {
const x = 15;
try {
clock.setTimeout('x', x);
- await clock.tick(x);
+ await clock.runFor(x);
expect(true).toBeFalsy();
} catch (e) {
expect(e).toBeInstanceOf(ReferenceError);
@@ -218,25 +203,25 @@ it.describe('setTimeout', () => {
});
});
-it.describe('tick', () => {
+it.describe('runFor', () => {
it('triggers immediately without specified delay', async ({ clock }) => {
const stub = createStub();
clock.setTimeout(stub);
- await clock.tick(0);
+ await clock.runFor(0);
expect(stub.called).toBeTruthy();
});
it('does not trigger without sufficient delay', async ({ clock }) => {
const stub = createStub();
clock.setTimeout(stub, 100);
- await clock.tick(10);
+ await clock.runFor(10);
expect(stub.called).toBeFalsy();
});
it('triggers after sufficient delay', async ({ clock }) => {
const stub = createStub();
clock.setTimeout(stub, 100);
- await clock.tick(100);
+ await clock.runFor(100);
expect(stub.called).toBeTruthy();
});
@@ -244,7 +229,7 @@ it.describe('tick', () => {
const spies = [createStub(), createStub()];
clock.setTimeout(spies[0], 100);
clock.setTimeout(spies[1], 100);
- await clock.tick(100);
+ await clock.runFor(100);
expect(spies[0].called).toBeTruthy();
expect(spies[1].called).toBeTruthy();
});
@@ -255,7 +240,7 @@ it.describe('tick', () => {
clock.setTimeout(spies[1], 100);
clock.setTimeout(spies[2], 99);
clock.setTimeout(spies[3], 100);
- await clock.tick(100);
+ await clock.runFor(100);
expect(spies[0].called).toBeTruthy();
expect(spies[1].called).toBeTruthy();
expect(spies[2].called).toBeTruthy();
@@ -274,19 +259,19 @@ it.describe('tick', () => {
// First spy calls another setTimeout with delay=0
clock.setTimeout(spies[0], 0);
clock.setTimeout(spies[2], 10);
- await clock.tick(10);
+ await clock.runFor(10);
expect(spies[0].called).toBeTruthy();
expect(spies[1].called).toBeTruthy();
expect(spies[2].called).toBeTruthy();
});
it('waits after setTimeout was called', async ({ clock }) => {
- await clock.tick(100);
+ await clock.runFor(100);
const stub = createStub();
clock.setTimeout(stub, 150);
- await clock.tick(50);
+ await clock.runFor(50);
expect(stub.called).toBeFalsy();
- await clock.tick(100);
+ await clock.runFor(100);
expect(stub.called).toBeTruthy();
});
@@ -294,19 +279,19 @@ it.describe('tick', () => {
const stubs = [createStub(), createStub(), createStub()];
clock.setTimeout(stubs[0], 100);
clock.setTimeout(stubs[1], 120);
- await clock.tick(10);
- await clock.tick(89);
+ await clock.runFor(10);
+ await clock.runFor(89);
expect(stubs[0].called).toBeFalsy();
expect(stubs[1].called).toBeFalsy();
clock.setTimeout(stubs[2], 20);
- await clock.tick(1);
+ await clock.runFor(1);
expect(stubs[0].called).toBeTruthy();
expect(stubs[1].called).toBeFalsy();
expect(stubs[2].called).toBeFalsy();
- await clock.tick(19);
+ await clock.runFor(19);
expect(stubs[1].called).toBeFalsy();
expect(stubs[2].called).toBeTruthy();
- await clock.tick(1);
+ await clock.runFor(1);
expect(stubs[1].called).toBeTruthy();
});
@@ -316,7 +301,7 @@ it.describe('tick', () => {
clock.setTimeout(stubs[0], 100);
clock.setTimeout(stubs[1], 120);
- await expect(clock.tick(120)).rejects.toThrow();
+ await expect(clock.runFor(120)).rejects.toThrow();
expect(stubs[0].called).toBeTruthy();
expect(stubs[1].called).toBeTruthy();
@@ -325,7 +310,7 @@ it.describe('tick', () => {
it('calls function with global object or null (strict mode) as this', async ({ clock }) => {
const stub = createStub().throws();
clock.setTimeout(stub, 100);
- await expect(clock.tick(100)).rejects.toThrow();
+ await expect(clock.runFor(100)).rejects.toThrow();
expect(stub.calledOn(global) || stub.calledOn(null)).toBeTruthy();
});
@@ -334,7 +319,7 @@ it.describe('tick', () => {
clock.setTimeout(spies[0], 13);
clock.setTimeout(spies[1], 11);
- await clock.tick(15);
+ await clock.runFor(15);
expect(spies[1].calledBefore(spies[0])).toBeTruthy();
});
@@ -346,7 +331,7 @@ it.describe('tick', () => {
spy(new clock.Date().getTime());
}, 10);
- await clock.tick(100);
+ await clock.runFor(100);
expect(spy.callCount).toBe(10);
expect(spy.calledWith(10)).toBeTruthy();
@@ -364,7 +349,7 @@ it.describe('tick', () => {
it('fires timer in intervals of 13', async ({ clock }) => {
const spy = createStub();
clock.setInterval(spy, 13);
- await clock.tick(500);
+ await clock.runFor(500);
expect(spy.callCount).toBe(38);
});
@@ -372,7 +357,7 @@ it.describe('tick', () => {
const spy = createStub();
// @ts-expect-error
clock.setInterval(spy, '13');
- await clock.tick(500);
+ await clock.runFor(500);
expect(spy.callCount).toBe(38);
});
@@ -388,7 +373,7 @@ it.describe('tick', () => {
spy10(new clock.Date().getTime());
}, 10);
- await clock.tick(500);
+ await clock.runFor(500);
expect(spy13.callCount).toBe(38);
expect(spy10.callCount).toBe(50);
@@ -402,7 +387,7 @@ it.describe('tick', () => {
clock.setInterval(spies[0], 10);
clock.setTimeout(spies[1], 50);
- await clock.tick(100);
+ await clock.runFor(100);
expect(spies[0].calledBefore(spies[1])).toBeTruthy();
expect(spies[0].callCount).toBe(10);
@@ -417,62 +402,18 @@ it.describe('tick', () => {
clock.clearInterval(id);
});
id = clock.setInterval(callback, 10);
- await clock.tick(100);
+ await clock.runFor(100);
expect(callback.callCount).toBe(3);
});
- it('passes 8 seconds', async ({ clock }) => {
- const spy = createStub();
- clock.setInterval(spy, 4000);
- await clock.tick('08');
- expect(spy.callCount).toBe(2);
- });
-
- it('passes 1 minute', async ({ clock }) => {
- const spy = createStub();
- clock.setInterval(spy, 6000);
- await clock.tick('01:00');
- expect(spy.callCount).toBe(10);
- });
-
- it('passes 2 hours, 34 minutes and 10 seconds', async ({ clock }) => {
- const spy = createStub();
- clock.setInterval(spy, 10000);
- await clock.tick('02:34:10');
- expect(spy.callCount).toBe(925);
- });
-
- it('throws for invalid format', async ({ clock }) => {
- const spy = createStub();
- clock.setInterval(spy, 10000);
-
- await expect(clock.tick('12:02:34:10')).rejects.toThrow();
-
- expect(spy.callCount).toBe(0);
- });
-
- it('throws for invalid minutes', async ({ clock }) => {
- const spy = createStub();
- clock.setInterval(spy, 10000);
- await expect(clock.tick('67:10')).rejects.toThrow();
- expect(spy.callCount).toBe(0);
- });
-
it('throws for negative minutes', async ({ clock }) => {
const spy = createStub();
clock.setInterval(spy, 10000);
- await expect(clock.tick('-7:10')).rejects.toThrow();
+ await expect(clock.runFor(-7)).rejects.toThrow();
expect(spy.callCount).toBe(0);
});
- it('treats missing argument as 0', async ({ clock }) => {
- // @ts-expect-error
- await clock.tick();
-
- expect(clock.now()).toBe(0);
- });
-
it('fires nested setTimeout calls properly', async ({ clock }) => {
let i = 0;
const callback = () => {
@@ -483,7 +424,7 @@ it.describe('tick', () => {
};
callback();
- await clock.tick(1000);
+ await clock.runFor(1000);
expect(i).toBe(11);
});
@@ -492,74 +433,69 @@ it.describe('tick', () => {
throw new Error('oh no!');
};
clock.setTimeout(callback, 1000);
- await expect(clock.tick(1000)).rejects.toThrow();
- });
-
- it('returns the current now value', async ({ clock }) => {
- const value = await clock.tick(200);
- expect(clock.now()).toBe(value);
+ await expect(clock.runFor(1000)).rejects.toThrow();
});
it('is not influenced by forward system clock changes', async ({ clock }) => {
const callback = () => {
- clock.setTime(new clock.Date().getTime() + 1000);
+ clock.setSystemTime(new clock.Date().getTime() + 1000);
};
const stub = createStub();
clock.setTimeout(callback, 1000);
clock.setTimeout(stub, 2000);
- await clock.tick(1990);
+ await clock.runFor(1990);
expect(stub.callCount).toBe(0);
- await clock.tick(20);
+ await clock.runFor(20);
expect(stub.callCount).toBe(1);
});
it('is not influenced by forward system clock changes 2', async ({ clock }) => {
const callback = () => {
- clock.setTime(new clock.Date().getTime() - 1000);
+ clock.setSystemTime(new clock.Date().getTime() - 1000);
};
const stub = createStub();
clock.setTimeout(callback, 1000);
clock.setTimeout(stub, 2000);
- await clock.tick(1990);
+ await clock.runFor(1990);
expect(stub.callCount).toBe(0);
- await clock.tick(20);
+ await clock.runFor(20);
expect(stub.callCount).toBe(1);
});
it('is not influenced by forward system clock changes when an error is thrown', async ({ clock }) => {
const callback = () => {
- clock.setTime(new clock.Date().getTime() + 1000);
+ clock.setSystemTime(new clock.Date().getTime() + 1000);
throw new Error();
};
const stub = createStub();
clock.setTimeout(callback, 1000);
clock.setTimeout(stub, 2000);
- await expect(clock.tick(1990)).rejects.toThrow();
+ await expect(clock.runFor(1990)).rejects.toThrow();
expect(stub.callCount).toBe(0);
- await clock.tick(20);
+ await clock.runFor(20);
expect(stub.callCount).toBe(1);
});
it('is not influenced by forward system clock changes when an error is thrown 2', async ({ clock }) => {
const callback = () => {
- clock.setTime(new clock.Date().getTime() - 1000);
+ clock.setSystemTime(new clock.Date().getTime() - 1000);
throw new Error();
};
const stub = createStub();
clock.setTimeout(callback, 1000);
clock.setTimeout(stub, 2000);
- await expect(clock.tick(1990)).rejects.toThrow();
+ await expect(clock.runFor(1990)).rejects.toThrow();
expect(stub.callCount).toBe(0);
- await clock.tick(20);
+ await clock.runFor(20);
expect(stub.callCount).toBe(1);
});
it('throws on negative ticks', async ({ clock }) => {
- await expect(clock.tick(-500)).rejects.toThrow('Negative ticks are not supported');
+ await expect(clock.runFor(-500)).rejects.toThrow('Negative ticks are not supported');
});
it('creates updated Date while ticking promises', async ({ clock }) => {
@@ -571,7 +507,7 @@ it.describe('tick', () => {
});
}, 10);
- await clock.tick(100);
+ await clock.runFor(100);
expect(spy.callCount).toBe(10);
expect(spy.calledWith(10)).toBeTruthy();
@@ -602,7 +538,7 @@ it.describe('tick', () => {
});
}, 10);
- await clock.tick(500);
+ await clock.runFor(500);
expect(spy13.callCount).toBe(38);
expect(spy10.callCount).toBe(50);
@@ -624,7 +560,7 @@ it.describe('tick', () => {
});
id = clock.setInterval(callback, 10);
- await clock.tick(100);
+ await clock.runFor(100);
expect(callback.callCount).toBe(3);
});
@@ -646,22 +582,22 @@ it.describe('tick', () => {
// Clock API is async.
await new Promise(setImmediate);
- await clock.tick(1000);
+ await clock.runFor(1000);
expect(i).toBe(11);
});
it('is not influenced by forward system clock changes in promises', async ({ clock }) => {
const callback = () => {
void Promise.resolve().then(() => {
- clock.setTime(new clock.Date().getTime() + 1000);
+ clock.setSystemTime(new clock.Date().getTime() + 1000);
});
};
const stub = createStub();
clock.setTimeout(callback, 1000);
clock.setTimeout(stub, 2000);
- await clock.tick(1990);
+ await clock.runFor(1990);
expect(stub.callCount).toBe(0);
- await clock.tick(20);
+ await clock.runFor(20);
expect(stub.callCount).toBe(1);
});
@@ -672,7 +608,7 @@ it.describe('tick', () => {
void Promise.resolve().then(spy);
}, 100);
- await clock.tick(100);
+ await clock.runFor(100);
expect(spy.called).toBeTruthy();
});
@@ -687,7 +623,7 @@ it.describe('tick', () => {
.then(spies[2]);
}, 100);
- await clock.tick(100);
+ await clock.runFor(100);
expect(spies[0].called).toBeTruthy();
expect(spies[1].called).toBeTruthy();
@@ -703,7 +639,7 @@ it.describe('tick', () => {
void Promise.resolve().then(spies[2]);
}, 100);
- await clock.tick(100);
+ await clock.runFor(100);
expect(spies[0].called).toBeTruthy();
expect(spies[1].called).toBeTruthy();
@@ -721,7 +657,7 @@ it.describe('tick', () => {
});
}, 100);
- await clock.tick(100);
+ await clock.runFor(100);
expect(spy.called).toBeTruthy();
});
@@ -734,7 +670,7 @@ it.describe('tick', () => {
void Promise.resolve().then(spies[2]).catch(spies[3]);
}, 100);
- await clock.tick(100);
+ await clock.runFor(100);
expect(spies[0].callCount).toBe(0);
expect(spies[1].called).toBeTruthy();
@@ -751,7 +687,7 @@ it.describe('tick', () => {
clock.setTimeout(spies[1], 200);
- await clock.tick(200);
+ await clock.runFor(200);
expect(spies[0].calledBefore(spies[1])).toBeTruthy();
});
@@ -764,7 +700,7 @@ it.describe('tick', () => {
// Clock API is async.
await new Promise(setImmediate);
- await clock.tick(100);
+ await clock.runFor(100);
expect(spies[0].calledBefore(spies[1])).toBeTruthy();
});
@@ -781,516 +717,18 @@ it.describe('tick', () => {
// Clock API is async.
await new Promise(setImmediate);
- await clock.tick(100);
+ await clock.runFor(100);
expect(spies[0].calledBefore(spies[1])).toBeTruthy();
});
});
-it.describe('next', () => {
- it('triggers the next timer', async ({ clock }) => {
- const stub = createStub();
- clock.setTimeout(stub, 100);
-
- await clock.next();
-
- expect(stub.called).toBeTruthy();
- });
-
- it('does not trigger simultaneous timers', async ({ clock }) => {
- const spies = [createStub(), createStub()];
- clock.setTimeout(spies[0], 100);
- clock.setTimeout(spies[1], 100);
-
- await clock.next();
-
- expect(spies[0].called).toBeTruthy();
- expect(spies[1].called).toBeFalsy();
- });
-
- it('subsequent calls trigger simultaneous timers', async ({ clock }) => {
- const spies = [createStub(), createStub(), createStub(), createStub()];
- clock.setTimeout(spies[0], 100);
- clock.setTimeout(spies[1], 100);
- clock.setTimeout(spies[2], 99);
- clock.setTimeout(spies[3], 100);
-
- await clock.next();
-
- expect(spies[2].called).toBeTruthy();
- expect(spies[0].called).toBeFalsy();
- expect(spies[1].called).toBeFalsy();
- expect(spies[3].called).toBeFalsy();
-
- await clock.next();
-
- expect(spies[0].called).toBeTruthy();
- expect(spies[1].called).toBeFalsy();
- expect(spies[3].called).toBeFalsy();
-
- await clock.next();
-
- expect(spies[1].called).toBeTruthy();
- expect(spies[3].called).toBeFalsy();
-
- await clock.next();
-
- expect(spies[3].called).toBeTruthy();
- });
-
- it('subsequent calls trigger simultaneous timers with zero callAt', async ({ clock }) => {
- const spies = [
- createStub(() => {
- clock.setTimeout(spies[1], 0);
- }),
- createStub(),
- createStub(),
- ];
-
- // First spy calls another setTimeout with delay=0
- clock.setTimeout(spies[0], 0);
- clock.setTimeout(spies[2], 10);
-
- await clock.next();
-
- expect(spies[0].called).toBeTruthy();
- expect(spies[1].called).toBeFalsy();
-
- await clock.next();
-
- expect(spies[1].called).toBeTruthy();
-
- await clock.next();
-
- expect(spies[2].called).toBeTruthy();
- });
-
- it('throws exception thrown by timer', async ({ clock }) => {
- const stub = createStub().throws();
- clock.setTimeout(stub, 100);
- await expect(clock.next()).rejects.toThrow();
- expect(stub.called).toBeTruthy();
- });
-
- it('calls function with global object or null (strict mode) as this', async ({ clock }) => {
- const stub = createStub().throws();
- clock.setTimeout(stub, 100);
- await expect(clock.next()).rejects.toThrow();
- expect(stub.calledOn(global) || stub.calledOn(null)).toBeTruthy();
- });
-
- it('subsequent calls trigger in the order scheduled', async ({ clock }) => {
- const spies = [createStub(), createStub()];
- clock.setTimeout(spies[0], 13);
- clock.setTimeout(spies[1], 11);
-
- await clock.next();
- await clock.next();
-
- expect(spies[1].calledBefore(spies[0])).toBeTruthy();
- });
-
- it('creates updated Date while ticking', async ({ clock }) => {
- const spy = createStub();
-
- clock.setInterval(() => {
- spy(new clock.Date().getTime());
- }, 10);
-
- await clock.next();
- await clock.next();
- await clock.next();
- await clock.next();
- await clock.next();
- await clock.next();
- await clock.next();
- await clock.next();
- await clock.next();
- await clock.next();
-
- expect(spy.callCount).toBe(10);
- expect(spy.calledWith(10)).toBeTruthy();
- expect(spy.calledWith(20)).toBeTruthy();
- expect(spy.calledWith(30)).toBeTruthy();
- expect(spy.calledWith(40)).toBeTruthy();
- expect(spy.calledWith(50)).toBeTruthy();
- expect(spy.calledWith(60)).toBeTruthy();
- expect(spy.calledWith(70)).toBeTruthy();
- expect(spy.calledWith(80)).toBeTruthy();
- expect(spy.calledWith(90)).toBeTruthy();
- expect(spy.calledWith(100)).toBeTruthy();
- });
-
- it('subsequent calls trigger timeouts and intervals in the order scheduled', async ({ clock }) => {
- const spies = [createStub(), createStub()];
- clock.setInterval(spies[0], 10);
- clock.setTimeout(spies[1], 50);
-
- await clock.next();
- await clock.next();
- await clock.next();
- await clock.next();
- await clock.next();
- await clock.next();
-
- expect(spies[0].calledBefore(spies[1])).toBeTruthy();
- expect(spies[0].callCount).toBe(5);
- expect(spies[1].callCount).toBe(1);
- });
-
- it('subsequent calls do not fire canceled intervals', async ({ clock }) => {
- // ESLint fails to detect this correctly
- /* eslint-disable prefer-const */
- let id;
- const callback = createStub(() => {
- if (callback.callCount === 3)
- clock.clearInterval(id);
- });
-
- id = clock.setInterval(callback, 10);
- await clock.next();
- await clock.next();
- await clock.next();
- await clock.next();
-
- expect(callback.callCount).toBe(3);
- });
-
- it('advances the clock based on when the timer was supposed to be called', async ({ clock }) => {
- clock.setTimeout(createStub(), 55);
- await clock.next();
-
- expect(clock.now()).toBe(55);
- });
-
- it('returns the current now value', async ({ clock }) => {
- clock.setTimeout(createStub(), 55);
- const value = await clock.next();
-
- expect(clock.now()).toBe(value);
- });
-
- it('does not fire intervals canceled in promises', async ({ clock }) => {
- // ESLint fails to detect this correctly
- /* eslint-disable prefer-const */
- let id;
- const callback = createStub(() => {
- if (callback.callCount === 3) {
- void Promise.resolve().then(() => {
- clock.clearInterval(id);
- });
- }
- });
-
- id = clock.setInterval(callback, 10);
- await clock.next();
- await clock.next();
- await clock.next();
- await clock.next();
-
- expect(callback.callCount).toBe(3);
- });
-
- it('should settle user-created promises', async ({ clock }) => {
- const spy = createStub();
-
- clock.setTimeout(() => {
- void Promise.resolve().then(spy);
- }, 55);
-
- await clock.next();
-
- expect(spy.called).toBeTruthy();
- });
-
- it('should settle nested user-created promises', async ({ clock }) => {
- const spy = createStub();
-
- clock.setTimeout(() => {
- void Promise.resolve().then(() => {
- void Promise.resolve().then(() => {
- void Promise.resolve().then(spy);
- });
- });
- }, 55);
-
- await clock.next();
-
- expect(spy.called).toBeTruthy();
- });
-
- it('should settle local promises before firing timers', async ({ clock }) => {
- const spies = [createStub(), createStub()];
- void Promise.resolve().then(spies[0]);
- clock.setTimeout(spies[1], 55);
-
- // Clock API is async.
- await new Promise(setImmediate);
- await clock.next();
- expect(spies[0].calledBefore(spies[1])).toBeTruthy();
- });
-});
-
-it.describe('runAll', () => {
- it('if there are no timers just return', async ({ clock }) => {
- await clock.runAll();
- });
-
- it('runs all timers', async ({ clock }) => {
- const spies = [createStub(), createStub()];
- clock.setTimeout(spies[0], 10);
- clock.setTimeout(spies[1], 50);
-
- await clock.runAll();
-
- expect(spies[0].called).toBeTruthy();
- expect(spies[1].called).toBeTruthy();
- });
-
- it('new timers added while running are also run', async ({ clock }) => {
- const spies = [
- createStub(() => {
- clock.setTimeout(spies[1], 50);
- }),
- createStub(),
- ];
-
- // Spy calls another setTimeout
- clock.setTimeout(spies[0], 10);
-
- await clock.runAll();
-
- expect(spies[0].called).toBeTruthy();
- expect(spies[1].called).toBeTruthy();
- });
-
- it('throws before allowing infinite recursion', async ({ clock }) => {
- const recursiveCallback = () => {
- clock.setTimeout(recursiveCallback, 10);
- };
- recursiveCallback();
- await expect(clock.runAll()).rejects.toThrow();
- });
-
- it('the loop limit can be set when creating a clock', async ({}) => {
- const clock = createClock(0, 1);
- const spies = [createStub(), createStub()];
- clock.setTimeout(spies[0], 10);
- clock.setTimeout(spies[1], 50);
- await expect(clock.runAll()).rejects.toThrow();
- });
-
- it('the loop limit can be set when installing a clock', async ({ install }) => {
- const clock = install({ loopLimit: 1 });
- const spies = [createStub(), createStub()];
- setTimeout(spies[0], 10);
- setTimeout(spies[1], 50);
-
- await expect(clock.runAll()).rejects.toThrow();
- });
-
- it('throws before allowing infinite recursion from promises', async ({ clock }) => {
- const recursiveCallback = () => {
- void Promise.resolve().then(() => {
- clock.setTimeout(recursiveCallback, 10);
- });
- };
- recursiveCallback();
-
- // Clock API is async.
- await new Promise(setImmediate);
- await expect(clock.runAll()).rejects.toThrow();
- });
-
- it('should settle user-created promises', async ({ clock }) => {
- const spy = createStub();
- clock.setTimeout(() => {
- void Promise.resolve().then(spy);
- }, 55);
- await clock.runAll();
- expect(spy.called).toBeTruthy();
- });
-
- it('should settle nested user-created promises', async ({ clock }) => {
- const spy = createStub();
-
- clock.setTimeout(() => {
- void Promise.resolve().then(() => {
- void Promise.resolve().then(() => {
- void Promise.resolve().then(spy);
- });
- });
- }, 55);
-
- await clock.runAll();
-
- expect(spy.called).toBeTruthy();
- });
-
- it('should settle local promises before firing timers', async ({ clock }) => {
- const spies = [createStub(), createStub()];
- void Promise.resolve().then(spies[0]);
- clock.setTimeout(spies[1], 55);
-
- // Clock API is async.
- await new Promise(setImmediate);
- await clock.runAll();
- expect(spies[0].calledBefore(spies[1])).toBeTruthy();
- });
-
- it('should settle user-created promises before firing more timers', async ({ clock }) => {
- const spies = [createStub(), createStub()];
- clock.setTimeout(() => {
- void Promise.resolve().then(spies[0]);
- }, 55);
- clock.setTimeout(spies[1], 75);
- await clock.runAll();
- expect(spies[0].calledBefore(spies[1])).toBeTruthy();
- });
-});
-
-it.describe('runToLast', () => {
- it('returns current time when there are no timers', async ({ clock }) => {
- const time = await clock.runToLast();
- expect(time).toBe(0);
- });
-
- it('runs all existing timers', async ({ clock }) => {
- const spies = [createStub(), createStub()];
- clock.setTimeout(spies[0], 10);
- clock.setTimeout(spies[1], 50);
- await clock.runToLast();
- expect(spies[0].called).toBeTruthy();
- expect(spies[1].called).toBeTruthy();
- });
-
- it('returns time of the last timer', async ({ clock }) => {
- const spies = [createStub(), createStub()];
- clock.setTimeout(spies[0], 10);
- clock.setTimeout(spies[1], 50);
- const time = await clock.runToLast();
- expect(time).toBe(50);
- });
-
- it('runs all existing timers when two timers are matched for being last', async ({ clock }) => {
- const spies = [createStub(), createStub()];
- clock.setTimeout(spies[0], 10);
- clock.setTimeout(spies[1], 10);
- await clock.runToLast();
- expect(spies[0].called).toBeTruthy();
- expect(spies[1].called).toBeTruthy();
- });
-
- it('new timers added with a call time later than the last existing timer are NOT run', async ({ clock }) => {
- const spies = [
- createStub(() => {
- clock.setTimeout(spies[1], 50);
- }),
- createStub(),
- ];
-
- // Spy calls another setTimeout
- clock.setTimeout(spies[0], 10);
- await clock.runToLast();
- expect(spies[0].called).toBeTruthy();
- expect(spies[1].called).toBeFalsy();
- });
-
- it('new timers added with a call time earlier than the last existing timer are run', async ({ clock }) => {
- const spies = [
- createStub(),
- createStub(() => {
- clock.setTimeout(spies[2], 50);
- }),
- createStub(),
- ];
-
- clock.setTimeout(spies[0], 100);
- // Spy calls another setTimeout
- clock.setTimeout(spies[1], 10);
- await clock.runToLast();
- expect(spies[0].called).toBeTruthy();
- expect(spies[1].called).toBeTruthy();
- expect(spies[2].called).toBeTruthy();
- });
-
- it('new timers cannot cause an infinite loop', async ({ clock }) => {
- const spy = createStub();
- const recursiveCallback = () => {
- clock.setTimeout(recursiveCallback, 0);
- };
-
- clock.setTimeout(recursiveCallback, 0);
- clock.setTimeout(spy, 100);
- await clock.runToLast();
- expect(spy.called).toBeTruthy();
- });
-
- it('should support clocks with start time', async ({ clock }) => {
- let invocations = 0;
-
- clock.setTimeout(function cb() {
- invocations++;
- clock.setTimeout(cb, 50);
- }, 50);
-
- await clock.runToLast();
-
- expect(invocations).toBe(1);
- });
-
- it('should settle user-created promises', async ({ clock }) => {
- const spy = createStub();
- clock.setTimeout(() => {
- void Promise.resolve().then(spy);
- }, 55);
- await clock.runToLast();
- expect(spy.called).toBeTruthy();
- });
-
- it('should settle nested user-created promises', async ({ clock }) => {
- const spy = createStub();
-
- clock.setTimeout(() => {
- void Promise.resolve().then(() => {
- void Promise.resolve().then(() => {
- void Promise.resolve().then(spy);
- });
- });
- }, 55);
-
- await clock.runToLast();
- expect(spy.called).toBeTruthy();
- });
-
- it('should settle local promises before firing timers', async ({ clock }) => {
- const spies = [createStub(), createStub()];
- void Promise.resolve().then(spies[0]);
- clock.setTimeout(spies[1], 55);
-
- // Clock API is async.
- await new Promise(setImmediate);
- await clock.runToLast();
- expect(spies[0].calledBefore(spies[1])).toBeTruthy();
- });
-
- it('should settle user-created promises before firing more timers', async ({ clock }) => {
- const spies = [createStub(), createStub()];
- clock.setTimeout(() => {
- void Promise.resolve().then(spies[0]);
- }, 55);
- clock.setTimeout(spies[1], 75);
- await clock.runToLast();
- expect(spies[0].calledBefore(spies[1])).toBeTruthy();
- });
-});
-
it.describe('clearTimeout', () => {
it('removes timeout', async ({ clock }) => {
const stub = createStub();
const id = clock.setTimeout(stub, 50);
clock.clearTimeout(id);
- await clock.tick(50);
+ await clock.runFor(50);
expect(stub.called).toBeFalsy();
});
@@ -1298,7 +736,7 @@ it.describe('clearTimeout', () => {
const stub = createStub();
const id = clock.setInterval(stub, 50);
clock.clearTimeout(id);
- await clock.tick(50);
+ await clock.runFor(50);
expect(stub.called).toBeFalsy();
});
@@ -1306,7 +744,7 @@ it.describe('clearTimeout', () => {
const stub = createStub();
const id = clock.setInterval(stub);
clock.clearTimeout(id);
- await clock.tick(50);
+ await clock.runFor(50);
expect(stub.called).toBeFalsy();
});
@@ -1315,21 +753,6 @@ it.describe('clearTimeout', () => {
});
});
-it.describe('reset', () => {
- it('resets to the time install with - issue #183', async ({ clock }) => {
- await clock.tick(100);
- clock.reset();
- expect(clock.now()).toBe(0);
- });
-
- it('resets hrTime - issue #206', async ({ clock }) => {
- await clock.tick(100);
- expect(clock.performance.now()).toEqual(100);
- clock.reset();
- expect(clock.performance.now()).toEqual(0);
- });
-});
-
it.describe('setInterval', () => {
it('throws if no arguments', async ({ clock }) => {
expect(() => {
@@ -1353,7 +776,7 @@ it.describe('setInterval', () => {
it('schedules recurring timeout', async ({ clock }) => {
const stub = createStub();
clock.setInterval(stub, 10);
- await clock.tick(99);
+ await clock.runFor(99);
expect(stub.callCount).toBe(9);
});
@@ -1361,23 +784,23 @@ it.describe('setInterval', () => {
it('is not influenced by forward system clock changes', async ({ clock }) => {
const stub = createStub();
clock.setInterval(stub, 10);
- await clock.tick(11);
+ await clock.runFor(11);
expect(stub.callCount).toBe(1);
- clock.setTime(new clock.Date().getTime() + 1000);
- await clock.tick(8);
+ clock.setSystemTime(new clock.Date().getTime() + 1000);
+ await clock.runFor(8);
expect(stub.callCount).toBe(1);
- await clock.tick(3);
+ await clock.runFor(3);
expect(stub.callCount).toBe(2);
});
it('is not influenced by backward system clock changes', async ({ clock }) => {
const stub = createStub();
clock.setInterval(stub, 10);
- await clock.tick(5);
- clock.setTime(new clock.Date().getTime() - 1000);
- await clock.tick(6);
+ await clock.runFor(5);
+ clock.setSystemTime(new clock.Date().getTime() - 1000);
+ await clock.runFor(6);
expect(stub.callCount).toBe(1);
- await clock.tick(10);
+ await clock.runFor(10);
expect(stub.callCount).toBe(2);
});
@@ -1388,7 +811,7 @@ it.describe('setInterval', () => {
});
const id = clock.setInterval(stub, 10);
- await clock.tick(100);
+ await clock.runFor(100);
expect(stub.callCount).toBe(3);
});
@@ -1396,7 +819,7 @@ it.describe('setInterval', () => {
it('passes setTimeout parameters', async ({ clock }) => {
const stub = createStub();
clock.setInterval(stub, 2, 'the first', 'the second');
- await clock.tick(3);
+ await clock.runFor(3);
expect(stub.calledWithExactly('the first', 'the second')).toBeTruthy();
});
});
@@ -1406,7 +829,7 @@ it.describe('clearInterval', () => {
const stub = createStub();
const id = clock.setInterval(stub, 50);
clock.clearInterval(id);
- await clock.tick(50);
+ await clock.runFor(50);
expect(stub.called).toBeFalsy();
});
@@ -1414,7 +837,7 @@ it.describe('clearInterval', () => {
const stub = createStub();
const id = clock.setInterval(stub);
clock.clearInterval(id);
- await clock.tick(50);
+ await clock.runFor(50);
expect(stub.called).toBeFalsy();
});
@@ -1422,7 +845,7 @@ it.describe('clearInterval', () => {
const stub = createStub();
const id = clock.setTimeout(stub, 50);
clock.clearInterval(id);
- await clock.tick(50);
+ await clock.runFor(50);
expect(stub.called).toBeFalsy();
});
@@ -1458,14 +881,14 @@ it.describe('date', () => {
it('listens to ticking clock', async ({ clock }) => {
const date1 = new clock.Date();
- await clock.tick(3);
+ await clock.runFor(3);
const date2 = new clock.Date();
expect(date2.getTime() - date1.getTime()).toBe(3);
});
it('listens to system clock changes', async ({ clock }) => {
const date1 = new clock.Date();
- clock.setTime(date1.getTime() + 1000);
+ clock.setSystemTime(date1.getTime() + 1000);
const date2 = new clock.Date();
expect(date2.getTime() - date1.getTime()).toBe(1000);
});
@@ -1568,16 +991,16 @@ it.describe('stubTimers', () => {
it('returns clock object', async ({ install }) => {
const clock = install();
expect(clock).toEqual(expect.any(Object));
- expect(clock.tick).toEqual(expect.any(Function));
+ expect(clock.runFor).toEqual(expect.any(Function));
});
it('takes an object parameter', async ({ install }) => {
- const clock = install({});
+ const clock = install();
expect(clock).toEqual(expect.any(Object));
});
it('sets initial timestamp', async ({ install }) => {
- const clock = install({ now: 1400 });
+ const clock = install(1400);
expect(clock.now()).toBe(1400);
});
@@ -1586,7 +1009,7 @@ it.describe('stubTimers', () => {
const stub = createStub();
setTimeout(stub, 1000);
- await clock.tick(1000);
+ await clock.runFor(1000);
expect(stub.called).toBeTruthy();
});
@@ -1603,7 +1026,7 @@ it.describe('stubTimers', () => {
const stub = createStub();
clearTimeout(setTimeout(stub, 1000));
- await clock.tick(1000);
+ await clock.runFor(1000);
expect(stub.called).toBeFalsy();
});
@@ -1613,7 +1036,7 @@ it.describe('stubTimers', () => {
const stub = createStub();
setInterval(stub, 500);
- await clock.tick(1000);
+ await clock.runFor(1000);
expect(stub.callCount).toBe(2);
});
@@ -1623,7 +1046,7 @@ it.describe('stubTimers', () => {
const stub = createStub();
clearInterval(setInterval(stub, 500));
- await clock.tick(1000);
+ await clock.runFor(1000);
expect(stub.called).toBeFalsy();
});
@@ -1631,7 +1054,7 @@ it.describe('stubTimers', () => {
it('replaces global performance.now', async ({ install }) => {
const clock = install();
const prev = performance.now();
- await clock.tick(1000);
+ await clock.runFor(1000);
const next = performance.now();
expect(next).toBe(1000);
expect(prev).toBe(0);
@@ -1733,12 +1156,6 @@ it.describe('stubTimers', () => {
expect(Date.prototype).toEqual(clock.Date.prototype);
});
- it('decide on Date.now support at call-time when supported', async ({ install }) => {
- (Date.now as any) = () => {};
- install({ now: 0 });
- expect(Date.now).toEqual(expect.any(Function));
- });
-
it('mirrors custom Date properties', async ({ install }) => {
const f = () => {
return '';
@@ -1761,7 +1178,7 @@ it.describe('stubTimers', () => {
expect(Date).not.toBe(originals.Date);
});
- it('resets faked methods', async ({ install }) => {
+ it('resets faked methods', async ({ }) => {
const { clock, originals } = rawInstall(globalThis, {
now: 0,
toFake: ['setTimeout', 'Date'],
@@ -1784,60 +1201,6 @@ it.describe('stubTimers', () => {
});
});
-it.describe('shouldAdvanceTime', () => {
- it('should create an auto advancing timer', async () => {
- const testDelay = 29;
- const date = new Date('2015-09-25');
- const clock = createClock(date);
- clock.advanceAutomatically();
- expect(clock.Date.now()).toBe(1443139200000);
- const timeoutStarted = clock.Date.now();
-
- let callback: (r: number) => void;
- const promise = new Promise(r => callback = r);
-
- clock.setTimeout(() => {
- const timeDifference = clock.Date.now() - timeoutStarted;
- callback(timeDifference);
- }, testDelay);
- expect(await promise).toBe(testDelay);
-
- });
-
- it('should test setInterval', async () => {
- const interval = 20;
- let intervalsTriggered = 0;
- const cyclesToTrigger = 3;
- const date = new Date('2015-09-25');
- const clock = createClock(date);
- clock.advanceAutomatically();
- expect(clock.Date.now()).toBe(1443139200000);
- const timeoutStarted = clock.Date.now();
-
- let callback: (r: number) => void;
- const promise = new Promise(r => callback = r);
-
- const intervalId = clock.setInterval(() => {
- if (++intervalsTriggered === cyclesToTrigger) {
- clock.clearInterval(intervalId);
- const timeDifference = clock.Date.now() - timeoutStarted;
- callback(timeDifference);
- }
- }, interval);
-
- expect(await promise).toBe(interval * cyclesToTrigger);
- });
-
- it('should not depend on having to stub setInterval or clearInterval to work', async ({ install }) => {
- const origSetInterval = globalThis.setInterval;
- const origClearInterval = globalThis.clearInterval;
-
- install({ toFake: ['setTimeout'] });
- expect(globalThis.setInterval).toBe(origSetInterval);
- expect(globalThis.clearInterval).toBe(origClearInterval);
- });
-});
-
it.describe('requestAnimationFrame', () => {
it('throws if no arguments', async ({ clock }) => {
expect(() => {
@@ -1860,30 +1223,30 @@ it.describe('requestAnimationFrame', () => {
it('should run every 16ms', async ({ clock }) => {
const stub = createStub();
clock.requestAnimationFrame(stub);
- await clock.tick(15);
+ await clock.runFor(15);
expect(stub.callCount).toBe(0);
- await clock.tick(1);
+ await clock.runFor(1);
expect(stub.callCount).toBe(1);
});
it('should be called with performance.now() when available', async ({ clock }) => {
const stub = createStub();
clock.requestAnimationFrame(stub);
- await clock.tick(20);
+ await clock.runFor(20);
expect(stub.calledWith(16)).toBeTruthy();
});
it('should be called with performance.now() even when performance unavailable', async ({ clock }) => {
const stub = createStub();
clock.requestAnimationFrame(stub);
- await clock.tick(20);
+ await clock.runFor(20);
expect(stub.calledWith(16)).toBeTruthy();
});
it('should call callback once', async ({ clock }) => {
const stub = createStub();
clock.requestAnimationFrame(stub);
- await clock.tick(32);
+ await clock.runFor(32);
expect(stub.callCount).toBe(1);
});
@@ -1891,9 +1254,9 @@ it.describe('requestAnimationFrame', () => {
const stub1 = createStub();
const stub2 = createStub();
clock.requestAnimationFrame(stub1);
- await clock.tick(5);
+ await clock.runFor(5);
clock.requestAnimationFrame(stub2);
- await clock.tick(11);
+ await clock.runFor(11);
expect(stub1.calledWith(16)).toBeTruthy();
expect(stub2.calledWith(16)).toBeTruthy();
});
@@ -1902,18 +1265,18 @@ it.describe('requestAnimationFrame', () => {
const stub1 = createStub();
const stub2 = createStub();
clock.requestAnimationFrame(stub1);
- await clock.tick(57);
+ await clock.runFor(57);
clock.requestAnimationFrame(stub2);
- await clock.tick(10);
+ await clock.runFor(10);
expect(stub1.calledWith(16)).toBeTruthy();
expect(stub2.calledWith(64)).toBeTruthy();
});
it('should schedule for next frame if on current frame', async ({ clock }) => {
const stub = createStub();
- await clock.tick(16);
+ await clock.runFor(16);
clock.requestAnimationFrame(stub);
- await clock.tick(16);
+ await clock.runFor(16);
expect(stub.calledWith(32)).toBeTruthy();
});
});
@@ -1923,7 +1286,7 @@ it.describe('cancelAnimationFrame', () => {
const stub = createStub();
const id = clock.requestAnimationFrame(stub);
clock.cancelAnimationFrame(id);
- await clock.tick(16);
+ await clock.runFor(16);
expect(stub.called).toBeFalsy();
});
@@ -1933,7 +1296,7 @@ it.describe('cancelAnimationFrame', () => {
expect(() => {
clock.cancelAnimationFrame(id);
}).toThrow();
- await clock.tick(50);
+ await clock.runFor(50);
expect(stub.called).toBeTruthy();
});
@@ -1943,7 +1306,7 @@ it.describe('cancelAnimationFrame', () => {
expect(() => {
clock.cancelAnimationFrame(id);
}).toThrow();
- await clock.tick(50);
+ await clock.runFor(50);
expect(stub.called).toBeTruthy();
});
@@ -1952,21 +1315,11 @@ it.describe('cancelAnimationFrame', () => {
});
});
-it.describe('runToFrame', () => {
- it('should tick next frame', async ({ clock }) => {
- await clock.runToFrame();
- expect(clock.now()).toBe(16);
- await clock.tick(3);
- await clock.runToFrame();
- expect(clock.now()).toBe(32);
- });
-});
-
-it.describe('jump', () => {
+it.describe('fastForward', () => {
it('ignores timers which wouldn\'t be run', async ({ clock }) => {
const stub = createStub();
clock.setTimeout(stub, 1000);
- await clock.jump(500);
+ await clock.fastForward(500);
expect(stub.called).toBeFalsy();
});
@@ -1975,7 +1328,7 @@ it.describe('jump', () => {
clock.setTimeout(() => {
stub(clock.Date.now());
}, 1000);
- await clock.jump(2000);
+ await clock.fastForward(2000);
expect(stub.callCount).toBe(1);
expect(stub.calledWith(2000)).toBeTruthy();
});
@@ -1988,19 +1341,12 @@ it.describe('jump', () => {
clock.setTimeout(shortTimers[0], 250);
clock.setInterval(shortTimers[1], 100);
clock.requestAnimationFrame(shortTimers[2]);
- await clock.jump(1500);
+ await clock.fastForward(1500);
for (const stub of longTimers)
expect(stub.called).toBeFalsy();
for (const stub of shortTimers)
expect(stub.callCount).toBe(1);
});
-
- it('supports string time arguments', async ({ clock }) => {
- const stub = createStub();
- clock.setTimeout(stub, 100000); // 100000 = 1:40
- await clock.jump('01:50');
- expect(stub.callCount).toBe(1);
- });
});
it.describe('performance.now()', () => {
@@ -2010,7 +1356,7 @@ it.describe('performance.now()', () => {
});
it('should run along with clock.tick', async ({ clock }) => {
- await clock.tick(5000);
+ await clock.runFor(5000);
const result = clock.performance.now();
expect(result).toBe(5000);
});
@@ -2019,7 +1365,7 @@ it.describe('performance.now()', () => {
for (let i = 0; i < 10; i++) {
const next = clock.performance.now();
expect(next).toBe(1000 * i);
- await clock.tick(1000);
+ await clock.runFor(1000);
}
});
@@ -2028,7 +1374,7 @@ it.describe('performance.now()', () => {
const result = clock.performance.now();
expect(result).toBe(2500);
}, 2500);
- await clock.tick(5000);
+ await clock.runFor(5000);
});
});
@@ -2054,7 +1400,7 @@ it.describe('requestIdleCallback', () => {
it('runs after all timers', async ({ clock }) => {
const stub = createStub();
clock.requestIdleCallback(stub);
- await clock.tick(1000);
+ await clock.runFor(1000);
expect(stub.called).toBeTruthy();
const idleCallbackArg = stub.firstCall.args[0];
expect(idleCallbackArg.didTimeout).toBeFalsy();
@@ -2066,7 +1412,7 @@ it.describe('requestIdleCallback', () => {
clock.setTimeout(() => {}, 10);
clock.setTimeout(() => {}, 30);
clock.requestIdleCallback(stub, { timeout: 20 });
- await clock.tick(20);
+ await clock.runFor(20);
expect(stub.called).toBeTruthy();
});
@@ -2074,7 +1420,7 @@ it.describe('requestIdleCallback', () => {
const stub = createStub();
clock.setTimeout(() => {}, 30);
clock.requestIdleCallback(stub);
- await clock.tick(35);
+ await clock.runFor(35);
expect(stub.called).toBeFalsy();
});
});
@@ -2084,149 +1430,11 @@ it.describe('cancelIdleCallback', () => {
const stub = createStub();
const callbackId = clock.requestIdleCallback(stub, { timeout: 0 });
clock.cancelIdleCallback(callbackId);
- await clock.tick(0);
+ await clock.runFor(0);
expect(stub.called).toBeFalsy();
});
});
-it.describe('loop limit stack trace', () => {
- const expectedMessage =
- 'Aborting after running 5 timers, assuming an infinite loop!';
- it.use({ loopLimit: 5 });
-
- it.describe('setTimeout', () => {
- it('provides a stack trace for running all async', async ({ clock }) => {
- const catchSpy = createStub();
- const recursiveCreateTimer = () => {
- clock.setTimeout(recursiveCreateTimer, 10);
- };
-
- recursiveCreateTimer();
- await clock.runAll().catch(catchSpy);
- expect(catchSpy.callCount).toBe(1);
- const err = catchSpy.firstCall.args[0];
- expect(err.message).toBe(expectedMessage);
- expect(err.stack).toMatch(new RegExp(`Error: ${expectedMessage}\\s+Timeout - recursiveCreateTimer`));
- });
-
- it('provides a stack trace for running all sync', async ({ clock }) => {
- let caughtError = false;
- const recursiveCreateTimer = () => {
- clock.setTimeout(recursiveCreateTimer, 10);
- };
-
- recursiveCreateTimer();
- try {
- await clock.runAll();
- } catch (err) {
- caughtError = true;
- expect(err.message).toBe(expectedMessage);
- expect(err.stack).toMatch(new RegExp(`Error: ${expectedMessage}\\s+Timeout - recursiveCreateTimer`));
- }
- expect(caughtError).toBeTruthy();
- });
- });
-
- it.describe('requestIdleCallback', () => {
- it('provides a stack trace for running all async', async ({ clock }) => {
- const catchSpy = createStub();
- const recursiveCreateTimer = () => {
- clock.requestIdleCallback(recursiveCreateTimer, { timeout: 10 });
- };
-
- recursiveCreateTimer();
- await clock.runAll().catch(catchSpy);
- expect(catchSpy.callCount).toBe(1);
- const err = catchSpy.firstCall.args[0];
- expect(err.message).toBe(expectedMessage);
- expect(err.stack).toMatch(new RegExp(`Error: ${expectedMessage}\\s+IdleCallback - recursiveCreateTimer`));
- });
-
- it('provides a stack trace for running all sync', async ({ clock }) => {
- let caughtError = false;
- const recursiveCreateTimer = () => {
- clock.requestIdleCallback(recursiveCreateTimer, { timeout: 10 });
- };
-
- recursiveCreateTimer();
- try {
- await clock.runAll();
- } catch (err) {
- caughtError = true;
- expect(err.message).toBe(expectedMessage);
- expect(err.stack).toMatch(new RegExp(`Error: ${expectedMessage}\\s+IdleCallback - recursiveCreateTimer`));
- }
- expect(caughtError).toBeTruthy();
- });
- });
-
- it.describe('setInterval', () => {
- it('provides a stack trace for running all async', async ({ clock }) => {
- const catchSpy = createStub();
- const recursiveCreateTimer = () => {
- clock.setInterval(recursiveCreateTimer, 10);
- };
-
- recursiveCreateTimer();
- await clock.runAll().catch(catchSpy);
- expect(catchSpy.callCount).toBe(1);
- const err = catchSpy.firstCall.args[0];
- expect(err.message).toBe(expectedMessage);
- expect(err.stack).toMatch(new RegExp(`Error: ${expectedMessage}\\s+Interval - recursiveCreateTimer`));
- });
-
- it('provides a stack trace for running all sync', async ({ clock }) => {
- let caughtError = false;
- const recursiveCreateTimer = () => {
- clock.setInterval(recursiveCreateTimer, 10);
- };
-
- recursiveCreateTimer();
- try {
- await clock.runAll();
- } catch (err) {
- caughtError = true;
- expect(err.message).toBe(expectedMessage);
- expect(err.stack).toMatch(new RegExp(`Error: ${expectedMessage}\\s+Interval - recursiveCreateTimer`));
- }
- expect(caughtError).toBeTruthy();
- });
- });
-
- it.describe('requestAnimationFrame', () => {
- it('provides a stack trace for running all async', async ({ clock }) => {
- const catchSpy = createStub();
- const recursiveCreateTimer = () => {
- clock.requestAnimationFrame(recursiveCreateTimer);
- };
-
- recursiveCreateTimer();
- await clock.runAll().catch(catchSpy);
- expect(catchSpy.callCount).toBe(1);
- const err = catchSpy.firstCall.args[0];
- expect(err.message).toBe(expectedMessage);
- expect(err.stack).toMatch(new RegExp(`Error: ${expectedMessage}\\s+AnimationFrame - recursiveCreateTimer`));
- });
-
- it('provides a stack trace for running all sync', async ({ clock }) => {
- let caughtError = false;
- const recursiveCreateTimer = () => {
- clock.requestAnimationFrame(recursiveCreateTimer);
- };
-
- recursiveCreateTimer();
- try {
- await clock.runAll();
- } catch (err) {
- caughtError = true;
- expect(err.message).toBe(expectedMessage);
- expect(err.stack).toMatch(new RegExp(`Error: ${expectedMessage}\\s+AnimationFrame - recursiveCreateTimer`));
- }
- expect(caughtError).toBeTruthy();
- });
- });
-});
-
it.describe('Intl API', () => {
function isFirstOfMonth(ianaTimeZone, timestamp?: number) {
return (
@@ -2301,13 +1509,13 @@ it.describe('Intl API', () => {
it('formatToParts via isFirstOfMonth -> Returns true when passed no timestamp and system time is first of the month', async ({ install }) => {
// June 1 04:00 UTC - Toronto is June 1 00:00
- install({ now: Date.UTC(2022, 5, 1, 4) });
+ install(Date.UTC(2022, 5, 1, 4));
expect(isFirstOfMonth('America/Toronto')).toBeTruthy();
});
it('formatToParts via isFirstOfMonth -> Returns false when passed no timestamp and system time is not first of the month', async ({ install }) => {
// June 1 00:00 UTC - Toronto is May 31 20:00
- install({ now: Date.UTC(2022, 5, 1) });
+ install(Date.UTC(2022, 5, 1));
expect(isFirstOfMonth('America/Toronto')).toBeFalsy();
});
diff --git a/tests/page/page-clock.frozen.spec.ts b/tests/page/page-clock.frozen.spec.ts
index 3d763a2cc3..bddb794da1 100644
--- a/tests/page/page-clock.frozen.spec.ts
+++ b/tests/page/page-clock.frozen.spec.ts
@@ -18,6 +18,7 @@ import { test as it, expect } from './pageTest';
it.skip(!process.env.PW_FREEZE_TIME);
-it('cock should be frozen', async ({ page }) => {
+it('clock should be frozen', async ({ page }) => {
+ await page.clock.setSystemTime(0);
expect(await page.evaluate('Date.now()')).toBe(0);
});
diff --git a/tests/page/page-clock.spec.ts b/tests/page/page-clock.spec.ts
index 4a6ca00896..fdc01c5570 100644
--- a/tests/page/page-clock.spec.ts
+++ b/tests/page/page-clock.spec.ts
@@ -35,8 +35,12 @@ const it = test.extend<{ calls: { params: any[] }[] }>({
});
it.describe('runFor', () => {
+ it.beforeEach(async ({ page }) => {
+ await page.clock.install();
+ await page.clock.pause();
+ });
+
it('triggers immediately without specified delay', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setTimeout(window.stub);
});
@@ -46,7 +50,6 @@ it.describe('runFor', () => {
});
it('does not trigger without sufficient delay', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setTimeout(window.stub, 100);
});
@@ -55,7 +58,6 @@ it.describe('runFor', () => {
});
it('triggers after sufficient delay', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setTimeout(window.stub, 100);
});
@@ -64,7 +66,6 @@ it.describe('runFor', () => {
});
it('triggers simultaneous timers', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setTimeout(window.stub, 100);
setTimeout(window.stub, 100);
@@ -74,7 +75,6 @@ it.describe('runFor', () => {
});
it('triggers multiple simultaneous timers', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setTimeout(window.stub, 100);
setTimeout(window.stub, 100);
@@ -86,7 +86,6 @@ it.describe('runFor', () => {
});
it('waits after setTimeout was called', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setTimeout(window.stub, 150);
});
@@ -97,18 +96,16 @@ it.describe('runFor', () => {
});
it('triggers event when some throw', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setTimeout(() => { throw new Error(); }, 100);
setTimeout(window.stub, 120);
});
-
await expect(page.clock.runFor(120)).rejects.toThrow();
expect(calls).toHaveLength(1);
});
it('creates updated Date while ticking', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
+ await page.clock.setSystemTime(0);
await page.evaluate(async () => {
setInterval(() => {
window.stub(new Date().getTime());
@@ -130,7 +127,6 @@ it.describe('runFor', () => {
});
it('passes 8 seconds', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setInterval(window.stub, 4000);
});
@@ -140,7 +136,6 @@ it.describe('runFor', () => {
});
it('passes 1 minute', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setInterval(window.stub, 6000);
});
@@ -150,7 +145,6 @@ it.describe('runFor', () => {
});
it('passes 2 hours, 34 minutes and 10 seconds', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setInterval(window.stub, 10000);
});
@@ -160,7 +154,6 @@ it.describe('runFor', () => {
});
it('throws for invalid format', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setInterval(window.stub, 10000);
});
@@ -169,332 +162,93 @@ it.describe('runFor', () => {
});
it('returns the current now value', async ({ page }) => {
- await page.clock.installFakeTimers(0);
+ await page.clock.setSystemTime(0);
const value = 200;
await page.clock.runFor(value);
expect(await page.evaluate(() => Date.now())).toBe(value);
});
});
-it.describe('skipTime', () => {
+it.describe('fastForward', () => {
+ it.beforeEach(async ({ page }) => {
+ await page.clock.install();
+ await page.clock.pause();
+ await page.clock.setSystemTime(0);
+ });
+
it(`ignores timers which wouldn't be run`, async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setTimeout(() => {
window.stub('should not be logged');
}, 1000);
});
- await page.clock.skipTime(500);
+ await page.clock.fastForward(500);
expect(calls).toEqual([]);
});
it('pushes back execution time for skipped timers', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setTimeout(() => {
window.stub(Date.now());
}, 1000);
});
- await page.clock.skipTime(2000);
+ await page.clock.fastForward(2000);
expect(calls).toEqual([{ params: [2000] }]);
});
it('supports string time arguments', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setTimeout(() => {
window.stub(Date.now());
}, 100000); // 100000 = 1:40
});
- await page.clock.skipTime('01:50');
+ await page.clock.fastForward('01:50');
expect(calls).toEqual([{ params: [110000] }]);
});
});
-it.describe('runAllTimers', () => {
- it('if there are no timers just return', async ({ page }) => {
- await page.clock.installFakeTimers(0);
- await page.clock.runAllTimers();
+it.describe('fastForwardTo', () => {
+ it.beforeEach(async ({ page }) => {
+ await page.clock.install();
+ await page.clock.pause();
+ await page.clock.setSystemTime(0);
});
- it('runs all timers', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- setTimeout(window.stub, 10);
- setTimeout(window.stub, 50);
- });
- await page.clock.runAllTimers();
- expect(calls.length).toBe(2);
- });
-
- it('new timers added while running are also run', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
+ it(`ignores timers which wouldn't be run`, async ({ page, calls }) => {
await page.evaluate(async () => {
setTimeout(() => {
- setTimeout(window.stub, 50);
- }, 10);
+ window.stub('should not be logged');
+ }, 1000);
});
- await page.clock.runAllTimers();
- expect(calls.length).toBe(1);
+ await page.clock.fastForwardTo(500);
+ expect(calls).toEqual([]);
});
- it('new timers added in promises while running are also run', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
+ it('pushes back execution time for skipped timers', async ({ page, calls }) => {
await page.evaluate(async () => {
setTimeout(() => {
- void Promise.resolve().then(() => {
- setTimeout(window.stub, 50);
- });
- }, 10);
+ window.stub(Date.now());
+ }, 1000);
});
- await page.clock.runAllTimers();
- expect(calls.length).toBe(1);
- });
- it('throws before allowing infinite recursion', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- const recursiveCallback = () => {
- window.stub();
- setTimeout(recursiveCallback, 10);
- };
- setTimeout(recursiveCallback, 10);
- });
- await expect(page.clock.runAllTimers()).rejects.toThrow();
- expect(calls).toHaveLength(1000);
- });
-
- it('throws before allowing infinite recursion from promises', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- const recursiveCallback = () => {
- window.stub();
- void Promise.resolve().then(() => {
- setTimeout(recursiveCallback, 10);
- });
- };
- setTimeout(recursiveCallback, 10);
- });
- await expect(page.clock.runAllTimers()).rejects.toThrow();
- expect(calls).toHaveLength(1000);
- });
-
- it('the loop limit can be set when creating a clock', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0, { loopLimit: 1 });
- await page.evaluate(async () => {
- setTimeout(window.stub, 10);
- setTimeout(window.stub, 50);
- });
- await expect(page.clock.runAllTimers()).rejects.toThrow();
- expect(calls).toHaveLength(1);
- });
-
- it('should settle user-created promises', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- setTimeout(() => {
- void Promise.resolve().then(() => window.stub());
- }, 55);
- });
- await page.clock.runAllTimers();
- expect(calls).toHaveLength(1);
- });
-
- it('should settle nested user-created promises', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- setTimeout(() => {
- void Promise.resolve().then(() => {
- void Promise.resolve().then(() => {
- void Promise.resolve().then(() => window.stub());
- });
- });
- }, 55);
- });
- await page.clock.runAllTimers();
- expect(calls).toHaveLength(1);
- });
-
- it('should settle local promises before firing timers', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- void Promise.resolve().then(() => window.stub(1));
- setTimeout(() => window.stub(2), 55);
- });
- await page.clock.runAllTimers();
- expect(calls).toEqual([
- { params: [1] },
- { params: [2] },
- ]);
- });
-});
-
-it.describe('runToLastTimer', () => {
- it('returns current time when there are no timers', async ({ page }) => {
- await page.clock.installFakeTimers(0);
- const time = await page.clock.runToLastTimer();
- expect(time).toBe(0);
- });
-
- it('runs all existing timers', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- setTimeout(window.stub, 10);
- setTimeout(window.stub, 50);
- });
- await page.clock.runToLastTimer();
- expect(calls.length).toBe(2);
- });
-
- it('returns time of the last timer', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- setTimeout(window.stub, 10);
- setTimeout(window.stub, 50);
- });
- const time = await page.clock.runToLastTimer();
- expect(time).toBe(50);
- });
-
- it('runs all existing timers when two timers are matched for being last', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- setTimeout(window.stub, 10);
- setTimeout(window.stub, 10);
- });
- await page.clock.runToLastTimer();
- expect(calls.length).toBe(2);
- });
-
- it('new timers added with a call time later than the last existing timer are NOT run', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- setTimeout(() => {
- window.stub();
- setTimeout(window.stub, 50);
- }, 10);
- });
- await page.clock.runToLastTimer();
- expect(calls.length).toBe(1);
- });
-
- it('new timers added with a call time earlier than the last existing timer are run', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- setTimeout(window.stub, 100);
- setTimeout(() => {
- setTimeout(window.stub, 50);
- }, 10);
- });
- await page.clock.runToLastTimer();
- expect(calls.length).toBe(2);
- });
-
- it('new timers cannot cause an infinite loop', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- const recursiveCallback = () => {
- window.stub();
- setTimeout(recursiveCallback, 0);
- };
- setTimeout(recursiveCallback, 0);
- setTimeout(window.stub, 100);
- });
- await page.clock.runToLastTimer();
- expect(calls.length).toBe(102);
- });
-
- it('should support clocks with start time', async ({ page, calls }) => {
- await page.clock.installFakeTimers(200);
- await page.evaluate(async () => {
- setTimeout(function cb() {
- window.stub();
- setTimeout(cb, 50);
- }, 50);
- });
- await page.clock.runToLastTimer();
- expect(calls.length).toBe(1);
- });
-
- it('new timers created from promises cannot cause an infinite loop', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- const recursiveCallback = () => {
- void Promise.resolve().then(() => {
- setTimeout(recursiveCallback, 0);
- });
- };
- setTimeout(recursiveCallback, 0);
- setTimeout(window.stub, 100);
- });
- await page.clock.runToLastTimer();
- expect(calls.length).toBe(1);
- });
-
- it('should settle user-created promises', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- setTimeout(() => {
- void Promise.resolve().then(() => window.stub());
- }, 55);
- });
- await page.clock.runToLastTimer();
- expect(calls.length).toBe(1);
- });
-
- it('should settle nested user-created promises', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- setTimeout(() => {
- void Promise.resolve().then(() => {
- void Promise.resolve().then(() => {
- void Promise.resolve().then(() => window.stub());
- });
- });
- }, 55);
- });
- await page.clock.runToLastTimer();
- expect(calls.length).toBe(1);
- });
-
- it('should settle local promises before firing timers', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- void Promise.resolve().then(() => window.stub(1));
- setTimeout(() => window.stub(2), 55);
- });
- await page.clock.runToLastTimer();
- expect(calls).toEqual([
- { params: [1] },
- { params: [2] },
- ]);
- });
-
- it('should settle user-created promises before firing more timers', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- setTimeout(() => {
- void Promise.resolve().then(() => window.stub(1));
- }, 55);
- setTimeout(() => window.stub(2), 75);
- });
- await page.clock.runToLastTimer();
- expect(calls).toEqual([
- { params: [1] },
- { params: [2] },
- ]);
+ await page.clock.fastForwardTo(2000);
+ expect(calls).toEqual([{ params: [2000] }]);
});
});
it.describe('stubTimers', () => {
+ it.beforeEach(async ({ page }) => {
+ await page.clock.install();
+ await page.clock.pause();
+ await page.clock.setSystemTime(0);
+ });
it('sets initial timestamp', async ({ page, calls }) => {
- await page.clock.installFakeTimers(1400);
+ await page.clock.setSystemTime(1400);
expect(await page.evaluate(() => Date.now())).toBe(1400);
});
it('replaces global setTimeout', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setTimeout(window.stub, 1000);
});
@@ -503,13 +257,11 @@ it.describe('stubTimers', () => {
});
it('global fake setTimeout should return id', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
const to = await page.evaluate(() => setTimeout(window.stub, 1000));
expect(typeof to).toBe('number');
});
it('replaces global clearTimeout', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
const to = setTimeout(window.stub, 1000);
clearTimeout(to);
@@ -519,7 +271,6 @@ it.describe('stubTimers', () => {
});
it('replaces global setInterval', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
setInterval(window.stub, 500);
});
@@ -528,7 +279,6 @@ it.describe('stubTimers', () => {
});
it('replaces global clearInterval', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
await page.evaluate(async () => {
const to = setInterval(window.stub, 500);
clearInterval(to);
@@ -538,7 +288,6 @@ it.describe('stubTimers', () => {
});
it('replaces global performance.now', async ({ page }) => {
- await page.clock.installFakeTimers(0);
const promise = page.evaluate(async () => {
const prev = performance.now();
await new Promise(f => setTimeout(f, 1000));
@@ -549,30 +298,35 @@ it.describe('stubTimers', () => {
expect(await promise).toEqual({ prev: 0, next: 1000 });
});
- it('replaces global performance.timeOrigin', async ({ page }) => {
- await page.clock.installFakeTimers(1000);
- const promise = page.evaluate(async () => {
- const prev = performance.now();
- await new Promise(f => setTimeout(f, 1000));
- const next = performance.now();
- return { prev, next };
- });
- expect(await page.evaluate(() => performance.timeOrigin)).toBe(1000);
- await page.clock.runFor(1000);
- expect(await promise).toEqual({ prev: 0, next: 1000 });
- });
-
it('fakes Date constructor', async ({ page }) => {
- await page.clock.installFakeTimers(0);
const now = await page.evaluate(() => new Date().getTime());
expect(now).toBe(0);
});
});
+it.describe('stubTimers', () => {
+ it('replaces global performance.timeOrigin', async ({ page }) => {
+ await page.clock.install({ time: 1000 });
+ await page.clock.pause();
+ await page.clock.setSystemTime(1000);
+ const promise = page.evaluate(async () => {
+ const prev = performance.now();
+ await new Promise(f => setTimeout(f, 1000));
+ const next = performance.now();
+ return { prev, next };
+ });
+ await page.clock.runFor(1000);
+ expect(await page.evaluate(() => performance.timeOrigin)).toBe(1000);
+ expect(await promise).toEqual({ prev: 0, next: 1000 });
+ });
+});
+
it.describe('popup', () => {
it('should tick after popup', async ({ page }) => {
+ await page.clock.install();
+ await page.clock.pause();
const now = new Date('2015-09-25');
- await page.clock.installFakeTimers(now);
+ await page.clock.setSystemTime(now);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(() => window.open('about:blank')),
@@ -584,11 +338,12 @@ it.describe('popup', () => {
expect(popupTimeAfter).toBe(now.getTime() + 1000);
});
- it('should tick before popup', async ({ page, browserName }) => {
+ it('should tick before popup', async ({ page }) => {
+ await page.clock.install();
+ await page.clock.pause();
const now = new Date('2015-09-25');
- await page.clock.installFakeTimers(now);
- const ticks = await page.clock.runFor(1000);
- expect(ticks).toBe(1000);
+ await page.clock.setSystemTime(now);
+ await page.clock.runFor(1000);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
@@ -597,90 +352,47 @@ it.describe('popup', () => {
const popupTime = await popup.evaluate(() => Date.now());
expect(popupTime).toBe(now.getTime() + 1000);
});
-});
-it.describe('runToNextTimer', () => {
- it('triggers the next timer', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- setTimeout(window.stub, 100);
+ it('should run time before popup', async ({ page, server }) => {
+ server.setRoute('/popup.html', async (req, res) => {
+ res.setHeader('Content-Type', 'text/html');
+ res.end(``);
});
- expect(await page.clock.runToNextTimer()).toBe(100);
- expect(calls).toHaveLength(1);
+ await page.clock.setSystemTime(0);
+ await page.goto(server.EMPTY_PAGE);
+ // Wait for 2 second in real life to check that it is past in popup.
+ await page.waitForTimeout(2000);
+ const [popup] = await Promise.all([
+ page.waitForEvent('popup'),
+ page.evaluate(url => window.open(url), server.PREFIX + '/popup.html'),
+ ]);
+ const popupTime = await popup.evaluate('time');
+ expect(popupTime).toBeGreaterThanOrEqual(2000);
});
- it('does not trigger simultaneous timers', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(() => {
- setTimeout(() => {
- window.stub();
- }, 100);
- setTimeout(() => {
- window.stub();
- }, 100);
+ it('should not run time before popup on pause', async ({ page, server }) => {
+ server.setRoute('/popup.html', async (req, res) => {
+ res.setHeader('Content-Type', 'text/html');
+ res.end(``);
});
-
- await page.clock.runToNextTimer();
- expect(calls).toHaveLength(1);
- });
-
- it('subsequent calls trigger simultaneous timers', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- setTimeout(() => {
- window.stub();
- }, 100);
- setTimeout(() => {
- window.stub();
- }, 100);
- setTimeout(() => {
- window.stub();
- }, 99);
- setTimeout(() => {
- window.stub();
- }, 100);
- });
-
- await page.clock.runToNextTimer();
- expect(calls).toHaveLength(1);
- await page.clock.runToNextTimer();
- expect(calls).toHaveLength(2);
- await page.clock.runToNextTimer();
- expect(calls).toHaveLength(3);
- await page.clock.runToNextTimer();
- expect(calls).toHaveLength(4);
- });
-
- it('subsequent calls triggers simultaneous timers with zero callAt', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- window.stub(1);
- setTimeout(() => {
- setTimeout(() => window.stub(2), 0);
- }, 0);
- });
-
- await page.clock.runToNextTimer();
- expect(calls).toEqual([{ params: [1] }]);
- await page.clock.runToNextTimer();
- expect(calls).toEqual([{ params: [1] }, { params: [2] }]);
- });
-
- it('throws exception thrown by timer', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- setTimeout(() => {
- throw new Error();
- }, 100);
- });
-
- await expect(page.clock.runToNextTimer()).rejects.toThrow();
+ await page.clock.install();
+ await page.clock.pause();
+ await page.clock.setSystemTime(0);
+ await page.goto(server.EMPTY_PAGE);
+ // Wait for 2 second in real life to check that it is past in popup.
+ await page.waitForTimeout(2000);
+ const [popup] = await Promise.all([
+ page.waitForEvent('popup'),
+ page.evaluate(url => window.open(url), server.PREFIX + '/popup.html'),
+ ]);
+ const popupTime = await popup.evaluate('time');
+ expect(popupTime).toBe(0);
});
});
-it.describe('setTime', () => {
+it.describe('setFixedTime', () => {
it('does not fake methods', async ({ page }) => {
- await page.clock.setTime(0);
+ await page.clock.setFixedTime(0);
// Should not stall.
await page.evaluate(() => {
@@ -688,54 +400,145 @@ it.describe('setTime', () => {
});
});
- it('allows setting time multiple times', async ({ page, calls }) => {
- await page.clock.setTime(100);
+ it('allows setting time multiple times', async ({ page }) => {
+ await page.clock.setFixedTime(100);
expect(await page.evaluate(() => Date.now())).toBe(100);
- await page.clock.setTime(200);
+ await page.clock.setFixedTime(200);
expect(await page.evaluate(() => Date.now())).toBe(200);
});
- it('supports skipTime w/o fake timers', async ({ page }) => {
- await page.clock.setTime(100);
+ it('fixed time is not affected by clock manipulation', async ({ page }) => {
+ await page.clock.setFixedTime(100);
+ expect(await page.evaluate(() => Date.now())).toBe(100);
+ await page.clock.fastForward(20);
expect(await page.evaluate(() => Date.now())).toBe(100);
- await page.clock.skipTime(20);
- expect(await page.evaluate(() => Date.now())).toBe(120);
});
it('allows installing fake timers after settings time', async ({ page, calls }) => {
- await page.clock.setTime(100);
+ await page.clock.setFixedTime(100);
expect(await page.evaluate(() => Date.now())).toBe(100);
- await page.clock.installFakeTimers(200);
+ await page.clock.setFixedTime(200);
await page.evaluate(async () => {
setTimeout(() => window.stub(Date.now()));
});
await page.clock.runFor(0);
expect(calls).toEqual([{ params: [200] }]);
});
+});
- it('allows setting time after installing fake timers', async ({ page, calls }) => {
- await page.clock.installFakeTimers(200);
- await page.evaluate(async () => {
- setTimeout(() => window.stub(Date.now()));
- });
- await page.clock.setTime(220);
- expect(calls).toEqual([{ params: [220] }]);
+it.describe('while running', () => {
+ it('should progress time', async ({ page }) => {
+ await page.clock.install({ time: 0 });
+ await page.goto('data:text/html,');
+ await page.waitForTimeout(1000);
+ const now = await page.evaluate(() => Date.now());
+ expect(now).toBeGreaterThanOrEqual(1000);
+ expect(now).toBeLessThanOrEqual(2000);
});
- it('does not allow flowing time backwards', async ({ page, calls }) => {
- await page.clock.installFakeTimers(200);
- await expect(page.clock.setTime(180)).rejects.toThrow();
+ it('should runFor', async ({ page }) => {
+ await page.clock.install({ time: 0 });
+ await page.goto('data:text/html,');
+ await page.clock.runFor(10000);
+ const now = await page.evaluate(() => Date.now());
+ expect(now).toBeGreaterThanOrEqual(10000);
+ expect(now).toBeLessThanOrEqual(11000);
});
- it('should turn setTime into jump', async ({ page, calls }) => {
- await page.clock.installFakeTimers(0);
- await page.evaluate(async () => {
- setTimeout(window.stub, 100);
- setTimeout(window.stub, 200);
- });
- await page.clock.setTime(100);
- expect(calls).toHaveLength(1);
- await page.clock.setTime(200);
- expect(calls).toHaveLength(2);
+ it('should fastForward', async ({ page }) => {
+ await page.clock.install({ time: 0 });
+ await page.goto('data:text/html,');
+ await page.clock.fastForward(10000);
+ const now = await page.evaluate(() => Date.now());
+ expect(now).toBeGreaterThanOrEqual(10000);
+ expect(now).toBeLessThanOrEqual(11000);
+ });
+
+ it('should fastForwardTo', async ({ page }) => {
+ await page.clock.install({ time: 0 });
+ await page.goto('data:text/html,');
+ await page.clock.fastForwardTo(10000);
+ const now = await page.evaluate(() => Date.now());
+ expect(now).toBeGreaterThanOrEqual(10000);
+ expect(now).toBeLessThanOrEqual(11000);
+ });
+
+ it('should pause', async ({ page }) => {
+ await page.clock.install({ time: 0 });
+ await page.goto('data:text/html,');
+ await page.clock.pause();
+ await page.waitForTimeout(1000);
+ await page.clock.resume();
+ const now = await page.evaluate(() => Date.now());
+ expect(now).toBeGreaterThanOrEqual(0);
+ expect(now).toBeLessThanOrEqual(1000);
+ });
+
+ it('should pause and fastForwardTo', async ({ page }) => {
+ await page.clock.install({ time: 0 });
+ await page.goto('data:text/html,');
+ await page.clock.pause();
+ await page.clock.fastForwardTo(1000);
+ const now = await page.evaluate(() => Date.now());
+ expect(now).toBe(1000);
+ });
+
+ it('should set system time on pause', async ({ page }) => {
+ await page.clock.install();
+ await page.goto('data:text/html,');
+ await page.clock.pause();
+ await page.clock.setSystemTime(1000);
+ const now = await page.evaluate(() => Date.now());
+ expect(now).toBe(1000);
+ });
+});
+
+it.describe('while on pause', () => {
+ it('fastForward should not run nested immediate', async ({ page, calls }) => {
+ await page.clock.install();
+ await page.goto('data:text/html,');
+ await page.clock.pause();
+ await page.evaluate(() => {
+ setTimeout(() => {
+ window.stub('outer');
+ setTimeout(() => window.stub('inner'), 0);
+ }, 1000);
+ });
+ await page.clock.fastForward(1000);
+ expect(calls).toEqual([{ params: ['outer'] }]);
+ await page.clock.fastForward(1);
+ expect(calls).toEqual([{ params: ['outer'] }, { params: ['inner'] }]);
+ });
+
+ it('runFor should not run nested immediate', async ({ page, calls }) => {
+ await page.clock.install();
+ await page.goto('data:text/html,');
+ await page.clock.pause();
+ await page.evaluate(() => {
+ setTimeout(() => {
+ window.stub('outer');
+ setTimeout(() => window.stub('inner'), 0);
+ }, 1000);
+ });
+ await page.clock.runFor(1000);
+ expect(calls).toEqual([{ params: ['outer'] }]);
+ await page.clock.runFor(1);
+ expect(calls).toEqual([{ params: ['outer'] }, { params: ['inner'] }]);
+ });
+
+ it('runFor should not run nested immediate from microtask', async ({ page, calls }) => {
+ await page.clock.install();
+ await page.goto('data:text/html,');
+ await page.clock.pause();
+ await page.evaluate(() => {
+ setTimeout(() => {
+ window.stub('outer');
+ void Promise.resolve().then(() => setTimeout(() => window.stub('inner'), 0));
+ }, 1000);
+ });
+ await page.clock.runFor(1000);
+ expect(calls).toEqual([{ params: ['outer'] }]);
+ await page.clock.runFor(1);
+ expect(calls).toEqual([{ params: ['outer'] }, { params: ['inner'] }]);
});
});