export function createTimeController() { let fixedTime = Date.now() return { install: (browser) => { return browser.eval(` (() => { const OriginalDate = Date; const originalSetTimeout = globalThis.setTimeout; const originalSetInterval = globalThis.setInterval; const originalClearTimeout = globalThis.clearTimeout; const originalClearInterval = globalThis.clearInterval; let fixedTime = ${fixedTime}; let timerIdCounter = 1; const timers = new Map(); // Override the Date constructor globalThis.Date = class extends OriginalDate { constructor(...args) { if (args.length === 0) { super(fixedTime); } else { super(...args); } } static now() { return fixedTime; } }; // Preserve static methods Object.setPrototypeOf(globalThis.Date, OriginalDate); // Override setTimeout globalThis.setTimeout = function(callback, delay, ...args) { const id = timerIdCounter++; timers.set(id, { callback, args, fireTime: fixedTime + (delay || 0), interval: false }); return id; }; // Override setInterval globalThis.setInterval = function(callback, delay, ...args) { const id = timerIdCounter++; timers.set(id, { callback, args, fireTime: fixedTime + (delay || 0), interval: true, delay: delay || 0 }); return id; }; // Override clearTimeout globalThis.clearTimeout = function(id) { timers.delete(id); }; // Override clearInterval globalThis.clearInterval = function(id) { timers.delete(id); }; // Add method to advance time globalThis.__advanceTime = (ms) => { fixedTime += ms; // Fire any timers that should have fired const toFire = []; for (const [id, timer] of timers.entries()) { if (timer.fireTime <= fixedTime) { toFire.push({ id, timer }); } } for (const { id, timer } of toFire) { try { timer.callback(...timer.args); } catch (e) { console.error('Timer callback error:', e); } if (timer.interval) { // Reschedule interval timer timer.fireTime = fixedTime + timer.delay; } else { // Remove one-time timer timers.delete(id); } } }; })(); `) }, advance: async (browser, ms: number) => { fixedTime += ms await browser.eval(`globalThis.__advanceTime(${ms})`) }, } }