chore: rename fakeTimers to clock (#31193)

This commit is contained in:
Pavel Feldman 2024-06-06 15:56:13 -07:00 committed by GitHub
parent 3e86ebc80c
commit 826343b8a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 3223 additions and 223 deletions

View File

@ -102,7 +102,7 @@ context.BackgroundPage += (_, backgroundPage) =>
* since: v1.45
- type: <[Clock]>
Playwright is using [@sinonjs/fake-timers](https://github.com/sinonjs/fake-timers) to fake timers and clock.
Playwright has ability to mock clock and passage of time.
## event: BrowserContext.close
* since: v1.8

View File

@ -155,7 +155,7 @@ page.Load -= PageLoadHandler;
* since: v1.45
- type: <[Clock]>
Playwright is using [@sinonjs/fake-timers](https://github.com/sinonjs/fake-timers) to fake timers and clock.
Playwright has ability to mock clock and passage of time.
## event: Page.close
* since: v1.8

View File

@ -15,12 +15,12 @@
*/
import type { BrowserContext } from './browserContext';
import * as fakeTimersSource from '../generated/fakeTimersSource';
import * as clockSource from '../generated/clockSource';
export class Clock {
private _browserContext: BrowserContext;
private _scriptInjected = false;
private _fakeTimersInstalled = false;
private _clockInstalled = false;
private _now = 0;
constructor(browserContext: BrowserContext) {
@ -30,48 +30,48 @@ export class Clock {
async installFakeTimers(time: number, loopLimit: number | undefined) {
await this._injectScriptIfNeeded();
await this._addAndEvaluate(`(() => {
globalThis.__pwFakeTimers.clock?.uninstall();
globalThis.__pwFakeTimers.clock = globalThis.__pwFakeTimers.install(${JSON.stringify({ now: time, loopLimit })});
globalThis.__pwClock.clock?.uninstall();
globalThis.__pwClock.clock = globalThis.__pwClock.install(${JSON.stringify({ now: time, loopLimit })});
})();`);
this._now = time;
this._fakeTimersInstalled = true;
this._clockInstalled = true;
}
async runToNextTimer(): Promise<number> {
this._assertInstalled();
await this._browserContext.addInitScript(`globalThis.__pwFakeTimers.clock.next()`);
this._now = await this._evaluateInFrames(`globalThis.__pwFakeTimers.clock.nextAsync()`);
await this._browserContext.addInitScript(`globalThis.__pwClock.clock.next()`);
this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.nextAsync()`);
return this._now;
}
async runAllTimers(): Promise<number> {
this._assertInstalled();
await this._browserContext.addInitScript(`globalThis.__pwFakeTimers.clock.runAll()`);
this._now = await this._evaluateInFrames(`globalThis.__pwFakeTimers.clock.runAllAsync()`);
await this._browserContext.addInitScript(`globalThis.__pwClock.clock.runAll()`);
this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.runAllAsync()`);
return this._now;
}
async runToLastTimer(): Promise<number> {
this._assertInstalled();
await this._browserContext.addInitScript(`globalThis.__pwFakeTimers.clock.runToLast()`);
this._now = await this._evaluateInFrames(`globalThis.__pwFakeTimers.clock.runToLastAsync()`);
await this._browserContext.addInitScript(`globalThis.__pwClock.clock.runToLast()`);
this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.runToLastAsync()`);
return this._now;
}
async setTime(time: number) {
if (this._fakeTimersInstalled) {
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.__pwFakeTimers.clock.jump(${jump})`);
await this._addAndEvaluate(`globalThis.__pwClock.clock.jump(${jump})`);
this._now = time;
return this._now;
}
await this._injectScriptIfNeeded();
await this._addAndEvaluate(`(() => {
globalThis.__pwFakeTimers.clock?.uninstall();
globalThis.__pwFakeTimers.clock = globalThis.__pwFakeTimers.install(${JSON.stringify({ now: time, toFake: ['Date'] })});
globalThis.__pwClock.clock?.uninstall();
globalThis.__pwClock.clock = globalThis.__pwClock.install(${JSON.stringify({ now: time, toFake: ['Date'] })});
})();`);
this._now = time;
return this._now;
@ -85,8 +85,8 @@ export class Clock {
async runFor(time: number | string): Promise<number> {
this._assertInstalled();
await this._browserContext.addInitScript(`globalThis.__pwFakeTimers.clock.tick(${JSON.stringify(time)})`);
this._now = await this._evaluateInFrames(`globalThis.__pwFakeTimers.clock.tickAsync(${JSON.stringify(time)})`);
await this._browserContext.addInitScript(`globalThis.__pwClock.clock.tick(${JSON.stringify(time)})`);
this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.tickAsync(${JSON.stringify(time)})`);
return this._now;
}
@ -96,8 +96,8 @@ export class Clock {
this._scriptInjected = true;
const script = `(() => {
const module = {};
${fakeTimersSource.source}
globalThis.__pwFakeTimers = (module.exports.inject())(globalThis);
${clockSource.source}
globalThis.__pwClock = (module.exports.inject())(globalThis);
})();`;
await this._addAndEvaluate(script);
}
@ -114,7 +114,7 @@ export class Clock {
}
private _assertInstalled() {
if (!this._fakeTimersInstalled)
if (!this._clockInstalled)
throw new Error('Clock is not installed');
}
}

View File

@ -10,7 +10,7 @@
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
type ClockMethods = {
export type ClockMethods = {
Date: DateConstructor;
setTimeout: Window['setTimeout'];
clearTimeout: Window['clearTimeout'];
@ -24,12 +24,12 @@ type ClockMethods = {
performance?: Window['performance'];
};
type ClockConfig = {
export type ClockConfig = {
now?: number | Date;
loopLimit?: number;
};
type InstallConfig = ClockConfig & {
export type InstallConfig = ClockConfig & {
toFake?: (keyof ClockMethods)[];
};
@ -44,7 +44,7 @@ enum TimerType {
type Timer = {
type: TimerType;
func: TimerHandler;
args: any[];
args: () => any[];
delay: number;
callAt: number;
createdAt: number;
@ -57,15 +57,13 @@ interface Embedder {
postTaskPeriodically(task: () => void, delay: number): () => void;
}
class Clock {
export class ClockController {
readonly start: number;
private _now: number;
private _loopLimit: number;
private _jobs: Timer[] = [];
private _adjustedSystemTime = 0;
private _duringTick = false;
private _timers = new Map<number, Timer>();
private _isNearInfiniteLimit = false;
private _uniqueTimerId = idCounterStart;
private _embedder: Embedder;
readonly disposables: (() => void)[] = [];
@ -88,10 +86,7 @@ class Clock {
}
performanceNow(): DOMHighResTimeStamp {
const millisSinceStart = this._now - this._adjustedSystemTime - this.start;
const secsSinceStart = Math.floor(millisSinceStart / 1000);
const millis = secsSinceStart * 1000;
return millis;
return this._now - this._adjustedSystemTime - this.start;
}
private _doTick(tickValue: number | string, isAsync: boolean, resolve?: (time: number) => void, reject?: (error: Error) => void): number | undefined {
@ -116,13 +111,10 @@ class Clock {
let compensationCheck: () => void;
let postTimerCall: () => void;
/* eslint-enable prefer-const */
this._duringTick = true;
// perform microtasks
oldNow = this._now;
this._runJobs();
if (oldNow !== this._now) {
// compensate for any setSystemTime() call during microtask callback
tickFrom += this._now - oldNow;
@ -138,7 +130,6 @@ class Clock {
this._now = timer.callAt;
oldNow = this._now;
try {
this._runJobs();
this._callTimer(timer);
} catch (e) {
firstException = firstException || e;
@ -158,7 +149,6 @@ class Clock {
// perform process.nextTick()s again
oldNow = this._now;
this._runJobs();
if (oldNow !== this._now) {
// compensate for any setSystemTime() call during process.nextTick() callback
tickFrom += this._now - oldNow;
@ -220,20 +210,12 @@ class Clock {
return this._doTick(tickValue, false)!;
}
tickAsync(tickValue: string | number): Promise<number> {
return new Promise<number>((resolve, reject) => {
this._embedder.postTask(() => {
try {
this._doTick(tickValue, true, resolve, reject);
} catch (e) {
reject(e);
}
});
});
async tickAsync(tickValue: string | number): Promise<number> {
await new Promise<void>(f => this._embedder.postTask(f));
return new Promise((resolve, reject) => this._doTick(tickValue, true, resolve, reject));
}
next() {
this._runJobs();
const timer = this._firstTimer();
if (!timer)
return this._now;
@ -242,117 +224,73 @@ class Clock {
try {
this._now = timer.callAt;
this._callTimer(timer);
this._runJobs();
return this._now;
} finally {
this._duringTick = false;
}
}
nextAsync() {
return new Promise<number>((resolve, reject) => {
this._embedder.postTask(() => {
try {
const timer = this._firstTimer();
if (!timer) {
resolve(this._now);
return;
}
async nextAsync() {
await new Promise<void>(f => this._embedder.postTask(f));
const timer = this._firstTimer();
if (!timer)
return this._now;
let err: Error;
this._duringTick = true;
this._now = timer.callAt;
try {
this._callTimer(timer);
} catch (e) {
err = e;
}
this._duringTick = false;
let err: Error | undefined;
this._duringTick = true;
this._now = timer.callAt;
try {
this._callTimer(timer);
} catch (e) {
err = e;
}
this._duringTick = false;
this._embedder.postTask(() => {
if (err)
reject(err);
else
resolve(this._now);
});
} catch (e) {
reject(e);
}
});
});
await new Promise<void>(f => this._embedder.postTask(f));
if (err)
throw err;
return this._now;
}
runAll() {
this._runJobs();
for (let i = 0; i < this._loopLimit; i++) {
const numTimers = this._timers.size;
if (numTimers === 0) {
this._resetIsNearInfiniteLimit();
if (numTimers === 0)
return this._now;
}
this.next();
this._checkIsNearInfiniteLimit(i);
}
const excessJob = this._firstTimer();
throw this._getInfiniteLoopError(excessJob!);
if (!excessJob)
return;
throw this._getInfiniteLoopError(excessJob);
}
runToFrame() {
return this.tick(this.getTimeToNextFrame());
}
runAllAsync() {
return new Promise<number>((resolve, reject) => {
let i = 0;
/**
*
*/
const doRun = () => {
this._embedder.postTask(() => {
try {
this._runJobs();
async runAllAsync() {
for (let i = 0; i < this._loopLimit; i++) {
await new Promise<void>(f => this._embedder.postTask(f));
const numTimers = this._timers.size;
if (numTimers === 0)
return this._now;
let numTimers;
if (i < this._loopLimit) {
if (!this._timers) {
this._resetIsNearInfiniteLimit();
resolve(this._now);
return;
}
this.next();
}
await new Promise<void>(f => this._embedder.postTask(f));
numTimers = this._timers.size;
if (numTimers === 0) {
this._resetIsNearInfiniteLimit();
resolve(this._now);
return;
}
this.next();
i++;
doRun();
this._checkIsNearInfiniteLimit(i);
return;
}
const excessJob = this._firstTimer();
reject(this._getInfiniteLoopError(excessJob!));
} catch (e) {
reject(e);
}
});
};
doRun();
});
const excessJob = this._firstTimer();
if (!excessJob)
return;
throw this._getInfiniteLoopError(excessJob);
}
runToLast() {
const timer = this._lastTimer();
if (!timer) {
this._runJobs();
if (!timer)
return this._now;
}
return this.tick(timer.callAt - this._now);
}
@ -362,7 +300,6 @@ class Clock {
try {
const timer = this._lastTimer();
if (!timer) {
this._runJobs();
resolve(this._now);
return;
}
@ -376,7 +313,6 @@ class Clock {
reset() {
this._timers.clear();
this._jobs = [];
this._now = this.start;
}
@ -410,34 +346,7 @@ class Clock {
return this.tick(ms);
}
private _checkIsNearInfiniteLimit(i: number): void {
if (this._loopLimit && i === this._loopLimit - 1)
this._isNearInfiniteLimit = true;
}
private _resetIsNearInfiniteLimit() {
this._isNearInfiniteLimit = false;
}
private _runJobs() {
// runs all microtick-deferred tasks - ecma262/#sec-runjobs
if (!this._jobs)
return;
for (let i = 0; i < this._jobs.length; i++) {
const job = this._jobs[i];
callFunction(job.func, job.args);
this._checkIsNearInfiniteLimit(i);
if (this._loopLimit && i > this._loopLimit)
throw this._getInfiniteLoopError(job);
}
this._resetIsNearInfiniteLimit();
this._jobs = [];
}
addTimer(options: { func: TimerHandler, type: TimerType, delay?: number | string, args?: any[] }): number {
addTimer(options: { func: TimerHandler, type: TimerType, delay?: number | string, args?: () => any[] }): number {
if (options.func === undefined)
throw new Error('Callback must be provided to timer calls');
@ -450,12 +359,12 @@ class Clock {
const timer: Timer = {
type: options.type,
func: options.func,
args: options.args || [],
args: options.args || (() => []),
delay,
callAt: this._now + (delay || (this._duringTick ? 1 : 0)),
createdAt: this._now,
id: this._uniqueTimerId++,
error: this._isNearInfiniteLimit ? new Error() : undefined,
error: new Error(),
};
this._timers.set(timer.id, timer);
return timer.id;
@ -472,7 +381,7 @@ class Clock {
}
countTimers() {
return this._timers.size + this._jobs.length;
return this._timers.size;
}
private _firstTimer(): Timer | null {
@ -500,7 +409,7 @@ class Clock {
this._timers.get(timer.id)!.callAt += timer.delay;
else
this._timers.delete(timer.id);
callFunction(timer.func, timer.args);
callFunction(timer.func, timer.args());
}
private _getInfiniteLoopError(job: Timer) {
@ -548,14 +457,7 @@ class Clock {
.slice(matchedLineIndex + 1)
.join('\n')}`;
try {
Object.defineProperty(infiniteLoopError, 'stack', {
value: stack,
});
} catch (e) {
// noop
}
infiniteLoopError.stack = stack;
return infiniteLoopError;
}
@ -661,7 +563,7 @@ function mirrorDateProperties(target: any, source: typeof Date): DateConstructor
return target;
}
function createDate(clock: Clock, NativeDate: typeof Date): DateConstructor & Date {
function createDate(clock: ClockController, NativeDate: typeof Date): DateConstructor & Date {
function ClockDate(this: typeof ClockDate, year: number, month: number, date: number, hour: number, minute: number, second: number, ms: number): Date | string {
// the Date constructor called as a function, ref Ecma-262 Edition 5.1, section 15.9.2.
// This remains so in the 10th edition of 2019 as well.
@ -717,7 +619,7 @@ function createDate(clock: Clock, NativeDate: typeof Date): DateConstructor & Da
* but we need to take control of those that have a
* dependency on the current clock.
*/
function createIntl(clock: Clock, NativeIntl: typeof Intl): typeof Intl {
function createIntl(clock: ClockController, NativeIntl: typeof Intl): typeof Intl {
const ClockIntl: any = {};
/*
* All properties of Intl are non-enumerable, so we need
@ -726,7 +628,7 @@ function createIntl(clock: Clock, NativeIntl: typeof Intl): typeof Intl {
for (const key of Object.keys(NativeIntl) as (keyof typeof Intl)[])
ClockIntl[key] = NativeIntl[key];
ClockIntl.DateTimeFormat = (...args: any[]) => {
ClockIntl.DateTimeFormat = function(...args: any[]) {
const realFormatter = new NativeIntl.DateTimeFormat(...args);
const formatter: Intl.DateTimeFormat = {
formatRange: realFormatter.formatRange.bind(realFormatter),
@ -787,20 +689,26 @@ function callFunction(func: TimerHandler, args: any[]) {
const maxTimeout = Math.pow(2, 31) - 1; // see https://heycam.github.io/webidl/#abstract-opdef-converttoint
const idCounterStart = 1e12; // arbitrarily large number to avoid collisions with native timer IDs
function platformOriginals(globalObject: WindowOrWorkerGlobalScope): ClockMethods {
return {
setTimeout: globalObject.setTimeout.bind(globalObject),
clearTimeout: globalObject.clearTimeout.bind(globalObject),
setInterval: globalObject.setInterval.bind(globalObject),
clearInterval: globalObject.clearInterval.bind(globalObject),
requestAnimationFrame: (globalObject as any).requestAnimationFrame ? (globalObject as any).requestAnimationFrame.bind(globalObject) : undefined,
cancelAnimationFrame: (globalObject as any).cancelAnimationFrame ? (globalObject as any).cancelAnimationFrame.bind(globalObject) : undefined,
requestIdleCallback: (globalObject as any).requestIdleCallback ? (globalObject as any).requestIdleCallback.bind(globalObject) : undefined,
cancelIdleCallback: (globalObject as any).cancelIdleCallback ? (globalObject as any).cancelIdleCallback.bind(globalObject) : undefined,
function platformOriginals(globalObject: WindowOrWorkerGlobalScope): { raw: ClockMethods, bound: ClockMethods } {
const raw: ClockMethods = {
setTimeout: globalObject.setTimeout,
clearTimeout: globalObject.clearTimeout,
setInterval: globalObject.setInterval,
clearInterval: globalObject.clearInterval,
requestAnimationFrame: (globalObject as any).requestAnimationFrame ? (globalObject as any).requestAnimationFrame : undefined,
cancelAnimationFrame: (globalObject as any).cancelAnimationFrame ? (globalObject as any).cancelAnimationFrame : undefined,
requestIdleCallback: (globalObject as any).requestIdleCallback ? (globalObject as any).requestIdleCallback : undefined,
cancelIdleCallback: (globalObject as any).cancelIdleCallback ? (globalObject as any).cancelIdleCallback : undefined,
Date: (globalObject as any).Date,
performance: globalObject.performance,
Intl: (globalObject as any).Intl,
};
const bound = { ...raw };
for (const key of Object.keys(bound) as (keyof ClockMethods)[]) {
if (key !== 'Date' && typeof bound[key] === 'function')
bound[key] = (bound[key] as any).bind(globalObject);
}
return { raw, bound };
}
/**
@ -813,14 +721,14 @@ function getScheduleHandler(type: TimerType) {
return `set${type}`;
}
function createApi(clock: Clock, originals: ClockMethods): ClockMethods {
function createApi(clock: ClockController, originals: ClockMethods): ClockMethods {
return {
setTimeout: (func: TimerHandler, timeout?: number | undefined, ...args: any[]) => {
const delay = timeout ? +timeout : timeout;
return clock.addTimer({
type: TimerType.Timeout,
func,
args,
args: () => args,
delay
});
},
@ -833,7 +741,7 @@ function createApi(clock: Clock, originals: ClockMethods): ClockMethods {
return clock.addTimer({
type: TimerType.Interval,
func,
args,
args: () => args,
delay,
});
},
@ -846,9 +754,7 @@ function createApi(clock: Clock, originals: ClockMethods): ClockMethods {
type: TimerType.AnimationFrame,
func: callback,
delay: clock.getTimeToNextFrame(),
get args() {
return [clock.performanceNow()];
},
args: () => [clock.performanceNow()],
});
},
cancelAnimationFrame: (timerId: number): void => {
@ -863,7 +769,7 @@ function createApi(clock: Clock, originals: ClockMethods): ClockMethods {
return clock.addTimer({
type: TimerType.IdleCallback,
func: callback,
args: [],
args: () => [],
delay: options?.timeout ? Math.min(options?.timeout, timeToNextIdlePeriod) : timeToNextIdlePeriod,
});
},
@ -884,33 +790,41 @@ function getClearHandler(type: TimerType) {
return `clear${type}`;
}
function fakePerformance(clock: Clock, performance: Performance): Performance {
function fakePerformance(clock: ClockController, performance: Performance): Performance {
const result: any = {
now: () => clock.performanceNow(),
timeOrigin: clock.start,
__proto__: performance,
};
// eslint-disable-next-line no-proto
for (const key of Object.keys((performance as any).__proto__)) {
if (key === 'now' || key === 'timeOrigin')
continue;
if (key === 'getEntries' || key === 'getEntriesByName' || key === 'getEntriesByType')
result[key] = () => [];
else
result[key] = () => {};
}
return result;
}
export function createClock(globalObject: WindowOrWorkerGlobalScope, config: ClockConfig = {}): { clock: Clock, api: Partial<ClockMethods>, originals: Partial<ClockMethods> } {
export function createClock(globalObject: WindowOrWorkerGlobalScope, config: ClockConfig = {}): { clock: ClockController, api: ClockMethods, originals: ClockMethods } {
const originals = platformOriginals(globalObject);
const embedder = {
postTask: (task: () => void) => {
originals.setTimeout!(task, 0);
originals.bound.setTimeout(task, 0);
},
postTaskPeriodically: (task: () => void, delay: number) => {
const intervalId = globalObject.setInterval(task, delay);
return () => originals.clearInterval!(intervalId);
return () => originals.bound.clearInterval(intervalId);
},
};
const clock = new Clock(embedder, config.now, config.loopLimit);
const api = createApi(clock, originals);
return { clock, api, originals };
const clock = new ClockController(embedder, config.now, config.loopLimit);
const api = createApi(clock, originals.bound);
return { clock, api, originals: originals.raw };
}
export function install(globalObject: WindowOrWorkerGlobalScope, config: InstallConfig = {}): { clock: Clock, api: Partial<ClockMethods>, originals: Partial<ClockMethods> } {
export function install(globalObject: WindowOrWorkerGlobalScope, config: InstallConfig = {}): { clock: ClockController, api: ClockMethods, originals: ClockMethods } {
if ((globalObject as any).Date?.isFake) {
// Timers are already faked; this is a problem.
// Make the user reset timers before continuing.
@ -946,6 +860,6 @@ export function inject(globalObject: WindowOrWorkerGlobalScope) {
const { clock } = install(globalObject, config);
return clock;
},
builtin: platformOriginals(globalObject),
builtin: platformOriginals(globalObject).bound,
};
}

View File

@ -125,14 +125,14 @@ export class InjectedScript {
}
builtinSetTimeout(callback: Function, timeout: number) {
if (this.window.__pwFakeTimers?.builtin)
return this.window.__pwFakeTimers.builtin.setTimeout(callback, timeout);
if (this.window.__pwClock?.builtin)
return this.window.__pwClock.builtin.setTimeout(callback, timeout);
return setTimeout(callback, timeout);
}
builtinRequestAnimationFrame(callback: FrameRequestCallback) {
if (this.window.__pwFakeTimers?.builtin)
return this.window.__pwFakeTimers.builtin.requestAnimationFrame(callback);
if (this.window.__pwClock?.builtin)
return this.window.__pwClock.builtin.requestAnimationFrame(callback);
return requestAnimationFrame(callback);
}
@ -1525,7 +1525,7 @@ function deepEquals(a: any, b: any): boolean {
declare global {
interface Window {
__pwFakeTimers?: {
__pwClock?: {
builtin: {
setTimeout: Window['setTimeout'],
requestAnimationFrame: Window['requestAnimationFrame'],

View File

@ -79,42 +79,42 @@ export class UtilityScript {
// eslint-disable-next-line no-restricted-globals
const window = (globalThis as any);
window.builtinSetTimeout = (callback: Function, timeout: number) => {
if (window.__pwFakeTimers?.builtin)
return window.__pwFakeTimers.builtin.setTimeout(callback, timeout);
if (window.__pwClock?.builtin)
return window.__pwClock.builtin.setTimeout(callback, timeout);
return setTimeout(callback, timeout);
};
window.builtinClearTimeout = (id: number) => {
if (window.__pwFakeTimers?.builtin)
return window.__pwFakeTimers.builtin.clearTimeout(id);
if (window.__pwClock?.builtin)
return window.__pwClock.builtin.clearTimeout(id);
return clearTimeout(id);
};
window.builtinSetInterval = (callback: Function, timeout: number) => {
if (window.__pwFakeTimers?.builtin)
return window.__pwFakeTimers.builtin.setInterval(callback, timeout);
if (window.__pwClock?.builtin)
return window.__pwClock.builtin.setInterval(callback, timeout);
return setInterval(callback, timeout);
};
window.builtinClearInterval = (id: number) => {
if (window.__pwFakeTimers?.builtin)
return window.__pwFakeTimers.builtin.clearInterval(id);
if (window.__pwClock?.builtin)
return window.__pwClock.builtin.clearInterval(id);
return clearInterval(id);
};
window.builtinRequestAnimationFrame = (callback: FrameRequestCallback) => {
if (window.__pwFakeTimers?.builtin)
return window.__pwFakeTimers.builtin.requestAnimationFrame(callback);
if (window.__pwClock?.builtin)
return window.__pwClock.builtin.requestAnimationFrame(callback);
return requestAnimationFrame(callback);
};
window.builtinCancelAnimationFrame = (id: number) => {
if (window.__pwFakeTimers?.builtin)
return window.__pwFakeTimers.builtin.cancelAnimationFrame(id);
if (window.__pwClock?.builtin)
return window.__pwClock.builtin.cancelAnimationFrame(id);
return cancelAnimationFrame(id);
};
window.builtinDate = window.__pwFakeTimers?.builtin.Date || Date;
window.builtinPerformance = window.__pwFakeTimers?.builtin.performance || performance;
window.builtinDate = window.__pwClock?.builtin.Date || Date;
window.builtinPerformance = window.__pwClock?.builtin.performance || performance;
}
}

View File

@ -4864,7 +4864,7 @@ export interface Page {
accessibility: Accessibility;
/**
* Playwright is using [@sinonjs/fake-timers](https://github.com/sinonjs/fake-timers) to fake timers and clock.
* Playwright has ability to mock clock and passage of time.
*/
clock: Clock;
@ -8986,7 +8986,7 @@ export interface BrowserContext {
/**
* Playwright is using [@sinonjs/fake-timers](https://github.com/sinonjs/fake-timers) to fake timers and clock.
* Playwright has ability to mock clock and passage of time.
*/
clock: Clock;

3086
tests/library/clock.spec.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -51,7 +51,7 @@ const injectedScripts = [
true,
],
[
path.join(ROOT, 'packages', 'playwright-core', 'src', 'server', 'injected', 'fakeTimers.ts'),
path.join(ROOT, 'packages', 'playwright-core', 'src', 'server', 'injected', 'clock.ts'),
path.join(ROOT, 'packages', 'playwright-core', 'lib', 'server', 'injected', 'packed'),
path.join(ROOT, 'packages', 'playwright-core', 'src', 'generated'),
true,