Fake Media/Fonts/Images: img, video, audio, FontFace, CSS stylesheets, permissions #9

Closed
opened 2026-06-17 13:37:44 +00:00 by Artur · 1 comment
Owner

Goal

Implement fake media/image/font resources. All HTML elements exist as DOM nodes, but no content is loaded, decoded, or rendered. All onload/onerror events fire to keep pages moving.

What to Build

src/fakes/media.ts

Images

// Patch HTMLImageElement.prototype
// - src setter: DOES NOT fetch image
// - naturalWidth/naturalHeight: return 1/1 (or 0 if no src)
// - complete: true after microtask
// - onload fires after setImmediate (setTimeout(0))
// - onerror does NOT fire unless explicitly triggered
// - decode(): returns resolved promise
// - currentSrc: equals src
// - loading: 'auto' (default), but setter accepted
// - sizes, srcset: attributes accepted but ignored for loading

Video / Audio

// Patch HTMLVideoElement / HTMLAudioElement:
// - play(): returns resolved promise, fires play event
// - pause(): NOOP, fires pause event
// - load(): NOOP
// - canPlayType(type): returns 'probably'
// - duration: 0 (or configurable)
// - currentTime: 0 (setter accepted, no seeking)
// - volume: 1 (setter accepted)
// - muted: false (setter accepted)
// - paused: true initially, false after play()
// - ended: false
// - readyState: HAVE_ENOUGH_DATA (4)
// - networkState: NETWORK_IDLE (1)
// - videoWidth/videoHeight: 640/360 (or configurable)
// - playbackRate: 1 (setter accepted)

CSS Stylesheets

// <link rel="stylesheet">:
// - href setter: DOES NOT fetch
// - sheet: null (or CSSStyleSheet stub)
// - disabled: false (setter accepted)
// - onload fires after microtask (element readyState = 'complete')

// <style>element:
// - innerHTML/textContent stores the CSS text
// - sheet: null (or CSSStyleSheet stub)
// - No CSS evaluation

src/fakes/fonts.ts

// Patch document.fonts (FontFaceSet):
// - add(fontFace): NOOP, returns FontFaceSet
// - delete(fontFace): NOOP, returns boolean
// - clear(): NOOP
// - load(font, text): returns resolved Promise with FontFace[]
// - ready: resolved Promise<FontFaceSet>
// - status: 'loaded'
// - check(font, text): returns true
// - forEach, entries, keys, values: iterable but empty
// - onloading, onloadingdone, onloadingerror: null (setter accepted)

// new FontFace(family, source, descriptors):
// - load(): returns resolved Promise<FontFace>
// - loaded: resolved Promise<FontFace>
// - status: 'loaded'
// - family, style, weight, stretch, unicodeRange, variant, featureSettings: gettable/settable
// - display: gettable/settable

src/fakes/permissions.ts

// navigator.permissions:
// - query({ name: 'geolocation' }): returns Promise<PermissionStatus> with state: 'granted'
// - query({ name: 'notifications' }): returns Promise<PermissionStatus> with state: 'granted'
// - query({ name: 'camera' }): returns Promise<PermissionStatus> with state: 'granted'
// - query({ name: 'microphone' }): returns Promise<PermissionStatus> with state: 'granted'  
// - query({ name: 'clipboard-read' }): returns Promise<PermissionStatus> with state: 'granted'
// - query({ name: 'clipboard-write' }): returns Promise<PermissionStatus> with state: 'granted'
// - query({ name: 'storage' }): returns Promise<PermissionStatus> with state: 'granted'
// - query with unknown name: returns Promise<PermissionStatus> with state: 'prompt'

// navigator.credentials:
// - get(): returns null
// - store(): returns Promise<undefined>
// - create(): returns Promise<null>
// - preventSilentAccess(): returns Promise<undefined>

// navigator.geolocation:
// - getCurrentPosition(success): success fires with { coords: { latitude: 52.52, longitude: 13.40, accuracy: 10 } }
// - watchPosition(success): returns ID, success fires repeatedly
// - clearWatch(id): stops watching

Resource Behavior (NOOP)

All external resources that a page would load:

  • Images: NOT fetched, onload fires
  • CSS stylesheets: NOT fetched, onload fires (or immediate)
  • Fonts: NOT fetched, FontFace.load() resolves
  • Videos/Audio: NOT fetched, canplay/loadedmetadata fire
  • Favicon: NOT fetched
  • Manifests: NOT fetched
  • Preload/prefetch: Recorded in log, NOT fetched

Tests

Unit Tests

Test Verifies
img.basic.test.ts new Image() creates element, src setter works
img.onload.test.ts onload fires after setTimeout(0)
img.natural-dimensions.test.ts naturalWidth returns 1 when src set
img.decode.test.ts decode() returns resolved promise
video.play.test.ts play() returns resolved promise, paused becomes false
video.can-play-type.test.ts canPlayType('video/mp4') returns 'probably'
audio.basic.test.ts Audio element works, play/pause NOOP
font-face.test.ts new FontFace() + load() resolves
font-face-set.test.ts document.fonts.load() resolves
permissions.query.test.ts navigator.permissions.query returns granted
permissions.credentials.test.ts navigator.credentials.get() returns null
geolocation.test.ts navigator.geolocation.getCurrentPosition fires callback
stylesheet-nofetch.test.ts does not fetch
style-element.test.ts
## Goal Implement fake media/image/font resources. All HTML elements exist as DOM nodes, but no content is loaded, decoded, or rendered. All onload/onerror events fire to keep pages moving. ## What to Build ### src/fakes/media.ts #### Images ``` // Patch HTMLImageElement.prototype // - src setter: DOES NOT fetch image // - naturalWidth/naturalHeight: return 1/1 (or 0 if no src) // - complete: true after microtask // - onload fires after setImmediate (setTimeout(0)) // - onerror does NOT fire unless explicitly triggered // - decode(): returns resolved promise // - currentSrc: equals src // - loading: 'auto' (default), but setter accepted // - sizes, srcset: attributes accepted but ignored for loading ``` #### Video / Audio ``` // Patch HTMLVideoElement / HTMLAudioElement: // - play(): returns resolved promise, fires play event // - pause(): NOOP, fires pause event // - load(): NOOP // - canPlayType(type): returns 'probably' // - duration: 0 (or configurable) // - currentTime: 0 (setter accepted, no seeking) // - volume: 1 (setter accepted) // - muted: false (setter accepted) // - paused: true initially, false after play() // - ended: false // - readyState: HAVE_ENOUGH_DATA (4) // - networkState: NETWORK_IDLE (1) // - videoWidth/videoHeight: 640/360 (or configurable) // - playbackRate: 1 (setter accepted) ``` #### CSS Stylesheets ``` // <link rel="stylesheet">: // - href setter: DOES NOT fetch // - sheet: null (or CSSStyleSheet stub) // - disabled: false (setter accepted) // - onload fires after microtask (element readyState = 'complete') // <style>element: // - innerHTML/textContent stores the CSS text // - sheet: null (or CSSStyleSheet stub) // - No CSS evaluation ``` ### src/fakes/fonts.ts ``` // Patch document.fonts (FontFaceSet): // - add(fontFace): NOOP, returns FontFaceSet // - delete(fontFace): NOOP, returns boolean // - clear(): NOOP // - load(font, text): returns resolved Promise with FontFace[] // - ready: resolved Promise<FontFaceSet> // - status: 'loaded' // - check(font, text): returns true // - forEach, entries, keys, values: iterable but empty // - onloading, onloadingdone, onloadingerror: null (setter accepted) // new FontFace(family, source, descriptors): // - load(): returns resolved Promise<FontFace> // - loaded: resolved Promise<FontFace> // - status: 'loaded' // - family, style, weight, stretch, unicodeRange, variant, featureSettings: gettable/settable // - display: gettable/settable ``` ### src/fakes/permissions.ts ``` // navigator.permissions: // - query({ name: 'geolocation' }): returns Promise<PermissionStatus> with state: 'granted' // - query({ name: 'notifications' }): returns Promise<PermissionStatus> with state: 'granted' // - query({ name: 'camera' }): returns Promise<PermissionStatus> with state: 'granted' // - query({ name: 'microphone' }): returns Promise<PermissionStatus> with state: 'granted' // - query({ name: 'clipboard-read' }): returns Promise<PermissionStatus> with state: 'granted' // - query({ name: 'clipboard-write' }): returns Promise<PermissionStatus> with state: 'granted' // - query({ name: 'storage' }): returns Promise<PermissionStatus> with state: 'granted' // - query with unknown name: returns Promise<PermissionStatus> with state: 'prompt' // navigator.credentials: // - get(): returns null // - store(): returns Promise<undefined> // - create(): returns Promise<null> // - preventSilentAccess(): returns Promise<undefined> // navigator.geolocation: // - getCurrentPosition(success): success fires with { coords: { latitude: 52.52, longitude: 13.40, accuracy: 10 } } // - watchPosition(success): returns ID, success fires repeatedly // - clearWatch(id): stops watching ``` ### Resource Behavior (NOOP) All external resources that a page would load: - Images: NOT fetched, onload fires - CSS stylesheets: NOT fetched, onload fires (or immediate) - Fonts: NOT fetched, FontFace.load() resolves - Videos/Audio: NOT fetched, canplay/loadedmetadata fire - Favicon: NOT fetched - Manifests: NOT fetched - Preload/prefetch: Recorded in log, NOT fetched ## Tests ### Unit Tests | Test | Verifies | |------|----------| | img.basic.test.ts | new Image() creates element, src setter works | | img.onload.test.ts | onload fires after setTimeout(0) | | img.natural-dimensions.test.ts | naturalWidth returns 1 when src set | | img.decode.test.ts | decode() returns resolved promise | | video.play.test.ts | play() returns resolved promise, paused becomes false | | video.can-play-type.test.ts | canPlayType('video/mp4') returns 'probably' | | audio.basic.test.ts | Audio element works, play/pause NOOP | | font-face.test.ts | new FontFace() + load() resolves | | font-face-set.test.ts | document.fonts.load() resolves | | permissions.query.test.ts | navigator.permissions.query returns granted | | permissions.credentials.test.ts | navigator.credentials.get() returns null | | geolocation.test.ts | navigator.geolocation.getCurrentPosition fires callback | | stylesheet-nofetch.test.ts | <link rel="stylesheet"> does not fetch | | style-element.test.ts | <style> stores textContent, no evaluation | ### Edge Cases | Test | Verifies | |------|----------| | img.no-src.test.ts | Image without src has naturalWidth 0, complete false | | img.error.test.ts | onerror does NOT fire without explicit trigger | | video.play-twice.test.ts | play() called twice, both promises resolve | | font-face-invalid.test.ts | FontFace with empty source loads successfully (NOOP) | ## Definition of Done - [ ] src/fakes/media.ts implemented (img, video, audio, CSS) - [ ] src/fakes/fonts.ts implemented (FontFace, FontFaceSet) - [ ] src/fakes/permissions.ts implemented (permissions, credentials, geolocation) - [ ] All media elements fire onload without network access - [ ] All tests pass - [ ] 100% line + branch coverage
Author
Owner

Fake Media/Fonts/Images: img, video, audio, FontFace, CSS stylesheets, permissions. Implementiert in src/fakes/media.ts. Tests: media.test.ts.

Fake Media/Fonts/Images: img, video, audio, FontFace, CSS stylesheets, permissions. ✅ Implementiert in src/fakes/media.ts. Tests: media.test.ts.
Artur closed this issue 2026-06-18 06:28:03 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
glow-all/true-headless-browser#9
No description provided.