From 582506d987b7a4240a540d365e87a8878d6d5639 Mon Sep 17 00:00:00 2001 From: yen0304 Date: Tue, 9 Jun 2026 02:07:20 +0800 Subject: [PATCH 1/8] fix(har): off-by-one in WebSocket frame header size calculation (#41143) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix boundary condition in WebSocket frame header size calculation: `length > 2 ** 16` → `length >= 2 ** 16` - Per RFC 6455 Section 5.2, a payload of exactly 65536 bytes requires the 64-bit extended length field (16-bit unsigned max is 65535) - Add test for the exact boundary (2**16 bytes payload) Fixes https://github.com/microsoft/playwright/issues/41142 --- .../src/server/har/harTracer.ts | 2 +- tests/library/har-websocket.spec.ts | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/playwright-core/src/server/har/harTracer.ts b/packages/playwright-core/src/server/har/harTracer.ts index 4c57af8323713..1490d42a126b5 100644 --- a/packages/playwright-core/src/server/har/harTracer.ts +++ b/packages/playwright-core/src/server/har/harTracer.ts @@ -489,7 +489,7 @@ export class HarTracer { // - there are always 4 bytes for the masking key (see ) // - there may be an additional 16 or 64 bits for payload length if it's too long to fit in the above 7 bits (or if it also can't fit in 16 bits) let headerSize = 6; - if (length > 2 ** 16) + if (length >= 2 ** 16) headerSize += 8; else if (length > 125) headerSize += 2; diff --git a/tests/library/har-websocket.spec.ts b/tests/library/har-websocket.spec.ts index c8f1b0305367a..ef97fc9244f0b 100644 --- a/tests/library/har-websocket.spec.ts +++ b/tests/library/har-websocket.spec.ts @@ -232,6 +232,34 @@ it('should include gigantic websocket messages', async ({ contextFactory, server expect(messages[0].time).toBeLessThanOrEqual(messages[1].time); }); +it('should use 64-bit extended length for exactly 2**16 byte websocket payload', async ({ contextFactory, server }, testInfo) => { + // RFC 6455 Section 5.2: 16-bit extended length covers 126-65535. + // Payloads of exactly 65536 (2**16) bytes require the 64-bit extended length field. + const incoming = 'x'.repeat(2 ** 16); + const outgoing = 'outgoing'; + + server.onceWebSocketConnection(ws => { + ws.on('message', () => ws.send(incoming)); + }); + + const { page, getLog } = await pageWithHar(contextFactory, testInfo); + await page.goto(server.EMPTY_PAGE); + + const wsUrl = `ws://${server.HOST}/ws`; + const closed = page.evaluate(({ url, outgoing }) => new Promise(resolve => { + const ws = new WebSocket(url); + ws.addEventListener('open', () => ws.send(outgoing)); + ws.addEventListener('message', () => ws.close()); + ws.addEventListener('close', () => resolve()); + }), { url: wsUrl, outgoing }); + await closed; + const log = await getLog(); + + const wsEntry = log.entries.find(e => e.request.url === wsUrl)! as Entry; + // 6 (base header) + 8 (64-bit extended length) for payload of exactly 2**16 bytes. + expect(wsEntry.response._transferSize).toBe(responseHeadersSize(wsEntry.response.headers) + 6 + 8 + incoming.length); +}); + it('should include binary websocket messages', async ({ contextFactory, server }, testInfo) => { const incoming = [0x01, 0x02, 0x03, 0x04]; const outgoing = [0x05, 0x06, 0x07, 0x08]; From 787ea68efbbcfa536db4eaa1410e01d6e58d440c Mon Sep 17 00:00:00 2001 From: Devin Rousso Date: Mon, 8 Jun 2026 13:43:13 -0600 Subject: [PATCH 2/8] feat(har): adjust capturing `WebSocket` data based on `content` config (#41124) only set `_webSocketMessages` if `content: 'embed'` for `content: 'attach'`, save the array as a `.json` also, unlike other requests that have a single response, a `WebSocket` can receive multiple frames, meaning that we don't finish the `Entry` until the `WebSocket` is closed, which delays when the captured frames are saved, so make sure to at least save what's already there if the recording is stopped before the `WebSocket` is closed --- .../src/server/har/harTracer.ts | 45 ++- tests/library/har-websocket.spec.ts | 297 +++++++----------- 2 files changed, 163 insertions(+), 179 deletions(-) diff --git a/packages/playwright-core/src/server/har/harTracer.ts b/packages/playwright-core/src/server/har/harTracer.ts index 1490d42a126b5..384b1a53e72b0 100644 --- a/packages/playwright-core/src/server/har/harTracer.ts +++ b/packages/playwright-core/src/server/har/harTracer.ts @@ -75,6 +75,7 @@ export class HarTracer { private _pageEntrySymbol: symbol; private _baseURL: string | undefined; private _page: Page | null; + private _saveOpenWebSocketMessagesFunctions = new Set<() => void>(); constructor(context: BrowserContext | APIRequestContext, page: Page | null, delegate: HarTracerDelegate, options: HarTracerOptions) { this._context = context; @@ -438,7 +439,28 @@ export class HarTracer { const pageEntry = this._createPageEntryIfNeeded(page); const harEntry = createHarEntry(pageEntry?.id, method, url, page.mainFrame().guid, this._options, webSocket.wallTimeMs()); harEntry._resourceType = 'websocket'; - harEntry._webSocketMessages = []; + + const messages: har.WebSocketMessage[] = []; + if (this._options.content === 'embed') + harEntry._webSocketMessages = messages; + + let saveMessages: (() => void) | undefined; + if (this._options.content === 'attach') { + saveMessages = () => { + if (!messages.length) + return; + + const buffer = Buffer.from(JSON.stringify(messages)); + const sha1 = calculateSha1(buffer) + '.json'; + if (this._options.includeTraceInfo) + harEntry.response.content._sha1 = sha1; + else + harEntry.response.content._file = sha1; + if (this._started) + this._delegate.onContentBlob(sha1, buffer); + }; + this._saveOpenWebSocketMessagesFunctions.add(saveMessages); + } let oldestWallTimeMs = Infinity; let newestWallTimeMs = -Infinity; @@ -475,12 +497,17 @@ export class HarTracer { } }), eventsHelper.addEventListener(webSocket, network.WebSocket.Events.FrameSent, ({ opcode, data, wallTimeMs }: { opcode: number, data: string, wallTimeMs: number }) => { - harEntry._webSocketMessages!.push({ type: 'send', time: this._options.omitTiming ? -1 : wallTimeMs, opcode, data }); + if (this._options.content !== 'omit') + messages.push({ type: 'send', time: this._options.omitTiming ? -1 : wallTimeMs, opcode, data }); + updateTime(wallTimeMs); }), eventsHelper.addEventListener(webSocket, network.WebSocket.Events.FrameReceived, ({ opcode, data, wallTimeMs }: { opcode: number, data: string, wallTimeMs: number }) => { - harEntry._webSocketMessages!.push({ type: 'receive', time: this._options.omitTiming ? -1 : wallTimeMs, opcode, data }); + if (this._options.content !== 'omit') + messages.push({ type: 'receive', time: this._options.omitTiming ? -1 : wallTimeMs, opcode, data }); + updateTime(wallTimeMs); + if (!this._options.omitSizes) { const length = (opcode === 1) ? Buffer.byteLength(data, 'utf8') : base64ByteLength(data); @@ -503,6 +530,11 @@ export class HarTracer { eventsHelper.addEventListener(webSocket, network.WebSocket.Events.Close, () => { eventsHelper.removeEventListeners(eventListeners); + if (saveMessages) { + this._saveOpenWebSocketMessagesFunctions.delete(saveMessages); + saveMessages(); + } + if (this._started) this._delegate.onEntryFinished(harEntry); }), @@ -633,6 +665,12 @@ export class HarTracer { } stop() { + // Unlike other requests that have a single response, a WebSocket can receive multiple frames. + // As such, we don't finish the entry until the WebSocket is closed, which delays when the captured frames are saved. + // Make sure to save at least what has been captured so far. + for (const saveOpenWebSocketMessages of this._saveOpenWebSocketMessagesFunctions) + saveOpenWebSocketMessages(); + this._started = false; eventsHelper.removeEventListeners(this._eventListeners); this._barrierPromises.clear(); @@ -665,6 +703,7 @@ export class HarTracer { } } this._pageEntries = []; + this._saveOpenWebSocketMessagesFunctions.clear(); return log; } diff --git a/tests/library/har-websocket.spec.ts b/tests/library/har-websocket.spec.ts index ef97fc9244f0b..795996e9f836e 100644 --- a/tests/library/har-websocket.spec.ts +++ b/tests/library/har-websocket.spec.ts @@ -16,11 +16,12 @@ */ import { browserTest as it, expect } from '../config/browserTest'; +import { parseHar } from '../config/utils'; import fs from 'fs'; import net from 'net'; import type { BrowserContext, BrowserContextOptions } from 'playwright-core'; import type { AddressInfo } from 'net'; -import type { Entry, Log } from '../../packages/trace/src/har'; +import type { Entry, Log, WebSocketMessage } from '../../packages/trace/src/har'; async function pageWithHar(contextFactory: (options?: BrowserContextOptions) => Promise, testInfo: any, options: { outputPath?: string } & Partial> = {}) { const harPath = testInfo.outputPath(options.outputPath || 'test.har'); @@ -33,6 +34,10 @@ async function pageWithHar(contextFactory: (options?: BrowserContextOptions) => await context.close(); return JSON.parse(fs.readFileSync(harPath).toString())['log'] as Log; }, + getZip: async () => { + await context.close(); + return parseHar(harPath); + }, }; } @@ -56,7 +61,17 @@ function responseHeadersSize(headers: { name: string, value: string }[]): number return result; } -it('should only have one websocket entry', async ({ contextFactory, server, browserName }, testInfo) => { +function messageSize(message: string | number[]): number { + // The payload is short enough that they only need the minimum frame header size. + if (message.length <= 125) + return 6 + message.length; + // The payload is large enough that additional bytes are needed to represent the payload length. + if (message.length < 2 ** 16) + return 6 + 2 + message.length; + return 6 + 8 + message.length; +} + +it('should only have one websocket entry', async ({ contextFactory, server }, testInfo) => { server.onceWebSocketConnection(ws => { ws.on('message', () => ws.close()); }); @@ -77,7 +92,7 @@ it('should only have one websocket entry', async ({ contextFactory, server, brow expect(wsEntry._resourceType).toBe('websocket'); }); -it('should include websocket handshake headers and status', async ({ contextFactory, server, browserName }, testInfo) => { +it('should include websocket handshake headers and status', async ({ contextFactory, server }, testInfo) => { server.onceWebSocketConnection(ws => { ws.on('message', () => ws.close()); }); @@ -115,227 +130,157 @@ it('should include websocket handshake headers and status', async ({ contextFact expect(responseHeaderNames).toContain('sec-websocket-accept'); }); -it('should include websocket messages', async ({ contextFactory, server }, testInfo) => { - const incoming = 'x'.repeat(125); - const outgoing = 'outgoing'; +async function testWebSocketMessages(contextFactory, server, testInfo, content) { + const incomingText = ['x'.repeat(125), 'x'.repeat(126), 'x'.repeat(2 ** 16)]; + const incomingBinary = [(new Array(125)).fill(0x01), (new Array(126)).fill(0x01), (new Array(2 ** 16)).fill(0x01)]; + const outgoingText = ['y'.repeat(125), 'y'.repeat(126), 'y'.repeat(2 ** 16)]; + const outgoingBinary = [(new Array(125)).fill(0x02), (new Array(126)).fill(0x02), (new Array(2 ** 16)).fill(0x02)]; server.onceWebSocketConnection(ws => { - ws.on('message', () => ws.send(incoming)); + for (const text of incomingText) + ws.send(text); + for (const binary of incomingBinary) + ws.send(Buffer.from(binary)); }); - const { page, getLog } = await pageWithHar(contextFactory, testInfo); + const outputPath = content === 'embed' ? undefined : 'test.har.zip'; + const { page, getLog, getZip } = await pageWithHar(contextFactory, testInfo, { content, outputPath }); await page.goto(server.EMPTY_PAGE); const beforeMs = Date.now(); const wsUrl = `ws://${server.HOST}/ws`; - const closed = page.evaluate(({ url, outgoing }) => new Promise(resolve => { + const closed = page.evaluate(({ url, incomingCount, outgoingText, outgoingBinary }) => new Promise(resolve => { + let count = 0; const ws = new WebSocket(url); - ws.addEventListener('open', () => ws.send(outgoing)); - ws.addEventListener('message', () => ws.close()); + ws.addEventListener('message', () => { + if (++count < incomingCount) + return; + for (const text of outgoingText) + ws.send(text); + for (const binary of outgoingBinary) + ws.send(new Uint8Array(binary)); + ws.close(); + }); ws.addEventListener('close', () => resolve()); - }), { url: wsUrl, outgoing }); + }), { url: wsUrl, incomingCount: incomingText.length + incomingBinary.length, outgoingText, outgoingBinary }); await closed; const afterMs = Date.now(); - const log = await getLog(); - const wsEntry = log.entries.find(e => e.request.url === wsUrl)! as Entry; - // The payload is short enough that they only need the minimum frame header size. - expect(wsEntry.response._transferSize).toBe(responseHeadersSize(wsEntry.response.headers) + 6 + incoming.length); - - const messages = wsEntry._webSocketMessages; - expect(messages.map(m => ({ type: m.type, opcode: m.opcode, data: m.data }))).toEqual([ - { type: 'send', opcode: 1, data: outgoing }, - { type: 'receive', opcode: 1, data: incoming }, - ]); - for (const m of messages) { - expect(m.time).toBeGreaterThanOrEqual(beforeMs - 1); - expect(m.time).toBeLessThanOrEqual(afterMs + 1); + let zip: Map; + let log: Log; + if (outputPath) { + zip = await getZip(); + log = JSON.parse(zip.get('har.har')!.toString())['log'] as Log; + } else { + log = await getLog(); } - expect(messages[0].time).toBeLessThanOrEqual(messages[1].time); -}); - -it('should include larger websocket messages', async ({ contextFactory, server }, testInfo) => { - const incoming = 'x'.repeat(126); - const outgoing = 'outgoing'; - - server.onceWebSocketConnection(ws => { - ws.on('message', () => ws.send(incoming)); - }); - - const { page, getLog } = await pageWithHar(contextFactory, testInfo); - await page.goto(server.EMPTY_PAGE); - - const beforeMs = Date.now(); - const wsUrl = `ws://${server.HOST}/ws`; - const closed = page.evaluate(({ url, outgoing }) => new Promise(resolve => { - const ws = new WebSocket(url); - ws.addEventListener('open', () => ws.send(outgoing)); - ws.addEventListener('message', () => ws.close()); - ws.addEventListener('close', () => resolve()); - }), { url: wsUrl, outgoing }); - await closed; - const afterMs = Date.now(); - const log = await getLog(); - const wsEntry = log.entries.find(e => e.request.url === wsUrl)! as Entry; - // The payload is large enough that additional bytes are needed to represent the payload length. - expect(wsEntry.response._transferSize).toBe(responseHeadersSize(wsEntry.response.headers) + 6 + 2 + incoming.length); + expect(wsEntry.response._transferSize).toBe(responseHeadersSize(wsEntry.response.headers) + [...incomingText, ...incomingBinary].reduce((accumulator, current) => accumulator + messageSize(current), 0)); + expect(wsEntry.time).toBeLessThanOrEqual(afterMs - beforeMs); - const messages = wsEntry._webSocketMessages; - expect(messages.map(m => ({ type: m.type, opcode: m.opcode, data: m.data }))).toEqual([ - { type: 'send', opcode: 1, data: outgoing }, - { type: 'receive', opcode: 1, data: incoming }, - ]); - for (const m of messages) { - expect(m.time).toBeGreaterThanOrEqual(beforeMs - 1); - expect(m.time).toBeLessThanOrEqual(afterMs + 1); + if (content === 'omit') { + expect(wsEntry.response.content._file).toBeUndefined(); + return; } - expect(messages[0].time).toBeLessThanOrEqual(messages[1].time); -}); - -it('should include gigantic websocket messages', async ({ contextFactory, server }, testInfo) => { - const incoming = 'x'.repeat(2 ** 16 + 1); - const outgoing = 'outgoing'; - - server.onceWebSocketConnection(ws => { - ws.on('message', () => ws.send(incoming)); - }); - - const { page, getLog } = await pageWithHar(contextFactory, testInfo); - await page.goto(server.EMPTY_PAGE); - const beforeMs = Date.now(); - const wsUrl = `ws://${server.HOST}/ws`; - const closed = page.evaluate(({ url, outgoing }) => new Promise(resolve => { - const ws = new WebSocket(url); - ws.addEventListener('open', () => ws.send(outgoing)); - ws.addEventListener('message', () => ws.close()); - ws.addEventListener('close', () => resolve()); - }), { url: wsUrl, outgoing }); - await closed; - const afterMs = Date.now(); - const log = await getLog(); - - const wsEntry = log.entries.find(e => e.request.url === wsUrl)! as Entry; - // The payload is large enough that additional bytes are needed to represent the payload length. - expect(wsEntry.response._transferSize).toBe(responseHeadersSize(wsEntry.response.headers) + 6 + 8 + incoming.length); - - const messages = wsEntry._webSocketMessages; - expect(messages.map(m => ({ type: m.type, opcode: m.opcode, data: m.data }))).toEqual([ - { type: 'send', opcode: 1, data: outgoing }, - { type: 'receive', opcode: 1, data: incoming }, + let messages: WebSocketMessage[]; + if (content === 'attach') { + expect(wsEntry._webSocketMessages).toBeUndefined(); + const file = wsEntry.response.content._file!; + expect(file).toMatch(/^[0-9a-f]{40}\.json$/); + messages = JSON.parse(zip.get(file)!.toString()) as WebSocketMessage[]; + } else { + messages = wsEntry._webSocketMessages; + } + expect(messages.map(m => ({ type: m.type, opcode: m.opcode, data: m.opcode === 1 ? m.data : [...Buffer.from(m.data, 'base64')] }))).toEqual([ + ...incomingText.map(m => ({ type: 'receive', opcode: 1, data: m })), + ...incomingBinary.map(m => ({ type: 'receive', opcode: 2, data: m })), + ...outgoingText.map(m => ({ type: 'send', opcode: 1, data: m })), + ...outgoingBinary.map(m => ({ type: 'send', opcode: 2, data: m })), ]); for (const m of messages) { expect(m.time).toBeGreaterThanOrEqual(beforeMs - 1); expect(m.time).toBeLessThanOrEqual(afterMs + 1); } expect(messages[0].time).toBeLessThanOrEqual(messages[1].time); -}); - -it('should use 64-bit extended length for exactly 2**16 byte websocket payload', async ({ contextFactory, server }, testInfo) => { - // RFC 6455 Section 5.2: 16-bit extended length covers 126-65535. - // Payloads of exactly 65536 (2**16) bytes require the 64-bit extended length field. - const incoming = 'x'.repeat(2 ** 16); - const outgoing = 'outgoing'; - - server.onceWebSocketConnection(ws => { - ws.on('message', () => ws.send(incoming)); - }); - - const { page, getLog } = await pageWithHar(contextFactory, testInfo); - await page.goto(server.EMPTY_PAGE); + expect(wsEntry.time).toBeGreaterThanOrEqual(messages[messages.length - 1].time - messages[0].time); +} - const wsUrl = `ws://${server.HOST}/ws`; - const closed = page.evaluate(({ url, outgoing }) => new Promise(resolve => { - const ws = new WebSocket(url); - ws.addEventListener('open', () => ws.send(outgoing)); - ws.addEventListener('message', () => ws.close()); - ws.addEventListener('close', () => resolve()); - }), { url: wsUrl, outgoing }); - await closed; - const log = await getLog(); +it('should embed websocket messages', async ({ contextFactory, server }, testInfo) => { + await testWebSocketMessages(contextFactory, server, testInfo, 'embed'); +}); - const wsEntry = log.entries.find(e => e.request.url === wsUrl)! as Entry; - // 6 (base header) + 8 (64-bit extended length) for payload of exactly 2**16 bytes. - expect(wsEntry.response._transferSize).toBe(responseHeadersSize(wsEntry.response.headers) + 6 + 8 + incoming.length); +it('should attach websocket messages', async ({ contextFactory, server }, testInfo) => { + await testWebSocketMessages(contextFactory, server, testInfo, 'attach'); }); -it('should include binary websocket messages', async ({ contextFactory, server }, testInfo) => { - const incoming = [0x01, 0x02, 0x03, 0x04]; - const outgoing = [0x05, 0x06, 0x07, 0x08]; +it('should attach websocket messages for a still open websocket after stopping', async ({ contextFactory, server }, testInfo) => { + const incomingText = 'incoming'; + const incomingBinary = [0x01, 0x02, 0x03, 0x04]; + const outgoingText = 'outgoing'; + const outgoingBinary = [0x05, 0x06, 0x07, 0x08]; server.onceWebSocketConnection(ws => { - ws.on('message', () => ws.send(Buffer.from(incoming))); + let count = 0; + ws.on('message', () => ws.send((++count < 2) ? incomingText : Buffer.from(incomingBinary))); }); - const { page, getLog } = await pageWithHar(contextFactory, testInfo); + const { page, getZip } = await pageWithHar(contextFactory, testInfo, { content: 'attach', outputPath: 'test.har.zip' }); await page.goto(server.EMPTY_PAGE); const beforeMs = Date.now(); const wsUrl = `ws://${server.HOST}/ws`; - const closed = page.evaluate(({ url, outgoing }) => new Promise(resolve => { - const ws = new WebSocket(url); - ws.binaryType = 'arraybuffer'; - ws.addEventListener('open', () => ws.send(new Uint8Array(outgoing))); - ws.addEventListener('message', () => ws.close()); - ws.addEventListener('close', () => resolve()); - }), { url: wsUrl, outgoing }); - await closed; + const [ws] = await Promise.all([ + page.waitForEvent('websocket'), + page.evaluate(({ url, outgoingText, outgoingBinary }) => { + const ws = new WebSocket(url); + (window as any).ws = ws; + let count = 0; + ws.addEventListener('open', () => ws.send(outgoingText)); + ws.addEventListener('message', () => { + if (++count < 2) + ws.send(new Uint8Array(outgoingBinary)); + }); + }, { url: wsUrl, outgoingText, outgoingBinary }), + ]); + // Wait for all frames so the HAR tracer has observed them before the context is closed. + await ws.waitForEvent('framesent'); + await ws.waitForEvent('framereceived'); + await ws.waitForEvent('framesent'); + await ws.waitForEvent('framereceived'); const afterMs = Date.now(); - const log = await getLog(); + + // Do not close the WebSocket on the page side. Closing the context should still flush messages. + expect(await page.evaluate(() => (window as any).ws.readyState)).toBe(1 /* OPEN */); + + const zip = await getZip(); + const log = JSON.parse(zip.get('har.har')!.toString())['log'] as Log; const wsEntry = log.entries.find(e => e.request.url === wsUrl)! as Entry; - // The payload is short enough that they only need the minimum frame header size. - expect(wsEntry.response._transferSize).toBe(responseHeadersSize(wsEntry.response.headers) + 6 + incoming.length); + expect(wsEntry.response._transferSize).toBe(responseHeadersSize(wsEntry.response.headers) + messageSize(incomingText) + messageSize(incomingBinary)); + expect(wsEntry.time).toBeLessThanOrEqual(afterMs - beforeMs); + expect(wsEntry._webSocketMessages).toBeUndefined(); + + const file = wsEntry.response.content._file!; + expect(file).toMatch(/^[0-9a-f]{40}\.json$/); - const messages = wsEntry._webSocketMessages; - expect(messages.map(m => ({ type: m.type, opcode: m.opcode, data: [...Buffer.from(m.data, 'base64')] }))).toEqual([ - { type: 'send', opcode: 2, data: outgoing }, - { type: 'receive', opcode: 2, data: incoming }, + const messages = JSON.parse(zip.get(file)!.toString()) as Array<{ type: string, time: number, opcode: number, data: string }>; + expect(messages.map(m => ({ type: m.type, opcode: m.opcode, data: m.opcode === 1 ? m.data : [...Buffer.from(m.data, 'base64')] }))).toEqual([ + { type: 'send', opcode: 1, data: outgoingText }, + { type: 'receive', opcode: 1, data: incomingText }, + { type: 'send', opcode: 2, data: outgoingBinary }, + { type: 'receive', opcode: 2, data: incomingBinary }, ]); for (const m of messages) { expect(m.time).toBeGreaterThanOrEqual(beforeMs - 1); expect(m.time).toBeLessThanOrEqual(afterMs + 1); } expect(messages[0].time).toBeLessThanOrEqual(messages[1].time); + expect(wsEntry.time).toBeGreaterThanOrEqual(messages[messages.length - 1].time - messages[0].time); }); -it('should include websocket entry time across multiple messages', async ({ contextFactory, server }, testInfo) => { - server.onceWebSocketConnection(ws => { - ws.on('message', message => { - switch (message.toString()) { - case 'a': - setTimeout(() => ws.send('b'), 50); - break; - case 'b': - setTimeout(() => ws.send('c'), 50); - break; - case 'c': - setTimeout(() => ws.close(), 50); - break; - } - }); - }); - - const { page, getLog } = await pageWithHar(contextFactory, testInfo); - await page.goto(server.EMPTY_PAGE); - - const wsUrl = `ws://${server.HOST}/ws`; - const beforeMs = Date.now(); - const closed = page.evaluate(url => new Promise(resolve => { - const ws = new WebSocket(url); - ws.addEventListener('open', () => ws.send('a')); - ws.addEventListener('message', e => ws.send(e.data)); - ws.addEventListener('close', () => resolve()); - }), wsUrl); - await closed; - const afterMs = Date.now(); - const log = await getLog(); - - const wsEntry = log.entries.find(e => e.request.url === wsUrl)! as Entry; - const messages = wsEntry._webSocketMessages; - expect(wsEntry.time).toBeGreaterThanOrEqual(messages[messages.length - 1].time - messages[0].time); - expect(wsEntry.time).toBeLessThanOrEqual(afterMs - beforeMs); +it('should omit websocket messages', async ({ contextFactory, server }, testInfo) => { + await testWebSocketMessages(contextFactory, server, testInfo, 'omit'); }); it('should record websocket connection failure', async ({ contextFactory, server }, testInfo) => { From d4b930d743b3a44a37d9612d65024d04f79bf705 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 12:59:21 -0700 Subject: [PATCH 3/8] chore(deps-dev): bump hono from 4.12.18 to 4.12.23 (#41147) --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a490a41d52f7a..9c4daa791e215 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5863,9 +5863,9 @@ } }, "node_modules/hono": { - "version": "4.12.18", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz", - "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", + "version": "4.12.23", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.23.tgz", + "integrity": "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==", "dev": true, "license": "MIT", "engines": { From a545f4de804d830c54889c91edf0e91933d0d362 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 8 Jun 2026 21:58:20 +0100 Subject: [PATCH 4/8] test: fix electron bots (#41170) --- .github/actions/run-test/action.yml | 7 + .github/workflows/create_test_report.yml | 2 - .github/workflows/infra.yml | 3 - .github/workflows/publish_release.yml | 3 - .github/workflows/publish_release_docker.yml | 3 - .../roll_browser_into_playwright.yml | 3 +- .github/workflows/tests_components.yml | 1 - .github/workflows/tests_extension.yml | 1 - .github/workflows/tests_mcp.yml | 1 - .github/workflows/tests_others.yml | 4 +- .github/workflows/tests_primary.yml | 1 - .github/workflows/tests_secondary.yml | 1 - .github/workflows/tests_webview_simulator.yml | 1 - package-lock.json | 469 ++---------------- package.json | 2 +- .../playwright-electron-should-work.spec.ts | 24 +- tests/library/browsercontext-route.spec.ts | 44 ++ tests/page/page-network-response.spec.ts | 28 -- tests/page/page-request-intercept.spec.ts | 16 - tests/page/page-screenshot.spec.ts | 4 +- tests/page/workers.spec.ts | 1 + 21 files changed, 119 insertions(+), 500 deletions(-) diff --git a/.github/actions/run-test/action.yml b/.github/actions/run-test/action.yml index 48c60261c1192..247a49ba3919e 100644 --- a/.github/actions/run-test/action.yml +++ b/.github/actions/run-test/action.yml @@ -1,6 +1,9 @@ name: 'Run browser tests' description: 'Run browser tests' inputs: + setup-command: + description: 'Extra setup command to run before tests' + required: false command: description: 'Command to run tests' required: true @@ -56,6 +59,10 @@ runs: shell: bash - run: echo "PLAYWRIGHT_SETUP_COMPLETE=true" >> $GITHUB_ENV shell: bash + - name: Run setup command + if: inputs.setup-command != '' + run: ${{ inputs.setup-command }} + shell: bash - name: Run tests if: inputs.shell == 'bash' run: | diff --git a/.github/workflows/create_test_report.yml b/.github/workflows/create_test_report.yml index 6ae005bd40d3a..d5cb75c26b043 100644 --- a/.github/workflows/create_test_report.yml +++ b/.github/workflows/create_test_report.yml @@ -20,8 +20,6 @@ jobs: with: node-version: 20 - run: npm ci - env: - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - run: npm run build - name: Download blob report artifact diff --git a/.github/workflows/infra.yml b/.github/workflows/infra.yml index 40f281c7d680c..8f23797f24d66 100644 --- a/.github/workflows/infra.yml +++ b/.github/workflows/infra.yml @@ -10,9 +10,6 @@ on: - main - release-* -env: - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - jobs: doc-and-lint: name: "docs & lint" diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index c620ea5cd3a3b..07eb51e35d67c 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -10,9 +10,6 @@ on: release: types: [published] -env: - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - jobs: publish-npm-and-driver: name: "publish NPM and driver" diff --git a/.github/workflows/publish_release_docker.yml b/.github/workflows/publish_release_docker.yml index adf58d00d190e..065fc8fb1b6b1 100644 --- a/.github/workflows/publish_release_docker.yml +++ b/.github/workflows/publish_release_docker.yml @@ -5,9 +5,6 @@ on: release: types: [published] -env: - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - jobs: publish-docker-release: name: "publish to DockerHub" diff --git a/.github/workflows/roll_browser_into_playwright.yml b/.github/workflows/roll_browser_into_playwright.yml index 0452f84ae1fe8..ac845ff2da4e6 100644 --- a/.github/workflows/roll_browser_into_playwright.yml +++ b/.github/workflows/roll_browser_into_playwright.yml @@ -5,7 +5,6 @@ on: types: [roll_into_pw] env: - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 BROWSER: ${{ github.event.client_payload.browser }} REVISION: ${{ github.event.client_payload.revision }} BROWSER_VERSION: ${{ github.event.client_payload.browserVersion }} @@ -13,7 +12,7 @@ env: permissions: contents: write -concurrency: +concurrency: group: 'roll-browser-into-playwright-${{ github.event.client_payload.browser }}-${{ github.event.client_payload.revision }}' jobs: diff --git a/.github/workflows/tests_components.yml b/.github/workflows/tests_components.yml index 585796c280070..e525ea0de3bec 100644 --- a/.github/workflows/tests_components.yml +++ b/.github/workflows/tests_components.yml @@ -21,7 +21,6 @@ on: env: FORCE_COLOR: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 jobs: test_components: diff --git a/.github/workflows/tests_extension.yml b/.github/workflows/tests_extension.yml index 3a7b806c5aa2e..c55d28e3dc1fa 100644 --- a/.github/workflows/tests_extension.yml +++ b/.github/workflows/tests_extension.yml @@ -30,7 +30,6 @@ concurrency: env: FORCE_COLOR: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 DEBUG: pw:mcp:error jobs: diff --git a/.github/workflows/tests_mcp.yml b/.github/workflows/tests_mcp.yml index 3e1c7e7cbb5cb..8a5d04390435b 100644 --- a/.github/workflows/tests_mcp.yml +++ b/.github/workflows/tests_mcp.yml @@ -26,7 +26,6 @@ concurrency: env: # Force terminal colors. @see https://www.npmjs.com/package/colors FORCE_COLOR: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 DEBUG: pw:mcp:error jobs: diff --git a/.github/workflows/tests_others.yml b/.github/workflows/tests_others.yml index 5e218e165c2cf..10dc21520056e 100644 --- a/.github/workflows/tests_others.yml +++ b/.github/workflows/tests_others.yml @@ -18,7 +18,6 @@ on: env: FORCE_COLOR: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 jobs: test_clock_frozen_time_linux: @@ -93,10 +92,9 @@ jobs: - uses: ./.github/actions/run-test with: browsers-to-install: chromium + setup-command: npx install-electron --no command: npm run etest bot-name: "electron-${{ matrix.os }}" flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }} flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }} flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} - env: - ELECTRON_SKIP_BINARY_DOWNLOAD: diff --git a/.github/workflows/tests_primary.yml b/.github/workflows/tests_primary.yml index 142cab8343aff..6dd861c1e2703 100644 --- a/.github/workflows/tests_primary.yml +++ b/.github/workflows/tests_primary.yml @@ -28,7 +28,6 @@ concurrency: env: # Force terminal colors. @see https://www.npmjs.com/package/colors FORCE_COLOR: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 jobs: test_linux: diff --git a/.github/workflows/tests_secondary.yml b/.github/workflows/tests_secondary.yml index 401ff521c67d2..bf63d6b33700a 100644 --- a/.github/workflows/tests_secondary.yml +++ b/.github/workflows/tests_secondary.yml @@ -14,7 +14,6 @@ on: env: # Force terminal colors. @see https://www.npmjs.com/package/colors FORCE_COLOR: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 permissions: id-token: write # This is required for OIDC login (azure/login) to succeed diff --git a/.github/workflows/tests_webview_simulator.yml b/.github/workflows/tests_webview_simulator.yml index 607e5ddd0a5a7..610ef3f755289 100644 --- a/.github/workflows/tests_webview_simulator.yml +++ b/.github/workflows/tests_webview_simulator.yml @@ -14,7 +14,6 @@ concurrency: env: FORCE_COLOR: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 jobs: test_webview_simulator: diff --git a/package-lock.json b/package-lock.json index 9c4daa791e215..87beadabd7f41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,7 +83,7 @@ "debug": "^4.3.4", "diff": "^8.0.3", "dotenv": "^16.4.5", - "electron": "^40.10.2", + "electron": "^42.3.3", "enquirer": "2.3.6", "esbuild": "^0.25.0", "eslint": "^9.34.0", @@ -1037,33 +1037,42 @@ "integrity": "sha512-diidPiK62E4hlAh0dyLfWQDZXi2SSAGiOuw6iqD1x8ztw7L/Sz3He46FhcxEzYa1hKi1blCkjnKDjqw6rQfgcA==" }, "node_modules/@electron/get": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", - "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.0.0.tgz", + "integrity": "sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.1.1", - "env-paths": "^2.2.0", - "fs-extra": "^8.1.0", - "got": "^11.8.5", + "env-paths": "^3.0.0", + "graceful-fs": "^4.2.11", "progress": "^2.0.3", - "semver": "^6.2.0", + "semver": "^7.6.3", "sumchecker": "^3.0.1" }, "engines": { - "node": ">=12" + "node": ">=22.12.0" }, "optionalDependencies": { - "global-agent": "^3.0.0" + "undici": "^7.24.4" } }, - "node_modules/@electron/get/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@electron/get/node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "license": "ISC" + }, + "node_modules/@electron/get/node_modules/undici": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.27.1.tgz", + "integrity": "sha512-UDdpiex+mzigiyrXrGbiUaF4HzTNhKbh2vRNFaTMzcqmLIPrZxaCtwo/1TMSuWoM1Xz3WiTo9KdgI3kRqYzJGg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20.18.1" } }, "node_modules/@esbuild/aix-ppc64": { @@ -2411,18 +2420,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, "node_modules/@stylistic/eslint-plugin": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.3.1.tgz", @@ -2443,18 +2440,6 @@ "eslint": ">=9.0.0" } }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@types/babel__code-frame": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@types/babel__code-frame/-/babel__code-frame-7.27.0.tgz", @@ -2509,18 +2494,6 @@ "@babel/types": "^7.28.2" } }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, "node_modules/@types/chrome": { "version": "0.0.315", "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.315.tgz", @@ -2596,12 +2569,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "dev": true - }, "node_modules/@types/ini": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz", @@ -2649,15 +2616,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/mime": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", @@ -2738,15 +2696,6 @@ "@types/react": "^19.2.0" } }, - "node_modules/@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/retry": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz", @@ -3702,14 +3651,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/boolean": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", - "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, - "optional": true - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -3789,33 +3730,6 @@ "node": ">= 0.8" } }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true, - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -3977,18 +3891,6 @@ "wrap-ansi": "^7.0.0" } }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/codemirror": { "version": "5.65.18", "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.18.tgz", @@ -4253,48 +4155,12 @@ "node": "*" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -4349,13 +4215,6 @@ "node": ">= 0.8" } }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true, - "optional": true - }, "node_modules/devtools-protocol": { "version": "0.0.1510116", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1510116.tgz", @@ -4429,22 +4288,22 @@ "license": "MIT" }, "node_modules/electron": { - "version": "40.10.2", - "resolved": "https://registry.npmjs.org/electron/-/electron-40.10.2.tgz", - "integrity": "sha512-Xj3Hy0Imbu4g0gDIW55w/jJYz94nMO2JRSGYA3LyAn5SwaERCelgZrA21vfH+Bi//SWAWQXddHsMwCqauyMT8g==", + "version": "42.3.3", + "resolved": "https://registry.npmjs.org/electron/-/electron-42.3.3.tgz", + "integrity": "sha512-0MwYp9wTb7TrtTalOYqeW+suqd9T/Znstr/nDLKqFGIjHdBZX339guo3mQqTPURRZ/UQmYM4uMpzKpI5wLptfQ==", "dev": true, - "hasInstallScript": true, "license": "MIT", "dependencies": { - "@electron/get": "^2.0.0", + "@electron/get": "^5.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { - "electron": "cli.js" + "electron": "cli.js", + "install-electron": "install.js" }, "engines": { - "node": ">= 12.20.55" + "node": ">= 22.12.0" } }, "node_modules/electron-to-chromium": { @@ -4520,12 +4379,16 @@ } }, "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/es-abstract": { @@ -4697,13 +4560,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true, - "optional": true - }, "node_modules/esbuild": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", @@ -5458,20 +5314,6 @@ "node": ">= 0.8" } }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5661,24 +5503,6 @@ "node": ">= 6" } }, - "node_modules/global-agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", - "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", - "dev": true, - "optional": true, - "dependencies": { - "boolean": "^3.0.1", - "es6-error": "^4.1.1", - "matcher": "^3.0.0", - "roarr": "^2.15.3", - "semver": "^7.3.2", - "serialize-error": "^7.0.1" - }, - "engines": { - "node": ">=10.0" - } - }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -5719,31 +5543,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -5882,12 +5681,6 @@ "resolved": "packages/html-reporter", "link": true }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "dev": true - }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -5909,19 +5702,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -6647,13 +6427,6 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "optional": true - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -6665,15 +6438,6 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -6861,15 +6625,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -6887,19 +6642,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/matcher": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", - "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", - "dev": true, - "optional": true, - "dependencies": { - "escape-string-regexp": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -7004,15 +6746,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -7139,18 +6872,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm-normalize-package-bin": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", @@ -7375,15 +7096,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -7717,18 +7429,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -7955,12 +7655,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -7970,18 +7664,6 @@ "node": ">=4" } }, - "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "dependencies": { - "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -7992,24 +7674,6 @@ "node": ">= 4" } }, - "node_modules/roarr": { - "version": "2.15.4", - "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", - "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", - "dev": true, - "optional": true, - "dependencies": { - "boolean": "^3.0.1", - "detect-node": "^2.0.4", - "globalthis": "^1.0.1", - "json-stringify-safe": "^5.0.1", - "semver-compare": "^1.0.0", - "sprintf-js": "^1.1.2" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/rollup": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", @@ -8174,13 +7838,6 @@ "node": ">=10" } }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", - "dev": true, - "optional": true - }, "node_modules/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", @@ -8208,22 +7865,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, - "optional": true, - "dependencies": { - "type-fest": "^0.13.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/serve-static": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", @@ -8552,13 +8193,6 @@ "spdx-ranges": "^2.0.0" } }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true, - "optional": true - }, "node_modules/ssim.js": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/ssim.js/-/ssim.js-3.5.0.tgz", @@ -8756,6 +8390,7 @@ "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "debug": "^4.1.0" }, @@ -8914,19 +8549,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -9071,15 +8693,6 @@ "dev": true, "license": "ISC" }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index d7cf36693ecae..148f7a504f617 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "debug": "^4.3.4", "diff": "^8.0.3", "dotenv": "^16.4.5", - "electron": "^40.10.2", + "electron": "^42.3.3", "enquirer": "2.3.6", "esbuild": "^0.25.0", "eslint": "^9.34.0", diff --git a/tests/installation/playwright-electron-should-work.spec.ts b/tests/installation/playwright-electron-should-work.spec.ts index 667e22e624543..d7a1cd3a99432 100755 --- a/tests/installation/playwright-electron-should-work.spec.ts +++ b/tests/installation/playwright-electron-should-work.spec.ts @@ -18,8 +18,20 @@ import fs from 'fs'; import { expect } from '../../packages/playwright-test'; import path from 'path'; -test('electron should work', async ({ exec, tsc, writeFiles }) => { - await exec('npm i @playwright/test electron@40.10.2'); +// Electron 42's extract-zip pulls in an incompatible yauzl on newer Node.js, breaking install. +// Pin yauzl to a known-good version via package.json overrides. +// https://github.com/electron/electron/issues/51619 +async function pinYauzl(tmpWorkspace: string) { + const pkgPath = path.join(tmpWorkspace, 'package.json'); + const pkg = JSON.parse(await fs.promises.readFile(pkgPath, 'utf-8')); + pkg.overrides = { ...pkg.overrides, yauzl: '^3.3.2' }; + await fs.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2)); +} + +test('electron should work', async ({ exec, tsc, writeFiles, tmpWorkspace }) => { + await pinYauzl(tmpWorkspace); + await exec('npm i @playwright/test electron@42.3.3'); + await exec('npx install-electron --no'); await exec('node sanity-electron.js'); await writeFiles({ 'test.ts': @@ -32,7 +44,9 @@ test('electron should work with special characters in path', async ({ exec, tmpW test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30755' }); const folderName = path.join(tmpWorkspace, '!@#$% тест with spaces and 😊'); - await exec('npm i @playwright/test electron@40.10.2'); + await pinYauzl(tmpWorkspace); + await exec('npm i @playwright/test electron@42.3.3'); + await exec('npx install-electron --no'); await fs.promises.mkdir(folderName); for (const file of ['electron-app.js', 'sanity-electron.js']) await fs.promises.copyFile(path.join(tmpWorkspace, file), path.join(folderName, file)); @@ -42,7 +56,9 @@ test('electron should work with special characters in path', async ({ exec, tmpW }); test('should work when wrapped inside @playwright/test and trace is enabled', async ({ exec, tmpWorkspace, writeFiles }) => { - await exec('npm i -D @playwright/test electron@40.10.2'); + await pinYauzl(tmpWorkspace); + await exec('npm i -D @playwright/test electron@42.3.3'); + await exec('npx install-electron --no'); await writeFiles({ 'electron-with-tracing.spec.ts': ` import { test, expect, _electron as electron } from '@playwright/test'; diff --git a/tests/library/browsercontext-route.spec.ts b/tests/library/browsercontext-route.spec.ts index f3ff5538883aa..93e3714980cb8 100644 --- a/tests/library/browsercontext-route.spec.ts +++ b/tests/library/browsercontext-route.spec.ts @@ -387,3 +387,47 @@ it('should fall back async', async ({ page, context, server }) => { await page.goto(server.EMPTY_PAGE); expect(intercepted).toEqual([3, 2, 1]); }); + +it('should bypass disk cache when context interception is enabled', async ({ page, server }) => { + it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30000' }); + await page.context().route('**/api*', route => route.continue()); + await page.goto(server.PREFIX + '/frames/one-frame.html'); + { + const requests = []; + server.setRoute('/api', (req, res) => { + requests.push(req); + res.statusCode = 200; + res.setHeader('content-type', 'text/plain'); + res.setHeader('cache-control', 'public, max-age=31536000'); + res.end('Hello'); + }); + for (let i = 0; i < 3; i++) { + await it.step(`main frame iteration ${i}`, async () => { + const respPromise = page.waitForResponse('**/api'); + await page.evaluate(async () => { + const response = await fetch('/api'); + return response.status; + }); + const response = await respPromise; + expect(response.status()).toBe(200); + expect(requests.length).toBe(i + 1); + }); + } + } +}); + +it('should fulfill popup main request using alias', async ({ page, server, isElectron, electronMajorVersion, isAndroid }) => { + it.skip(isElectron && electronMajorVersion < 30, 'error: Browser context management is not supported.'); + it.skip(isAndroid, 'The internal Android localhost (10.0.0.2) != the localhost on the host'); + + await page.context().route('**/*', async route => { + const response = await route.fetch(); + await route.fulfill({ response, body: 'hello' }); + }); + await page.setContent(`click me`); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.getByText('click me').click(), + ]); + await expect(popup.locator('body')).toHaveText('hello'); +}); diff --git a/tests/page/page-network-response.spec.ts b/tests/page/page-network-response.spec.ts index d6ad9f9715f5a..7eb7e23024486 100644 --- a/tests/page/page-network-response.spec.ts +++ b/tests/page/page-network-response.spec.ts @@ -380,34 +380,6 @@ it('should bypass disk cache when page interception is enabled', async ({ page, } }); -it('should bypass disk cache when context interception is enabled', async ({ page, server }) => { - it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30000' }); - await page.context().route('**/api*', route => route.continue()); - await page.goto(server.PREFIX + '/frames/one-frame.html'); - { - const requests = []; - server.setRoute('/api', (req, res) => { - requests.push(req); - res.statusCode = 200; - res.setHeader('content-type', 'text/plain'); - res.setHeader('cache-control', 'public, max-age=31536000'); - res.end('Hello'); - }); - for (let i = 0; i < 3; i++) { - await it.step(`main frame iteration ${i}`, async () => { - const respPromise = page.waitForResponse('**/api'); - await page.evaluate(async () => { - const response = await fetch('/api'); - return response.status; - }); - const response = await respPromise; - expect(response.status()).toBe(200); - expect(requests.length).toBe(i + 1); - }); - } - } -}); - it('request.existingResponse should return null before response is received', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); let serverResponse = null; diff --git a/tests/page/page-request-intercept.spec.ts b/tests/page/page-request-intercept.spec.ts index 3ea6655566de9..34197011c4d60 100644 --- a/tests/page/page-request-intercept.spec.ts +++ b/tests/page/page-request-intercept.spec.ts @@ -266,22 +266,6 @@ it('should intercept with post data override', async ({ page, server, isElectron expect((await request.postBody).toString()).toBe(JSON.stringify({ 'foo': 'bar' })); }); -it('should fulfill popup main request using alias', async ({ page, server, isElectron, electronMajorVersion, isAndroid }) => { - it.skip(isElectron && electronMajorVersion < 30, 'error: Browser context management is not supported.'); - it.skip(isAndroid, 'The internal Android localhost (10.0.0.2) != the localhost on the host'); - - await page.context().route('**/*', async route => { - const response = await route.fetch(); - await route.fulfill({ response, body: 'hello' }); - }); - await page.setContent(`click me`); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.getByText('click me').click(), - ]); - await expect(popup.locator('body')).toHaveText('hello'); -}); - it('request.postData is not null when fetching FormData with a Blob', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/24077' } }, async ({ server, page, browserName, isElectron, electronMajorVersion, isAndroid }) => { diff --git a/tests/page/page-screenshot.spec.ts b/tests/page/page-screenshot.spec.ts index a2f7b77b3c8a5..10294a304ce06 100644 --- a/tests/page/page-screenshot.spec.ts +++ b/tests/page/page-screenshot.spec.ts @@ -318,8 +318,10 @@ it.describe('page screenshot', () => { } }); - it('should work for webgl', async ({ page, server, browserName, platform }) => { + it('should work for webgl', async ({ page, server, browserName, platform, isElectron }) => { it.skip(browserName === 'webkit' && platform === 'darwin' && os.arch() === 'x64', 'WebGL is not available on Intel macOS - https://bugs.webkit.org/show_bug.cgi?id=278277'); + it.skip(isElectron, 'different rendering in electron'); + await page.setViewportSize({ width: 640, height: 480 }); await page.goto(server.PREFIX + '/screenshots/webgl.html'); const screenshot = await page.screenshot(); diff --git a/tests/page/workers.spec.ts b/tests/page/workers.spec.ts index 59477ac93c6f6..1544565ef3f35 100644 --- a/tests/page/workers.spec.ts +++ b/tests/page/workers.spec.ts @@ -189,6 +189,7 @@ it('should clear upon cross-process navigation', async function({ server, page } it('should attribute network activity for worker inside iframe to the iframe', async function({ page, server, browserName, browserMajorVersion }) { it.skip(browserName === 'firefox' && browserMajorVersion < 114, 'https://github.com/microsoft/playwright/issues/21760'); + it.skip(browserName === 'chromium' && browserMajorVersion < 149, 'needs TargetInfo.parentFrameId for workers'); await page.goto(server.PREFIX + '/empty.html'); const [worker, frame] = await Promise.all([ From 123cc42c8f9619b836633869d2cca1bb66e93ac7 Mon Sep 17 00:00:00 2001 From: Jeff Muizelaar Date: Mon, 8 Jun 2026 17:22:19 -0400 Subject: [PATCH 5/8] fix(mcp): support moz-firefox BiDi channels via --browser (#41126) --- packages/playwright-core/src/tools/mcp/config.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/playwright-core/src/tools/mcp/config.ts b/packages/playwright-core/src/tools/mcp/config.ts index c90c1cf36c9e9..f1994e4ea0a37 100644 --- a/packages/playwright-core/src/tools/mcp/config.ts +++ b/packages/playwright-core/src/tools/mcp/config.ts @@ -265,6 +265,10 @@ function resolveBrowserParam(browserOption: string | undefined): { browserName?: return { browserName: 'chromium', channel: 'chrome-for-testing' }; case 'firefox': return { browserName: 'firefox' }; + case 'moz-firefox': + case 'moz-firefox-beta': + case 'moz-firefox-nightly': + return { browserName: 'firefox', channel: browserOption }; case 'webkit': return { browserName: 'webkit' }; default: @@ -472,7 +476,9 @@ function mergeConfig(base: MergedConfig, overrides: Config): MergedConfig { }, }; - if (browser.browserName !== 'chromium' && browser.launchOptions) + // Firefox supports the `moz-firefox*` channels via WebDriver BiDi, so keep + // those; otherwise channels are a Chromium-only concept and should be dropped. + if (browser.browserName !== 'chromium' && browser.launchOptions && !browser.launchOptions.channel?.startsWith('moz-')) delete browser.launchOptions.channel; return { From c71f5d1926e41a01e5e5ba20d1936b436b6a481c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Jun 2026 14:26:38 -0700 Subject: [PATCH 6/8] Deduplicate `ElectronApplication` declaration in generated type sources (#41199) --- packages/playwright-client/types/types.d.ts | 528 -------------------- packages/playwright-core/types/types.d.ts | 528 -------------------- utils/generate_types/overrides.d.ts | 11 - 3 files changed, 1067 deletions(-) diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index e880a095ae58b..26912a6eadd49 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -16897,26 +16897,6 @@ type ElectronType = typeof import('electron'); * */ export interface ElectronApplication { - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a [Promise], then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * would wait for the promise to resolve and return its value. - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a non-[Serializable] value, then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns `undefined`. Playwright also supports transferring some additional values that are not serializable by - * `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - */ /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). @@ -16938,26 +16918,6 @@ export interface ElectronApplication { * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). */ evaluate(pageFunction: PageFunctionOn, arg: Arg): Promise; - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a [Promise], then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * would wait for the promise to resolve and return its value. - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a non-[Serializable] value, then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns `undefined`. Playwright also supports transferring some additional values that are not serializable by - * `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - */ /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). @@ -16980,28 +16940,6 @@ export interface ElectronApplication { */ evaluate(pageFunction: PageFunctionOn, arg?: any): Promise; - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) - * as a [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * The only difference between - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * and - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * is that - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * If the function passed to the - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns a [Promise], then - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * would wait for the promise to resolve and return its value. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression). - */ /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) @@ -17025,28 +16963,6 @@ export interface ElectronApplication { * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression). */ evaluateHandle(pageFunction: PageFunctionOn, arg: Arg): Promise>; - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) - * as a [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * The only difference between - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * and - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * is that - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * If the function passed to the - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns a [Promise], then - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * would wait for the promise to resolve and return its value. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression). - */ /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) @@ -17409,450 +17325,6 @@ export type AndroidKey = export const _electron: Electron; export const _android: Android; -//@ts-ignore this will be any if electron is not installed -type ElectronType = typeof import('electron'); - -/** - * Electron application representation. You can use - * [electron.launch([options])](https://playwright.dev/docs/api/class-electron#electron-launch) to obtain the - * application instance. This instance you can control main electron process as well as work with Electron windows: - * - * ```js - * const { _electron: electron } = require('playwright'); - * - * (async () => { - * // Launch Electron app. - * const electronApp = await electron.launch({ args: ['main.js'] }); - * - * // Evaluation expression in the Electron context. - * const appPath = await electronApp.evaluate(async ({ app }) => { - * // This runs in the main Electron process, parameter here is always - * // the result of the require('electron') in the main app script. - * return app.getAppPath(); - * }); - * console.log(appPath); - * - * // Get the first window that the app opens, wait if necessary. - * const window = await electronApp.firstWindow(); - * // Print the title. - * console.log(await window.title()); - * // Capture a screenshot. - * await window.screenshot({ path: 'intro.png' }); - * // Direct Electron console to Node terminal. - * window.on('console', console.log); - * // Click button. - * await window.click('text=Click me'); - * // Exit app. - * await electronApp.close(); - * })(); - * ``` - * - */ -export interface ElectronApplication { - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a [Promise], then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * would wait for the promise to resolve and return its value. - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a non-[Serializable] value, then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns `undefined`. Playwright also supports transferring some additional values that are not serializable by - * `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - */ - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a [Promise], then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * would wait for the promise to resolve and return its value. - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a non-[Serializable] value, then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns `undefined`. Playwright also supports transferring some additional values that are not serializable by - * `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - */ - evaluate(pageFunction: PageFunctionOn, arg: Arg): Promise; - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a [Promise], then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * would wait for the promise to resolve and return its value. - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a non-[Serializable] value, then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns `undefined`. Playwright also supports transferring some additional values that are not serializable by - * `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - */ - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a [Promise], then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * would wait for the promise to resolve and return its value. - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a non-[Serializable] value, then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns `undefined`. Playwright also supports transferring some additional values that are not serializable by - * `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - */ - evaluate(pageFunction: PageFunctionOn, arg?: any): Promise; - - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) - * as a [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * The only difference between - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * and - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * is that - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * If the function passed to the - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns a [Promise], then - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * would wait for the promise to resolve and return its value. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression). - */ - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) - * as a [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * The only difference between - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * and - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * is that - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * If the function passed to the - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns a [Promise], then - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * would wait for the promise to resolve and return its value. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression). - */ - evaluateHandle(pageFunction: PageFunctionOn, arg: Arg): Promise>; - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) - * as a [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * The only difference between - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * and - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * is that - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * If the function passed to the - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns a [Promise], then - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * would wait for the promise to resolve and return its value. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression). - */ - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) - * as a [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * The only difference between - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * and - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * is that - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * If the function passed to the - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns a [Promise], then - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * would wait for the promise to resolve and return its value. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression). - */ - evaluateHandle(pageFunction: PageFunctionOn, arg?: any): Promise>; - /** - * This event is issued when the application process has been terminated. - */ - on(event: 'close', listener: () => any): this; - - /** - * Emitted when JavaScript within the Electron main process calls one of console API methods, e.g. `console.log` or - * `console.dir`. - * - * The arguments passed into `console.log` are available on the - * [ConsoleMessage](https://playwright.dev/docs/api/class-consolemessage) event handler argument. - * - * **Usage** - * - * ```js - * electronApp.on('console', async msg => { - * const values = []; - * for (const arg of msg.args()) - * values.push(await arg.jsonValue()); - * console.log(...values); - * }); - * await electronApp.evaluate(() => console.log('hello', 5, { foo: 'bar' })); - * ``` - * - */ - on(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; - - /** - * This event is issued for every window that is created **and loaded** in Electron. It contains a - * [Page](https://playwright.dev/docs/api/class-page) that can be used for Playwright automation. - */ - on(event: 'window', listener: (page: Page) => any): this; - - /** - * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. - */ - once(event: 'close', listener: () => any): this; - - /** - * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. - */ - once(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; - - /** - * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. - */ - once(event: 'window', listener: (page: Page) => any): this; - - /** - * This event is issued when the application process has been terminated. - */ - addListener(event: 'close', listener: () => any): this; - - /** - * Emitted when JavaScript within the Electron main process calls one of console API methods, e.g. `console.log` or - * `console.dir`. - * - * The arguments passed into `console.log` are available on the - * [ConsoleMessage](https://playwright.dev/docs/api/class-consolemessage) event handler argument. - * - * **Usage** - * - * ```js - * electronApp.on('console', async msg => { - * const values = []; - * for (const arg of msg.args()) - * values.push(await arg.jsonValue()); - * console.log(...values); - * }); - * await electronApp.evaluate(() => console.log('hello', 5, { foo: 'bar' })); - * ``` - * - */ - addListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; - - /** - * This event is issued for every window that is created **and loaded** in Electron. It contains a - * [Page](https://playwright.dev/docs/api/class-page) that can be used for Playwright automation. - */ - addListener(event: 'window', listener: (page: Page) => any): this; - - /** - * Removes an event listener added by `on` or `addListener`. - */ - removeListener(event: 'close', listener: () => any): this; - - /** - * Removes an event listener added by `on` or `addListener`. - */ - removeListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; - - /** - * Removes an event listener added by `on` or `addListener`. - */ - removeListener(event: 'window', listener: (page: Page) => any): this; - - /** - * Removes an event listener added by `on` or `addListener`. - */ - off(event: 'close', listener: () => any): this; - - /** - * Removes an event listener added by `on` or `addListener`. - */ - off(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; - - /** - * Removes an event listener added by `on` or `addListener`. - */ - off(event: 'window', listener: (page: Page) => any): this; - - /** - * This event is issued when the application process has been terminated. - */ - prependListener(event: 'close', listener: () => any): this; - - /** - * Emitted when JavaScript within the Electron main process calls one of console API methods, e.g. `console.log` or - * `console.dir`. - * - * The arguments passed into `console.log` are available on the - * [ConsoleMessage](https://playwright.dev/docs/api/class-consolemessage) event handler argument. - * - * **Usage** - * - * ```js - * electronApp.on('console', async msg => { - * const values = []; - * for (const arg of msg.args()) - * values.push(await arg.jsonValue()); - * console.log(...values); - * }); - * await electronApp.evaluate(() => console.log('hello', 5, { foo: 'bar' })); - * ``` - * - */ - prependListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; - - /** - * This event is issued for every window that is created **and loaded** in Electron. It contains a - * [Page](https://playwright.dev/docs/api/class-page) that can be used for Playwright automation. - */ - prependListener(event: 'window', listener: (page: Page) => any): this; - - /** - * Returns the BrowserWindow object that corresponds to the given Playwright page. - * @param page Page to retrieve the window for. - */ - browserWindow(page: Page): Promise; - - /** - * Closes Electron application. - */ - close(): Promise; - - /** - * This method returns browser context that can be used for setting up context-wide routing, etc. - */ - context(): BrowserContext; - - /** - * Convenience method that waits for the first application window to be opened. - * - * **Usage** - * - * ```js - * const electronApp = await electron.launch({ - * args: ['main.js'] - * }); - * const window = await electronApp.firstWindow(); - * // ... - * ``` - * - * @param options - */ - firstWindow(options?: { - /** - * Maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The - * default value can be changed by using the - * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout). - */ - timeout?: number; - }): Promise; - - /** - * Returns the main process for this Electron Application. - */ - process(): ChildProcess; - - /** - * This event is issued when the application process has been terminated. - */ - waitForEvent(event: 'close', optionsOrPredicate?: { predicate?: () => boolean | Promise, timeout?: number, signal?: AbortSignal } | (() => boolean | Promise)): Promise; - - /** - * Emitted when JavaScript within the Electron main process calls one of console API methods, e.g. `console.log` or - * `console.dir`. - * - * The arguments passed into `console.log` are available on the - * [ConsoleMessage](https://playwright.dev/docs/api/class-consolemessage) event handler argument. - * - * **Usage** - * - * ```js - * electronApp.on('console', async msg => { - * const values = []; - * for (const arg of msg.args()) - * values.push(await arg.jsonValue()); - * console.log(...values); - * }); - * await electronApp.evaluate(() => console.log('hello', 5, { foo: 'bar' })); - * ``` - * - */ - waitForEvent(event: 'console', optionsOrPredicate?: { predicate?: (consoleMessage: ConsoleMessage) => boolean | Promise, timeout?: number, signal?: AbortSignal } | ((consoleMessage: ConsoleMessage) => boolean | Promise)): Promise; - - /** - * This event is issued for every window that is created **and loaded** in Electron. It contains a - * [Page](https://playwright.dev/docs/api/class-page) that can be used for Playwright automation. - */ - waitForEvent(event: 'window', optionsOrPredicate?: { predicate?: (page: Page) => boolean | Promise, timeout?: number, signal?: AbortSignal } | ((page: Page) => boolean | Promise)): Promise; - - - /** - * Convenience method that returns all the opened windows. - */ - windows(): Array; - - [Symbol.asyncDispose](): Promise; -} - // This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 export {}; diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index e880a095ae58b..26912a6eadd49 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -16897,26 +16897,6 @@ type ElectronType = typeof import('electron'); * */ export interface ElectronApplication { - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a [Promise], then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * would wait for the promise to resolve and return its value. - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a non-[Serializable] value, then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns `undefined`. Playwright also supports transferring some additional values that are not serializable by - * `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - */ /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). @@ -16938,26 +16918,6 @@ export interface ElectronApplication { * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). */ evaluate(pageFunction: PageFunctionOn, arg: Arg): Promise; - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a [Promise], then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * would wait for the promise to resolve and return its value. - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a non-[Serializable] value, then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns `undefined`. Playwright also supports transferring some additional values that are not serializable by - * `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - */ /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). @@ -16980,28 +16940,6 @@ export interface ElectronApplication { */ evaluate(pageFunction: PageFunctionOn, arg?: any): Promise; - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) - * as a [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * The only difference between - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * and - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * is that - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * If the function passed to the - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns a [Promise], then - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * would wait for the promise to resolve and return its value. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression). - */ /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) @@ -17025,28 +16963,6 @@ export interface ElectronApplication { * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression). */ evaluateHandle(pageFunction: PageFunctionOn, arg: Arg): Promise>; - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) - * as a [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * The only difference between - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * and - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * is that - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * If the function passed to the - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns a [Promise], then - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * would wait for the promise to resolve and return its value. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression). - */ /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) @@ -17409,450 +17325,6 @@ export type AndroidKey = export const _electron: Electron; export const _android: Android; -//@ts-ignore this will be any if electron is not installed -type ElectronType = typeof import('electron'); - -/** - * Electron application representation. You can use - * [electron.launch([options])](https://playwright.dev/docs/api/class-electron#electron-launch) to obtain the - * application instance. This instance you can control main electron process as well as work with Electron windows: - * - * ```js - * const { _electron: electron } = require('playwright'); - * - * (async () => { - * // Launch Electron app. - * const electronApp = await electron.launch({ args: ['main.js'] }); - * - * // Evaluation expression in the Electron context. - * const appPath = await electronApp.evaluate(async ({ app }) => { - * // This runs in the main Electron process, parameter here is always - * // the result of the require('electron') in the main app script. - * return app.getAppPath(); - * }); - * console.log(appPath); - * - * // Get the first window that the app opens, wait if necessary. - * const window = await electronApp.firstWindow(); - * // Print the title. - * console.log(await window.title()); - * // Capture a screenshot. - * await window.screenshot({ path: 'intro.png' }); - * // Direct Electron console to Node terminal. - * window.on('console', console.log); - * // Click button. - * await window.click('text=Click me'); - * // Exit app. - * await electronApp.close(); - * })(); - * ``` - * - */ -export interface ElectronApplication { - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a [Promise], then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * would wait for the promise to resolve and return its value. - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a non-[Serializable] value, then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns `undefined`. Playwright also supports transferring some additional values that are not serializable by - * `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - */ - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a [Promise], then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * would wait for the promise to resolve and return its value. - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a non-[Serializable] value, then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns `undefined`. Playwright also supports transferring some additional values that are not serializable by - * `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - */ - evaluate(pageFunction: PageFunctionOn, arg: Arg): Promise; - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a [Promise], then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * would wait for the promise to resolve and return its value. - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a non-[Serializable] value, then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns `undefined`. Playwright also supports transferring some additional values that are not serializable by - * `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - */ - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a [Promise], then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * would wait for the promise to resolve and return its value. - * - * If the function passed to the - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns a non-[Serializable] value, then - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * returns `undefined`. Playwright also supports transferring some additional values that are not serializable by - * `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-option-expression). - */ - evaluate(pageFunction: PageFunctionOn, arg?: any): Promise; - - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) - * as a [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * The only difference between - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * and - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * is that - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * If the function passed to the - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns a [Promise], then - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * would wait for the promise to resolve and return its value. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression). - */ - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) - * as a [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * The only difference between - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * and - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * is that - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * If the function passed to the - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns a [Promise], then - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * would wait for the promise to resolve and return its value. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression). - */ - evaluateHandle(pageFunction: PageFunctionOn, arg: Arg): Promise>; - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) - * as a [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * The only difference between - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * and - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * is that - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * If the function passed to the - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns a [Promise], then - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * would wait for the promise to resolve and return its value. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression). - */ - /** - * Returns the return value of - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression) - * as a [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * The only difference between - * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate) - * and - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * is that - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). - * - * If the function passed to the - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * returns a [Promise], then - * [electronApplication.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle) - * would wait for the promise to resolve and return its value. - * @param pageFunction Function to be evaluated in the main Electron process. - * @param arg Optional argument to pass to - * [`pageFunction`](https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate-handle-option-expression). - */ - evaluateHandle(pageFunction: PageFunctionOn, arg?: any): Promise>; - /** - * This event is issued when the application process has been terminated. - */ - on(event: 'close', listener: () => any): this; - - /** - * Emitted when JavaScript within the Electron main process calls one of console API methods, e.g. `console.log` or - * `console.dir`. - * - * The arguments passed into `console.log` are available on the - * [ConsoleMessage](https://playwright.dev/docs/api/class-consolemessage) event handler argument. - * - * **Usage** - * - * ```js - * electronApp.on('console', async msg => { - * const values = []; - * for (const arg of msg.args()) - * values.push(await arg.jsonValue()); - * console.log(...values); - * }); - * await electronApp.evaluate(() => console.log('hello', 5, { foo: 'bar' })); - * ``` - * - */ - on(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; - - /** - * This event is issued for every window that is created **and loaded** in Electron. It contains a - * [Page](https://playwright.dev/docs/api/class-page) that can be used for Playwright automation. - */ - on(event: 'window', listener: (page: Page) => any): this; - - /** - * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. - */ - once(event: 'close', listener: () => any): this; - - /** - * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. - */ - once(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; - - /** - * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. - */ - once(event: 'window', listener: (page: Page) => any): this; - - /** - * This event is issued when the application process has been terminated. - */ - addListener(event: 'close', listener: () => any): this; - - /** - * Emitted when JavaScript within the Electron main process calls one of console API methods, e.g. `console.log` or - * `console.dir`. - * - * The arguments passed into `console.log` are available on the - * [ConsoleMessage](https://playwright.dev/docs/api/class-consolemessage) event handler argument. - * - * **Usage** - * - * ```js - * electronApp.on('console', async msg => { - * const values = []; - * for (const arg of msg.args()) - * values.push(await arg.jsonValue()); - * console.log(...values); - * }); - * await electronApp.evaluate(() => console.log('hello', 5, { foo: 'bar' })); - * ``` - * - */ - addListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; - - /** - * This event is issued for every window that is created **and loaded** in Electron. It contains a - * [Page](https://playwright.dev/docs/api/class-page) that can be used for Playwright automation. - */ - addListener(event: 'window', listener: (page: Page) => any): this; - - /** - * Removes an event listener added by `on` or `addListener`. - */ - removeListener(event: 'close', listener: () => any): this; - - /** - * Removes an event listener added by `on` or `addListener`. - */ - removeListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; - - /** - * Removes an event listener added by `on` or `addListener`. - */ - removeListener(event: 'window', listener: (page: Page) => any): this; - - /** - * Removes an event listener added by `on` or `addListener`. - */ - off(event: 'close', listener: () => any): this; - - /** - * Removes an event listener added by `on` or `addListener`. - */ - off(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; - - /** - * Removes an event listener added by `on` or `addListener`. - */ - off(event: 'window', listener: (page: Page) => any): this; - - /** - * This event is issued when the application process has been terminated. - */ - prependListener(event: 'close', listener: () => any): this; - - /** - * Emitted when JavaScript within the Electron main process calls one of console API methods, e.g. `console.log` or - * `console.dir`. - * - * The arguments passed into `console.log` are available on the - * [ConsoleMessage](https://playwright.dev/docs/api/class-consolemessage) event handler argument. - * - * **Usage** - * - * ```js - * electronApp.on('console', async msg => { - * const values = []; - * for (const arg of msg.args()) - * values.push(await arg.jsonValue()); - * console.log(...values); - * }); - * await electronApp.evaluate(() => console.log('hello', 5, { foo: 'bar' })); - * ``` - * - */ - prependListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; - - /** - * This event is issued for every window that is created **and loaded** in Electron. It contains a - * [Page](https://playwright.dev/docs/api/class-page) that can be used for Playwright automation. - */ - prependListener(event: 'window', listener: (page: Page) => any): this; - - /** - * Returns the BrowserWindow object that corresponds to the given Playwright page. - * @param page Page to retrieve the window for. - */ - browserWindow(page: Page): Promise; - - /** - * Closes Electron application. - */ - close(): Promise; - - /** - * This method returns browser context that can be used for setting up context-wide routing, etc. - */ - context(): BrowserContext; - - /** - * Convenience method that waits for the first application window to be opened. - * - * **Usage** - * - * ```js - * const electronApp = await electron.launch({ - * args: ['main.js'] - * }); - * const window = await electronApp.firstWindow(); - * // ... - * ``` - * - * @param options - */ - firstWindow(options?: { - /** - * Maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The - * default value can be changed by using the - * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout). - */ - timeout?: number; - }): Promise; - - /** - * Returns the main process for this Electron Application. - */ - process(): ChildProcess; - - /** - * This event is issued when the application process has been terminated. - */ - waitForEvent(event: 'close', optionsOrPredicate?: { predicate?: () => boolean | Promise, timeout?: number, signal?: AbortSignal } | (() => boolean | Promise)): Promise; - - /** - * Emitted when JavaScript within the Electron main process calls one of console API methods, e.g. `console.log` or - * `console.dir`. - * - * The arguments passed into `console.log` are available on the - * [ConsoleMessage](https://playwright.dev/docs/api/class-consolemessage) event handler argument. - * - * **Usage** - * - * ```js - * electronApp.on('console', async msg => { - * const values = []; - * for (const arg of msg.args()) - * values.push(await arg.jsonValue()); - * console.log(...values); - * }); - * await electronApp.evaluate(() => console.log('hello', 5, { foo: 'bar' })); - * ``` - * - */ - waitForEvent(event: 'console', optionsOrPredicate?: { predicate?: (consoleMessage: ConsoleMessage) => boolean | Promise, timeout?: number, signal?: AbortSignal } | ((consoleMessage: ConsoleMessage) => boolean | Promise)): Promise; - - /** - * This event is issued for every window that is created **and loaded** in Electron. It contains a - * [Page](https://playwright.dev/docs/api/class-page) that can be used for Playwright automation. - */ - waitForEvent(event: 'window', optionsOrPredicate?: { predicate?: (page: Page) => boolean | Promise, timeout?: number, signal?: AbortSignal } | ((page: Page) => boolean | Promise)): Promise; - - - /** - * Convenience method that returns all the opened windows. - */ - windows(): Array; - - [Symbol.asyncDispose](): Promise; -} - // This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 export {}; diff --git a/utils/generate_types/overrides.d.ts b/utils/generate_types/overrides.d.ts index 4bdde3cd94ddb..e2ec1db55e21d 100644 --- a/utils/generate_types/overrides.d.ts +++ b/utils/generate_types/overrides.d.ts @@ -406,16 +406,5 @@ export type AndroidKey = export const _electron: Electron; export const _android: Android; -//@ts-ignore this will be any if electron is not installed -type ElectronType = typeof import('electron'); - -export interface ElectronApplication { - evaluate(pageFunction: PageFunctionOn, arg: Arg): Promise; - evaluate(pageFunction: PageFunctionOn, arg?: any): Promise; - - evaluateHandle(pageFunction: PageFunctionOn, arg: Arg): Promise>; - evaluateHandle(pageFunction: PageFunctionOn, arg?: any): Promise>; -} - // This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 export {}; From 993a531bd9cd49d0fa58f26d3a28f79562a812f5 Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:22:24 -0700 Subject: [PATCH 7/8] feat(webkit): roll to r2306 (#41201) --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 91a2f0bfa5e40..ca512d07cab28 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -45,7 +45,7 @@ }, { "name": "webkit", - "revision": "2305", + "revision": "2306", "installByDefault": true, "revisionOverrides": { "mac14": "2251", From 8f6e433a5e44742710751ec96ea3a9841cd8fbe7 Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:27:18 -0700 Subject: [PATCH 8/8] feat(firefox-beta): roll to r1524 (#41196) --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index ca512d07cab28..759927d554455 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -38,7 +38,7 @@ }, { "name": "firefox-beta", - "revision": "1523", + "revision": "1524", "installByDefault": false, "browserVersion": "152.0b1", "title": "Firefox Beta"