Skip to content

Commit

Permalink
chore(clock): introduce pauseAt (#31255)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman authored Jun 11, 2024
1 parent 8fd0a56 commit 2b257ea
Show file tree
Hide file tree
Showing 13 changed files with 151 additions and 195 deletions.
80 changes: 40 additions & 40 deletions docs/src/api/class-clock.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,43 +45,6 @@ await page.Clock.FastForwardAsync("30:00");

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

Expand Down Expand Up @@ -145,10 +108,47 @@ await page.Clock.RunForAsync("30:00");
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.pause
## async method: Clock.pauseAt
* since: v1.45

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.
Advance the clock by jumping forward in time and pause the time. Once this method is called, no timers
are fired unless [`method: Clock.runFor`], [`method: Clock.fastForward`], [`method: Clock.pauseAt`] or [`method: Clock.resume`] is called.

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 and
pausing.

**Usage**

```js
await page.clock.pauseAt(new Date('2020-02-02'));
await page.clock.pauseAt('2020-02-02');
```

```python async
await page.clock.pause_at(datetime.datetime(2020, 2, 2))
await page.clock.pause_at("2020-02-02")
```

```python sync
page.clock.pause_at(datetime.datetime(2020, 2, 2))
page.clock.pause_at("2020-02-02")
```

```java
page.clock().pauseAt(Instant.parse("2020-02-02"));
page.clock().pauseAt("2020-02-02");
```

```csharp
await page.Clock.PauseAtAsync(DateTime.Parse("2020-02-02"));
await page.Clock.PauseAtAsync("2020-02-02");
```

### param: Clock.pauseAt.time
* since: v1.45
- `time` <[int]|[string]|[Date]>


## async method: Clock.resume
* since: v1.45
Expand Down Expand Up @@ -202,7 +202,7 @@ 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`].
Sets current system time but does not trigger any timers.

**Usage**

Expand Down
42 changes: 16 additions & 26 deletions docs/src/clock.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,9 @@ In this case, you can install the clock and fast forward to the time of interest
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'));
// Pretend that the user closed the laptop lid and opened it again at 10am,
// Pause the time once reached that point.
await page.clock.pauseAt(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');
Expand All @@ -86,10 +85,9 @@ await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM
await page.clock.install(time=datetime.datetime(2024, 2, 2, 8, 0, 0))
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.fast_forward_to(datetime.datetime(2024, 2, 2, 10, 0, 0))
# Pause the time once reached that point.
await page.clock.pause_at(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")
Expand All @@ -105,10 +103,9 @@ await expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:
page.clock.install(time=datetime.datetime(2024, 2, 2, 8, 0, 0))
page.goto("http://localhost:3333")

# 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))
# Pause the time once reached that point.
page.clock.pause_at(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")
Expand All @@ -125,10 +122,9 @@ page.clock().install(new Clock.InstallOptions().setTime(Instant.parse("2024-02-0
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"));
// Pause the time once reached that point.
page.clock().pauseAt(Instant.parse("2024-02-02T10:00:00"));

// Assert the page state.
assertThat(locator).hasText("2/2/2024, 10:00:00 AM");
Expand All @@ -147,10 +143,9 @@ await Page.Clock.InstallAsync(new
});
await Page.GotoAsync("http://localhost:3333");

// 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));
// Pause the time once reached that point.
await Page.Clock.PauseAtAsync(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");
Expand Down Expand Up @@ -272,8 +267,7 @@ 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 page.clock.pauseAt(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.
Expand All @@ -292,8 +286,7 @@ 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 page.clock.pause_at(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.
Expand All @@ -312,8 +305,7 @@ 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))
page.clock.pause_at(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.
Expand All @@ -331,8 +323,7 @@ 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"));
page.clock().pauseAt(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.
Expand All @@ -352,8 +343,7 @@ 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 Page.Clock.PauseAtAsync(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.
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/client/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
if (!forReuse && !!process.env.PW_FREEZE_TIME) {
await this._wrapApiCall(async () => {
await context.clock.install({ time: 0 });
await context.clock.pause();
await context.clock.pauseAt(1000);
}, true);
}
return context;
Expand Down
8 changes: 2 additions & 6 deletions packages/playwright-core/src/client/clock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,8 @@ export class Clock implements api.Clock {
await this._browserContext._channel.clockFastForward(parseTicks(ticks));
}

async fastForwardTo(time: number | string | Date) {
await this._browserContext._channel.clockFastForwardTo(parseTime(time));
}

async pause() {
await this._browserContext._channel.clockPause({});
async pauseAt(time: number | string | Date) {
await this._browserContext._channel.clockPauseAt(parseTime(time));
}

async resume() {
Expand Down
10 changes: 4 additions & 6 deletions packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -968,18 +968,16 @@ scheme.BrowserContextClockFastForwardParams = tObject({
ticksString: tOptional(tString),
});
scheme.BrowserContextClockFastForwardResult = tOptional(tObject({}));
scheme.BrowserContextClockFastForwardToParams = tObject({
scheme.BrowserContextClockInstallParams = tObject({
timeNumber: tOptional(tNumber),
timeString: tOptional(tString),
});
scheme.BrowserContextClockFastForwardToResult = tOptional(tObject({}));
scheme.BrowserContextClockInstallParams = tObject({
scheme.BrowserContextClockInstallResult = tOptional(tObject({}));
scheme.BrowserContextClockPauseAtParams = tObject({
timeNumber: tOptional(tNumber),
timeString: tOptional(tString),
});
scheme.BrowserContextClockInstallResult = tOptional(tObject({}));
scheme.BrowserContextClockPauseParams = tOptional(tObject({}));
scheme.BrowserContextClockPauseResult = tOptional(tObject({}));
scheme.BrowserContextClockPauseAtResult = tOptional(tObject({}));
scheme.BrowserContextClockResumeParams = tOptional(tObject({}));
scheme.BrowserContextClockResumeResult = tOptional(tObject({}));
scheme.BrowserContextClockRunForParams = tObject({
Expand Down
14 changes: 4 additions & 10 deletions packages/playwright-core/src/server/clock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,18 @@ export class Clock {
await this._evaluateInFrames(`globalThis.__pwClock.controller.fastForward(${ticksMillis})`);
}

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 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 pause() {
async pauseAt(ticks: number | string) {
await this._installIfNeeded();
await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('pause', ${Date.now()})`);
await this._evaluateInFrames(`globalThis.__pwClock.controller.pause()`);
const timeMillis = parseTime(ticks);
await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('pauseAt', ${Date.now()}, ${timeMillis})`);
await this._evaluateInFrames(`globalThis.__pwClock.controller.pauseAt(${timeMillis})`);
}

async resume() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,16 +316,12 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
await this._context.clock.fastForward(params.ticksString ?? params.ticksNumber ?? 0);
}

async clockFastForwardTo(params: channels.BrowserContextClockFastForwardToParams, metadata?: CallMetadata | undefined): Promise<channels.BrowserContextClockFastForwardToResult> {
await this._context.clock.fastForwardTo(params.timeString ?? params.timeNumber ?? 0);
}

async clockInstall(params: channels.BrowserContextClockInstallParams, metadata?: CallMetadata | undefined): Promise<channels.BrowserContextClockInstallResult> {
await this._context.clock.install(params.timeString ?? params.timeNumber ?? undefined);
}

async clockPause(params: channels.BrowserContextClockPauseParams, metadata?: CallMetadata | undefined): Promise<channels.BrowserContextClockPauseResult> {
await this._context.clock.pause();
async clockPauseAt(params: channels.BrowserContextClockPauseAtParams, metadata?: CallMetadata | undefined): Promise<channels.BrowserContextClockPauseAtResult> {
await this._context.clock.pauseAt(params.timeString ?? params.timeNumber ?? 0);
}

async clockResume(params: channels.BrowserContextClockResumeParams, metadata?: CallMetadata | undefined): Promise<channels.BrowserContextClockResumeResult> {
Expand Down
32 changes: 16 additions & 16 deletions packages/playwright-core/src/server/injected/clock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ type Time = {
origin: number;
};

type LogEntryType = 'fastForward' | 'fastForwardTo' | 'install' | 'pause' | 'resume' | 'runFor' | 'setFixedTime' | 'setSystemTime';
type LogEntryType = 'fastForward' |'install' | 'pauseAt' | 'resume' | 'runFor' | 'setFixedTime' | 'setSystemTime';

export class ClockController {
readonly _now: Time;
Expand Down Expand Up @@ -163,9 +163,10 @@ export class ClockController {
throw firstException;
}

pause() {
async pauseAt(time: number) {
this._replayLogOnce();
this._innerPause();
await this._innerFastForwardTo(time);
}

private _innerPause() {
Expand Down Expand Up @@ -218,18 +219,18 @@ export class ClockController {

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;
}
await this.runFor(ms);
await this._innerFastForwardTo(this._now.ticks + ticks | 0);
}

async fastForwardTo(time: number) {
this._replayLogOnce();
const ticks = time - this._now.time;
await this.fastForward(ticks);

private async _innerFastForwardTo(toTicks: number) {
if (toTicks < this._now.ticks)
throw new Error('Cannot fast-forward to the past');
for (const timer of this._timers.values()) {
if (toTicks > timer.callAt)
timer.callAt = toTicks;
}
await this._runTo(toTicks);
}

addTimer(options: { func: TimerHandler, type: TimerType, delay?: number | string, args?: any[] }): number {
Expand Down Expand Up @@ -381,11 +382,10 @@ export class ClockController {
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();
} else if (type === 'pauseAt') {
isPaused = true;
this._innerPause();
this._innerSetTime(param!);
} else if (type === 'resume') {
this._innerResume();
isPaused = false;
Expand Down
Loading

0 comments on commit 2b257ea

Please sign in to comment.