Summary
BrowserFrame.window is declared as Window & { document: VirtualDocument } but is actually a three-property object literal cast through as any. It is not a Window, so nothing that a page-context script or a Playwright-style API would reach for is present: no storage, no timers, no getComputedStyle, no navigator, no event target methods, and location is a two-property stub.
This is the substrate the rest of the Browser API sits on, so it limits what evaluate() (#1587) can meaningfully resolve, and it blocks any Playwright-shaped API that needs to run in page context.
Reproduction
import { Browser, Window } from 'very-happy-dom'
const frame = new Browser().newPage().mainFrame
console.log(Object.keys(frame.window)) // [ "document", "location", "Event" ]
console.log(frame.window instanceof Window) // false
console.log(typeof frame.window.localStorage) // undefined
console.log(typeof frame.window.setTimeout) // undefined
console.log(typeof frame.window.getComputedStyle) // undefined
console.log(typeof frame.window.navigator) // undefined
console.log(typeof frame.window.addEventListener) // undefined
console.log(frame.window.location.pathname) // undefinedConfirmed against main at 5fae54d4 with Bun 1.4.3.
Probing 41 members that exist on a real new Window() — localStorage, sessionStorage, fetch, setTimeout, clearTimeout, setInterval, requestAnimationFrame, queueMicrotask, matchMedia, getComputedStyle, navigator, history, customElements, addEventListener, dispatchEvent, removeEventListener, MutationObserver, IntersectionObserver, ResizeObserver, CustomEvent, HTMLElement, Image, DOMParser, URL, AbortController, indexedDB, crypto, performance, innerWidth, innerHeight, self, window, top, parent, happyDOM, CSS, XMLHttpRequest, WebSocket, FormData, Range, getSelection — finds 41 of 41 missing on a frame window.
Current implementation
BrowserFrame's constructor builds the window by hand:
// src/browser/BrowserFrame.ts
this.document = new VirtualDocument()
this.window = {
document: this.document,
location: {
href: this._url,
toString: () => this._url,
},
Event: VirtualEvent,
} as anyThe as any is what allows the declared Window type to disagree with the value, so this does not surface at the type level for consumers of frame.window.
Expected behavior
A frame should own a real Window instance, with its document as the frame's document:
frame.window instanceof Windowistrue- storage, timers, observers,
navigator,matchMediaandgetComputedStyleare present and operate on the frame's document frame.window.locationis a realLocation, withpathname,search,hash,origin,protocol,hostand the assignment setters, staying in sync withframe.urlframe.window.document === frame.documentcontinues to holdself/windowself-references resolve, andtop/parentreflect the frame tree for child frames
Constructing the Window with the page's viewport and the context's settings would also let per-context configuration reach page code.