wip
This commit is contained in:
parent
8bc139b27c
commit
294f0523c7
|
|
@ -35,6 +35,10 @@ Install fake timers with the specified base time.
|
||||||
|
|
||||||
The maximum number of timers that will be run in [`method: Clock.runAllTimers`]. Defaults to `1000`.
|
The maximum number of timers that will be run in [`method: Clock.runAllTimers`]. Defaults to `1000`.
|
||||||
|
|
||||||
|
### option: Clock.installFakeTimers.speed
|
||||||
|
* since: v1.45
|
||||||
|
- `speed` <[float]>
|
||||||
|
|
||||||
## async method: Clock.runAllTimers
|
## async method: Clock.runAllTimers
|
||||||
* since: v1.45
|
* since: v1.45
|
||||||
- returns: <[int]>
|
- returns: <[int]>
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,9 @@ export class Clock implements api.Clock {
|
||||||
this._browserContext = browserContext;
|
this._browserContext = browserContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
async installFakeTimers(time: number | Date, options: { loopLimit?: number } = {}) {
|
async installFakeTimers(time: number | Date, options: { loopLimit?: number, speed?: number } = {}) {
|
||||||
const timeMs = time instanceof Date ? time.getTime() : time;
|
const timeMs = time instanceof Date ? time.getTime() : time;
|
||||||
await this._browserContext._channel.clockInstallFakeTimers({ time: timeMs, loopLimit: options.loopLimit });
|
await this._browserContext._channel.clockInstallFakeTimers({ time: timeMs, loopLimit: options.loopLimit, speed: options.speed });
|
||||||
}
|
}
|
||||||
|
|
||||||
async runAllTimers(): Promise<number> {
|
async runAllTimers(): Promise<number> {
|
||||||
|
|
|
||||||
|
|
@ -966,6 +966,7 @@ scheme.BrowserContextUpdateSubscriptionResult = tOptional(tObject({}));
|
||||||
scheme.BrowserContextClockInstallFakeTimersParams = tObject({
|
scheme.BrowserContextClockInstallFakeTimersParams = tObject({
|
||||||
time: tNumber,
|
time: tNumber,
|
||||||
loopLimit: tOptional(tNumber),
|
loopLimit: tOptional(tNumber),
|
||||||
|
speed: tOptional(tNumber),
|
||||||
});
|
});
|
||||||
scheme.BrowserContextClockInstallFakeTimersResult = tOptional(tObject({}));
|
scheme.BrowserContextClockInstallFakeTimersResult = tOptional(tObject({}));
|
||||||
scheme.BrowserContextClockRunAllTimersParams = tOptional(tObject({}));
|
scheme.BrowserContextClockRunAllTimersParams = tOptional(tObject({}));
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,11 @@ export class Clock {
|
||||||
this._browserContext = browserContext;
|
this._browserContext = browserContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
async installFakeTimers(time: number, loopLimit: number | undefined) {
|
async installFakeTimers(time: number, loopLimit: number | undefined, speed: number | undefined) {
|
||||||
await this._injectScriptIfNeeded();
|
await this._injectScriptIfNeeded();
|
||||||
await this._addAndEvaluate(`(() => {
|
await this._addAndEvaluate(`(() => {
|
||||||
globalThis.__pwClock.clock?.uninstall();
|
globalThis.__pwClock.clock?.uninstall();
|
||||||
globalThis.__pwClock.clock = globalThis.__pwClock.install(${JSON.stringify({ now: time, loopLimit })});
|
globalThis.__pwClock.clock = globalThis.__pwClock.install(${JSON.stringify({ now: time, loopLimit, speed })});
|
||||||
})();`);
|
})();`);
|
||||||
this._now = time;
|
this._now = time;
|
||||||
this._clockInstalled = true;
|
this._clockInstalled = true;
|
||||||
|
|
|
||||||
|
|
@ -313,7 +313,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||||
}
|
}
|
||||||
|
|
||||||
async clockInstallFakeTimers(params: channels.BrowserContextClockInstallFakeTimersParams, metadata?: CallMetadata | undefined): Promise<channels.BrowserContextClockInstallFakeTimersResult> {
|
async clockInstallFakeTimers(params: channels.BrowserContextClockInstallFakeTimersParams, metadata?: CallMetadata | undefined): Promise<channels.BrowserContextClockInstallFakeTimersResult> {
|
||||||
await this._context.clock.installFakeTimers(params.time, params.loopLimit);
|
await this._context.clock.installFakeTimers(params.time, params.loopLimit, params.speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clockRunAllTimers(params: channels.BrowserContextClockRunAllTimersParams, metadata?: CallMetadata | undefined): Promise<channels.BrowserContextClockRunAllTimersResult> {
|
async clockRunAllTimers(params: channels.BrowserContextClockRunAllTimersParams, metadata?: CallMetadata | undefined): Promise<channels.BrowserContextClockRunAllTimersResult> {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ export type ClockConfig = {
|
||||||
|
|
||||||
export type InstallConfig = ClockConfig & {
|
export type InstallConfig = ClockConfig & {
|
||||||
toFake?: (keyof ClockMethods)[];
|
toFake?: (keyof ClockMethods)[];
|
||||||
|
speed?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum TimerType {
|
enum TimerType {
|
||||||
|
|
@ -53,7 +54,8 @@ type Timer = {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Embedder {
|
interface Embedder {
|
||||||
postTask(task: () => void): void;
|
performanceNow(): DOMHighResTimeStamp;
|
||||||
|
postTask(task: () => void, timeout?: number): () => void;
|
||||||
postTaskPeriodically(task: () => void, delay: number): () => void;
|
postTaskPeriodically(task: () => void, delay: number): () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,6 +68,8 @@ export class ClockController {
|
||||||
private _uniqueTimerId = idCounterStart;
|
private _uniqueTimerId = idCounterStart;
|
||||||
private _embedder: Embedder;
|
private _embedder: Embedder;
|
||||||
readonly disposables: (() => void)[] = [];
|
readonly disposables: (() => void)[] = [];
|
||||||
|
private _realTime: { startTicks: number, speed: number, lastSyncTicks: number } | undefined;
|
||||||
|
private _currentRealTimeTimer: { callAt: number, dispose: () => void } | undefined;
|
||||||
|
|
||||||
constructor(embedder: Embedder, startDate: Date | number | undefined, loopLimit: number = 1000) {
|
constructor(embedder: Embedder, startDate: Date | number | undefined, loopLimit: number = 1000) {
|
||||||
const start = Math.floor(getEpoch(startDate));
|
const start = Math.floor(getEpoch(startDate));
|
||||||
|
|
@ -145,6 +149,37 @@ export class ClockController {
|
||||||
return this.tick(this.getTimeToNextFrame());
|
return this.tick(this.getTimeToNextFrame());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startRealTime(options: { speed?: number } = {}) {
|
||||||
|
const now = Math.ceil(this._embedder.performanceNow());
|
||||||
|
this._realTime = { startTicks: now, speed: options.speed || 1, lastSyncTicks: now };
|
||||||
|
}
|
||||||
|
|
||||||
|
private _ensureTimer() {
|
||||||
|
const firstTimer = this._firstTimer();
|
||||||
|
if (!firstTimer)
|
||||||
|
return;
|
||||||
|
const callAt = firstTimer.callAt;
|
||||||
|
|
||||||
|
if (this._currentRealTimeTimer && this._currentRealTimeTimer.callAt < callAt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this._currentRealTimeTimer) {
|
||||||
|
this._currentRealTimeTimer.dispose();
|
||||||
|
this._currentRealTimeTimer = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._currentRealTimeTimer = {
|
||||||
|
callAt,
|
||||||
|
dispose: this._embedder.postTask(() => {
|
||||||
|
const now = Math.ceil(this._embedder.performanceNow());
|
||||||
|
const sinceLastSync = now - this._realTime!.lastSyncTicks;
|
||||||
|
this._realTime!.lastSyncTicks = now;
|
||||||
|
this._currentRealTimeTimer = undefined;
|
||||||
|
this.tick(sinceLastSync * this._realTime!.speed).then(() => this._ensureTimer());
|
||||||
|
}, (callAt - this._now.ticks) / this._realTime!.speed),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async runAll(): Promise<number> {
|
async runAll(): Promise<number> {
|
||||||
for (let i = 0; i < this._loopLimit; i++) {
|
for (let i = 0; i < this._loopLimit; i++) {
|
||||||
const numTimers = this._timers.size;
|
const numTimers = this._timers.size;
|
||||||
|
|
@ -204,6 +239,8 @@ export class ClockController {
|
||||||
error: new Error(),
|
error: new Error(),
|
||||||
};
|
};
|
||||||
this._timers.set(timer.id, timer);
|
this._timers.set(timer.id, timer);
|
||||||
|
if (this._realTime)
|
||||||
|
this._ensureTimer();
|
||||||
return timer.id;
|
return timer.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -412,7 +449,7 @@ function parseTime(value: number | string): number {
|
||||||
|
|
||||||
function mirrorDateProperties(target: any, source: typeof Date): DateConstructor & Date {
|
function mirrorDateProperties(target: any, source: typeof Date): DateConstructor & Date {
|
||||||
let prop;
|
let prop;
|
||||||
for (prop of Object.keys(source) as (keyof DateConstructor)[])
|
for (prop of Object.getOwnPropertyNames(source) as (keyof DateConstructor)[])
|
||||||
target[prop] = source[prop];
|
target[prop] = source[prop];
|
||||||
target.toString = () => source.toString();
|
target.toString = () => source.toString();
|
||||||
target.prototype = source.prototype;
|
target.prototype = source.prototype;
|
||||||
|
|
@ -485,7 +522,7 @@ function createIntl(clock: ClockController, NativeIntl: typeof Intl): typeof Int
|
||||||
* All properties of Intl are non-enumerable, so we need
|
* All properties of Intl are non-enumerable, so we need
|
||||||
* to do a bit of work to get them out.
|
* 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[key] = NativeIntl[key];
|
||||||
|
|
||||||
ClockIntl.DateTimeFormat = function(...args: any[]) {
|
ClockIntl.DateTimeFormat = function(...args: any[]) {
|
||||||
|
|
@ -661,8 +698,10 @@ function fakePerformance(clock: ClockController, performance: Performance): Perf
|
||||||
export function createClock(globalObject: WindowOrWorkerGlobalScope, config: ClockConfig = {}): { clock: ClockController, api: ClockMethods, originals: ClockMethods } {
|
export function createClock(globalObject: WindowOrWorkerGlobalScope, config: ClockConfig = {}): { clock: ClockController, api: ClockMethods, originals: ClockMethods } {
|
||||||
const originals = platformOriginals(globalObject);
|
const originals = platformOriginals(globalObject);
|
||||||
const embedder = {
|
const embedder = {
|
||||||
postTask: (task: () => void) => {
|
performanceNow: () => originals.raw.performance!.now(),
|
||||||
originals.bound.setTimeout(task, 0);
|
postTask: (task: () => void, timeout?: number) => {
|
||||||
|
const timerId = originals.bound.setTimeout(task, timeout);
|
||||||
|
return () => originals.bound.clearTimeout(timerId);
|
||||||
},
|
},
|
||||||
postTaskPeriodically: (task: () => void, delay: number) => {
|
postTaskPeriodically: (task: () => void, delay: number) => {
|
||||||
const intervalId = globalObject.setInterval(task, delay);
|
const intervalId = globalObject.setInterval(task, delay);
|
||||||
|
|
@ -702,6 +741,9 @@ export function install(globalObject: WindowOrWorkerGlobalScope, config: Install
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof config.speed === 'number')
|
||||||
|
clock.startRealTime({ speed: config.speed });
|
||||||
|
|
||||||
return { clock, api, originals };
|
return { clock, api, originals };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
2
packages/playwright-core/types/types.d.ts
vendored
2
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -17271,6 +17271,8 @@ export interface Clock {
|
||||||
* [clock.runAllTimers()](https://playwright.dev/docs/api/class-clock#clock-run-all-timers). Defaults to `1000`.
|
* [clock.runAllTimers()](https://playwright.dev/docs/api/class-clock#clock-run-all-timers). Defaults to `1000`.
|
||||||
*/
|
*/
|
||||||
loopLimit?: number;
|
loopLimit?: number;
|
||||||
|
|
||||||
|
speed?: number;
|
||||||
}): Promise<void>;
|
}): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1758,9 +1758,11 @@ export type BrowserContextUpdateSubscriptionResult = void;
|
||||||
export type BrowserContextClockInstallFakeTimersParams = {
|
export type BrowserContextClockInstallFakeTimersParams = {
|
||||||
time: number,
|
time: number,
|
||||||
loopLimit?: number,
|
loopLimit?: number,
|
||||||
|
speed?: number,
|
||||||
};
|
};
|
||||||
export type BrowserContextClockInstallFakeTimersOptions = {
|
export type BrowserContextClockInstallFakeTimersOptions = {
|
||||||
loopLimit?: number,
|
loopLimit?: number,
|
||||||
|
speed?: number,
|
||||||
};
|
};
|
||||||
export type BrowserContextClockInstallFakeTimersResult = void;
|
export type BrowserContextClockInstallFakeTimersResult = void;
|
||||||
export type BrowserContextClockRunAllTimersParams = {};
|
export type BrowserContextClockRunAllTimersParams = {};
|
||||||
|
|
|
||||||
|
|
@ -1208,6 +1208,7 @@ BrowserContext:
|
||||||
parameters:
|
parameters:
|
||||||
time: number
|
time: number
|
||||||
loopLimit: number?
|
loopLimit: number?
|
||||||
|
speed: number?
|
||||||
|
|
||||||
clockRunAllTimers:
|
clockRunAllTimers:
|
||||||
returns:
|
returns:
|
||||||
|
|
|
||||||
|
|
@ -2326,6 +2326,40 @@ it.describe('Intl API', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.describe('startRealTime', () => {
|
||||||
|
it.only('should start real time', async ({ clock }) => {
|
||||||
|
clock.startRealTime({ speed: 2 });
|
||||||
|
const start = Date.now();
|
||||||
|
clock.setTimeout(() => {
|
||||||
|
const end = Date.now();
|
||||||
|
console.log('A', clock.Date.now(), end - start);
|
||||||
|
}, 1000);
|
||||||
|
clock.setTimeout(() => {
|
||||||
|
const end = Date.now();
|
||||||
|
console.log('B', clock.Date.now(), end - start);
|
||||||
|
}, 1100);
|
||||||
|
clock.setTimeout(() => {
|
||||||
|
const end = Date.now();
|
||||||
|
console.log('C', clock.Date.now(), end - start);
|
||||||
|
clock.setTimeout(() => {
|
||||||
|
const end = Date.now();
|
||||||
|
console.log('D', clock.Date.now(), end - start);
|
||||||
|
}, 400);
|
||||||
|
}, 300);
|
||||||
|
await new Promise(f => setTimeout(f, 2000));
|
||||||
|
});
|
||||||
|
|
||||||
|
it.only('should start real time interval', async ({ clock }) => {
|
||||||
|
clock.startRealTime({ speed: 2 });
|
||||||
|
const start = Date.now();
|
||||||
|
clock.setInterval(() => {
|
||||||
|
const end = Date.now();
|
||||||
|
console.log('A', clock.Date.now(), end - start);
|
||||||
|
}, 100);
|
||||||
|
await new Promise(f => setTimeout(f, 2000));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
interface Stub {
|
interface Stub {
|
||||||
called: boolean;
|
called: boolean;
|
||||||
callCount: number;
|
callCount: number;
|
||||||
|
|
|
||||||
|
|
@ -738,4 +738,12 @@ it.describe('setTime', () => {
|
||||||
await page.clock.setTime(200);
|
await page.clock.setTime(200);
|
||||||
expect(calls).toHaveLength(2);
|
expect(calls).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.only('my clock', async ({ page }) => {
|
||||||
|
await page.clock.installFakeTimers(Date.now(), { speed: 0.5 });
|
||||||
|
await page.goto('https://www.online-stopwatch.com/large-online-clock/');
|
||||||
|
// await page.goto('https://www.example.com');
|
||||||
|
await new Promise(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue