diff --git a/packages/injected/src/clock.ts b/packages/injected/src/clock.ts index c623e0f54f527..18c42e9ced53b 100644 --- a/packages/injected/src/clock.ts +++ b/packages/injected/src/clock.ts @@ -612,10 +612,11 @@ function platformOriginals(globalObject: WindowOrWorkerGlobalScope): { raw: Buil Date: (globalObject as any).Date, performance: globalObject.performance, Intl: (globalObject as any).Intl, + AbortSignal: (globalObject as any).AbortSignal, }; const bound = { ...raw }; for (const key of Object.keys(bound) as (keyof Builtins)[]) { - if (key !== 'Date' && typeof bound[key] === 'function') + if (key !== 'Date' && key !== 'AbortSignal' && typeof bound[key] === 'function') bound[key] = (bound[key] as any).bind(globalObject); } return { raw, bound }; @@ -688,6 +689,7 @@ function createApi(clock: ClockController, originals: Builtins): Builtins { Intl: originals.Intl ? createIntl(clock, originals.Intl) : (undefined as unknown as Builtins['Intl']), Date: createDate(clock, originals.Date), performance: originals.performance ? fakePerformance(clock, originals.performance) : (undefined as unknown as Builtins['performance']), + AbortSignal: originals.AbortSignal ? fakeAbortSignal(clock, originals.AbortSignal) : (undefined as unknown as Builtins['AbortSignal']), }; } @@ -715,6 +717,22 @@ function fakePerformance(clock: ClockController, performance: Builtins['performa return result; } +function fakeAbortSignal(clock: ClockController, abortSignal: Builtins['AbortSignal']): Builtins['AbortSignal'] { + const result: any = { + ...abortSignal, + timeout: (ms: number) => { + const controller = new AbortController(); + clock.addTimer({ + delay: ms, + type: TimerType.Timeout, + func: () => controller.abort(new DOMException('This operation was aborted', 'AbortError')), + }); + return controller.signal; + }, + }; + return result; +} + export function createClock(globalObject: WindowOrWorkerGlobalScope): { clock: ClockController, api: Builtins, originals: Builtins } { const originals = platformOriginals(globalObject); const embedder: Embedder = { @@ -750,6 +768,8 @@ export function install(globalObject: WindowOrWorkerGlobalScope, config: Install (globalObject as any).Date = mirrorDateProperties(api.Date, (globalObject as any).Date); } else if (method === 'Intl') { (globalObject as any).Intl = api[method]!; + } else if (method === 'AbortSignal') { + (globalObject as any).AbortSignal = api[method]!; } else if (method === 'performance') { (globalObject as any).performance = api[method]!; const kEventTimeStamp = Symbol('playwrightEventTimeStamp'); diff --git a/packages/injected/src/utilityScript.ts b/packages/injected/src/utilityScript.ts index e61ad96d45b5f..5c54d9ed98814 100644 --- a/packages/injected/src/utilityScript.ts +++ b/packages/injected/src/utilityScript.ts @@ -29,6 +29,7 @@ export type Builtins = { performance: Window['performance'], Intl: typeof window['Intl'], Date: typeof window['Date'], + AbortSignal: typeof window['AbortSignal'], }; export class UtilityScript { @@ -55,6 +56,7 @@ export class UtilityScript { performance: global.performance, Intl: global.Intl, Date: global.Date, + AbortSignal: global.AbortSignal, } satisfies Builtins; } if (this.isUnderTest) diff --git a/tests/library/page-clock.spec.ts b/tests/library/page-clock.spec.ts index 277ec4672696a..8fcec7f2ba2ac 100644 --- a/tests/library/page-clock.spec.ts +++ b/tests/library/page-clock.spec.ts @@ -534,6 +534,43 @@ it.describe('Date.now', () => { }); }); +it('AbortSignal.timeout', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/39293' } }, async ({ page }) => { + await page.clock.install({ time: 0 }); + const controller = await page.evaluateHandle(() => { + const signal = AbortSignal.any([ + AbortSignal.timeout(100) + ]); + const handle = { + signal, + event: false, + handler: false, + }; + signal.addEventListener('abort', () => handle.event = true); + signal.onabort = () => handle.handler = true; + return handle; + }); + expect(await controller.evaluate(handle => ({ + signal: handle.signal.aborted, + event: handle.event, + handler: handle.handler, + }))).toEqual({ + signal: false, + event: false, + handler: false, + }); + await page.clock.runFor(200); + expect(await controller.evaluate(handle => ({ + signal: handle.signal.aborted, + event: handle.event, + handler: handle.handler, + }))).toEqual({ + signal: true, + event: true, + handler: true, + }); + expect(await page.evaluate(() => AbortSignal.abort().aborted)).toBe(true); +}); + it('correctly increments Date.now()/performance.now() during blocking execution', { annotation: { type: 'issue',