mirror of
https://github.com/microsoft/playwright.git
synced 2024-09-11 20:37:54 +03:00
chore: rename fakeTimers to clock (#31193)
This commit is contained in:
parent
3e86ebc80c
commit
826343b8a0
@ -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
|
||||
|
@ -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
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
@ -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'],
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
4
packages/playwright-core/types/types.d.ts
vendored
4
packages/playwright-core/types/types.d.ts
vendored
@ -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
3086
tests/library/clock.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user