From 7f742a04b0d94d29d7dedb84403a2e2a3fbad18a Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 13 Feb 2025 18:33:17 -0800 Subject: [PATCH 1/9] chore: make client compile for web (#34792) --- .../src/client/elementHandle.ts | 7 +- packages/playwright-core/src/client/stream.ts | 25 +------ .../src/client/writableStream.ts | 25 +------ .../playwright-core/src/common/platform.ts | 73 +++++++++---------- .../src/server/utils/nodePlatform.ts | 59 +++++++++++++++ 5 files changed, 98 insertions(+), 91 deletions(-) diff --git a/packages/playwright-core/src/client/elementHandle.ts b/packages/playwright-core/src/client/elementHandle.ts index b1ef721ce3..5c0988f438 100644 --- a/packages/playwright-core/src/client/elementHandle.ts +++ b/packages/playwright-core/src/client/elementHandle.ts @@ -14,9 +14,6 @@ * limitations under the License. */ -import { pipeline } from 'stream'; -import { promisify } from 'util'; - import { Frame } from './frame'; import { JSHandle, parseResult, serializeArgument } from './jsHandle'; import { assert } from '../utils/isomorphic/debug'; @@ -34,8 +31,6 @@ import type * as api from '../../types/types'; import type { Platform } from '../common/platform'; import type * as channels from '@protocol/channels'; -const pipelineAsync = promisify(pipeline); - export class ElementHandle extends JSHandle implements api.ElementHandle { readonly _elementChannel: channels.ElementHandleChannel; @@ -306,7 +301,7 @@ export async function convertInputFiles(platform: Platform, files: string | File }), true); for (let i = 0; i < files.length; i++) { const writable = WritableStream.from(writableStreams[i]); - await pipelineAsync(platform.fs().createReadStream(files[i]), writable.stream()); + await platform.streamFile(files[i], writable.stream()); } return { directoryStream: rootDir, diff --git a/packages/playwright-core/src/client/stream.ts b/packages/playwright-core/src/client/stream.ts index 4f43b9afad..97c1e4b634 100644 --- a/packages/playwright-core/src/client/stream.ts +++ b/packages/playwright-core/src/client/stream.ts @@ -30,29 +30,6 @@ export class Stream extends ChannelOwner { } stream(): Readable { - return new StreamImpl(this._channel); - } -} - -class StreamImpl extends Readable { - private _channel: channels.StreamChannel; - - constructor(channel: channels.StreamChannel) { - super(); - this._channel = channel; - } - - override async _read() { - const result = await this._channel.read({ size: 1024 * 1024 }); - if (result.binary.byteLength) - this.push(result.binary); - else - this.push(null); - } - - override _destroy(error: Error | null, callback: (error: Error | null | undefined) => void): void { - // Stream might be destroyed after the connection was closed. - this._channel.close().catch(e => null); - super._destroy(error, callback); + return this._platform.streamReadable(this._channel); } } diff --git a/packages/playwright-core/src/client/writableStream.ts b/packages/playwright-core/src/client/writableStream.ts index 66cf17201d..38a2d0214a 100644 --- a/packages/playwright-core/src/client/writableStream.ts +++ b/packages/playwright-core/src/client/writableStream.ts @@ -14,11 +14,10 @@ * limitations under the License. */ -import { Writable } from 'stream'; - import { ChannelOwner } from './channelOwner'; import type * as channels from '@protocol/channels'; +import type { Writable } from 'stream'; export class WritableStream extends ChannelOwner { static from(Stream: channels.WritableStreamChannel): WritableStream { @@ -30,26 +29,6 @@ export class WritableStream extends ChannelOwner } stream(): Writable { - return new WritableStreamImpl(this._channel); - } -} - -class WritableStreamImpl extends Writable { - private _channel: channels.WritableStreamChannel; - - constructor(channel: channels.WritableStreamChannel) { - super(); - this._channel = channel; - } - - override async _write(chunk: Buffer | string, encoding: BufferEncoding, callback: (error?: Error | null) => void) { - const error = await this._channel.write({ binary: typeof chunk === 'string' ? Buffer.from(chunk) : chunk }).catch(e => e); - callback(error || null); - } - - override async _final(callback: (error?: Error | null) => void) { - // Stream might be destroyed after the connection was closed. - const error = await this._channel.close().catch(e => e); - callback(error || null); + return this._platform.streamWritable(this._channel); } } diff --git a/packages/playwright-core/src/common/platform.ts b/packages/playwright-core/src/common/platform.ts index 10bdc35922..25819b9a6f 100644 --- a/packages/playwright-core/src/common/platform.ts +++ b/packages/playwright-core/src/common/platform.ts @@ -19,6 +19,8 @@ import { webColors, noColors } from '../utils/isomorphic/colors'; import type * as fs from 'fs'; import type * as path from 'path'; import type { Colors } from '../utils/isomorphic/colors'; +import type { Readable, Writable } from 'stream'; +import type * as channels from '@protocol/channels'; export type Zone = { push(data: unknown): Zone; @@ -47,47 +49,12 @@ export type Platform = { log(name: 'api' | 'channel', message: string | Error | object): void; path: () => typeof path; pathSeparator: string; + streamFile(path: string, writable: Writable): Promise, + streamReadable: (channel: channels.StreamChannel) => Readable, + streamWritable: (channel: channels.WritableStreamChannel) => Writable, zones: { empty: Zone, current: () => Zone; }; }; -export const webPlatform: Platform = { - name: 'web', - - calculateSha1: async (text: string) => { - const bytes = new TextEncoder().encode(text); - const hashBuffer = await window.crypto.subtle.digest('SHA-1', bytes); - return Array.from(new Uint8Array(hashBuffer), b => b.toString(16).padStart(2, '0')).join(''); - }, - - colors: webColors, - - createGuid: () => { - return Array.from(window.crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join(''); - }, - - fs: () => { - throw new Error('File system is not available'); - }, - - inspectCustom: undefined, - - isDebuggerAttached: () => false, - - isLogEnabled(name: 'api' | 'channel') { - return false; - }, - - log(name: 'api' | 'channel', message: string | Error | object) {}, - - path: () => { - throw new Error('Path module is not available'); - }, - - pathSeparator: '/', - - zones: { empty: noopZone, current: () => noopZone }, -}; - export const emptyPlatform: Platform = { name: 'empty', @@ -121,5 +88,35 @@ export const emptyPlatform: Platform = { pathSeparator: '/', + streamFile(path: string, writable: Writable): Promise { + throw new Error('Streams are not available'); + }, + + streamReadable: (channel: channels.StreamChannel) => { + throw new Error('Streams are not available'); + }, + + streamWritable: (channel: channels.WritableStreamChannel) => { + throw new Error('Streams are not available'); + }, + zones: { empty: noopZone, current: () => noopZone }, }; + +export const webPlatform: Platform = { + ...emptyPlatform, + + name: 'web', + + calculateSha1: async (text: string) => { + const bytes = new TextEncoder().encode(text); + const hashBuffer = await window.crypto.subtle.digest('SHA-1', bytes); + return Array.from(new Uint8Array(hashBuffer), b => b.toString(16).padStart(2, '0')).join(''); + }, + + colors: webColors, + + createGuid: () => { + return Array.from(window.crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join(''); + }, +}; diff --git a/packages/playwright-core/src/server/utils/nodePlatform.ts b/packages/playwright-core/src/server/utils/nodePlatform.ts index e9883da361..fcdfebde2a 100644 --- a/packages/playwright-core/src/server/utils/nodePlatform.ts +++ b/packages/playwright-core/src/server/utils/nodePlatform.ts @@ -18,6 +18,7 @@ import * as crypto from 'crypto'; import * as fs from 'fs'; import * as path from 'path'; import * as util from 'util'; +import { Readable, Writable, pipeline } from 'stream'; import { colors } from '../../utilsBundle'; import { debugLogger } from './debugLogger'; @@ -25,6 +26,9 @@ import { currentZone, emptyZone } from './zones'; import type { Platform, Zone } from '../../common/platform'; import type { Zone as ZoneImpl } from './zones'; +import type * as channels from '@protocol/channels'; + +const pipelineAsync = util.promisify(pipeline); class NodeZone implements Zone { private _zone: ZoneImpl; @@ -81,8 +85,63 @@ export const nodePlatform: Platform = { pathSeparator: path.sep, + async streamFile(path: string, stream: Writable): Promise { + await pipelineAsync(fs.createReadStream(path), stream); + }, + + streamReadable: (channel: channels.StreamChannel) => { + return new ReadableStreamImpl(channel); + }, + + streamWritable: (channel: channels.WritableStreamChannel) => { + return new WritableStreamImpl(channel); + }, + zones: { current: () => new NodeZone(currentZone()), empty: new NodeZone(emptyZone), } }; + +class ReadableStreamImpl extends Readable { + private _channel: channels.StreamChannel; + + constructor(channel: channels.StreamChannel) { + super(); + this._channel = channel; + } + + override async _read() { + const result = await this._channel.read({ size: 1024 * 1024 }); + if (result.binary.byteLength) + this.push(result.binary); + else + this.push(null); + } + + override _destroy(error: Error | null, callback: (error: Error | null | undefined) => void): void { + // Stream might be destroyed after the connection was closed. + this._channel.close().catch(e => null); + super._destroy(error, callback); + } +} + +class WritableStreamImpl extends Writable { + private _channel: channels.WritableStreamChannel; + + constructor(channel: channels.WritableStreamChannel) { + super(); + this._channel = channel; + } + + override async _write(chunk: Buffer | string, encoding: BufferEncoding, callback: (error?: Error | null) => void) { + const error = await this._channel.write({ binary: typeof chunk === 'string' ? Buffer.from(chunk) : chunk }).catch(e => e); + callback(error || null); + } + + override async _final(callback: (error?: Error | null) => void) { + // Stream might be destroyed after the connection was closed. + const error = await this._channel.close().catch(e => e); + callback(error || null); + } +} From e276d92dd3fe02c72b81ad0b88305f4e27050418 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 14 Feb 2025 00:33:27 -0800 Subject: [PATCH 2/9] feat(chromium-tip-of-tree): roll to r1303 (#34783) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 38c5d71834..1299c8f0a0 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,15 +15,15 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1302", + "revision": "1303", "installByDefault": false, - "browserVersion": "135.0.7011.0" + "browserVersion": "135.0.7015.0" }, { "name": "chromium-tip-of-tree-headless-shell", - "revision": "1302", + "revision": "1303", "installByDefault": false, - "browserVersion": "135.0.7011.0" + "browserVersion": "135.0.7015.0" }, { "name": "firefox", From 32c299c89d37f40a0adb63255febf6a42311bf2d Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 14 Feb 2025 00:33:55 -0800 Subject: [PATCH 3/9] feat(chromium): roll to r1159 (#34780) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Max Schmitt --- README.md | 4 +- packages/playwright-core/browsers.json | 8 +- .../src/server/deviceDescriptorsSource.json | 96 +++++++++---------- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index f236eafd1a..e8a9231fd0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🎭 Playwright -[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-134.0.6998.3-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-135.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.2-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-134.0.6998.15-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-135.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.2-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) @@ -8,7 +8,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 134.0.6998.3 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 134.0.6998.15 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 18.2 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 135.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 1299c8f0a0..8d864d05ca 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,15 +3,15 @@ "browsers": [ { "name": "chromium", - "revision": "1158", + "revision": "1159", "installByDefault": true, - "browserVersion": "134.0.6998.3" + "browserVersion": "134.0.6998.15" }, { "name": "chromium-headless-shell", - "revision": "1158", + "revision": "1159", "installByDefault": true, - "browserVersion": "134.0.6998.3" + "browserVersion": "134.0.6998.15" }, { "name": "chromium-tip-of-tree", diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index 1e6d0be7b8..cbbf0608ec 100644 --- a/packages/playwright-core/src/server/deviceDescriptorsSource.json +++ b/packages/playwright-core/src/server/deviceDescriptorsSource.json @@ -110,7 +110,7 @@ "defaultBrowserType": "webkit" }, "Galaxy S5": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -121,7 +121,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -132,7 +132,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 360, "height": 740 @@ -143,7 +143,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 740, "height": 360 @@ -154,7 +154,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 320, "height": 658 @@ -165,7 +165,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+ landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 658, "height": 320 @@ -176,7 +176,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36", "viewport": { "width": 712, "height": 1138 @@ -187,7 +187,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36", "viewport": { "width": 1138, "height": 712 @@ -1098,7 +1098,7 @@ "defaultBrowserType": "webkit" }, "LG Optimus L70": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1109,7 +1109,7 @@ "defaultBrowserType": "chromium" }, "LG Optimus L70 landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1120,7 +1120,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1131,7 +1131,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1142,7 +1142,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1153,7 +1153,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1164,7 +1164,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36", "viewport": { "width": 800, "height": 1280 @@ -1175,7 +1175,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36", "viewport": { "width": 1280, "height": 800 @@ -1186,7 +1186,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1197,7 +1197,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1208,7 +1208,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1219,7 +1219,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1230,7 +1230,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1241,7 +1241,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1252,7 +1252,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1263,7 +1263,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1274,7 +1274,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1285,7 +1285,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1296,7 +1296,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36", "viewport": { "width": 600, "height": 960 @@ -1307,7 +1307,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36", "viewport": { "width": 960, "height": 600 @@ -1362,7 +1362,7 @@ "defaultBrowserType": "webkit" }, "Pixel 2": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 411, "height": 731 @@ -1373,7 +1373,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 731, "height": 411 @@ -1384,7 +1384,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 411, "height": 823 @@ -1395,7 +1395,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 823, "height": 411 @@ -1406,7 +1406,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 393, "height": 786 @@ -1417,7 +1417,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 786, "height": 393 @@ -1428,7 +1428,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 353, "height": 745 @@ -1439,7 +1439,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 745, "height": 353 @@ -1450,7 +1450,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -1465,7 +1465,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "screen": { "height": 892, "width": 412 @@ -1480,7 +1480,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "screen": { "width": 393, "height": 851 @@ -1495,7 +1495,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "screen": { "width": 851, "height": 393 @@ -1510,7 +1510,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -1525,7 +1525,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -1540,7 +1540,7 @@ "defaultBrowserType": "chromium" }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1551,7 +1551,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1562,7 +1562,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36", "screen": { "width": 1792, "height": 1120 @@ -1577,7 +1577,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36 Edg/134.0.6998.3", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36 Edg/134.0.6998.15", "screen": { "width": 1792, "height": 1120 @@ -1622,7 +1622,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36", "screen": { "width": 1920, "height": 1080 @@ -1637,7 +1637,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36 Edg/134.0.6998.3", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36 Edg/134.0.6998.15", "screen": { "width": 1920, "height": 1080 From e6b405c0124698e7e3e0dc2be6103f415066835e Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 14 Feb 2025 09:32:06 +0000 Subject: [PATCH 4/9] chore: show gha pull request title instead of a merge commit (#34781) --- packages/html-reporter/src/metadataView.tsx | 30 +++++---- packages/playwright/src/isomorphic/types.d.ts | 1 + .../src/plugins/gitCommitInfoPlugin.ts | 22 +++++-- .../playwright-test-fixtures.ts | 1 + tests/playwright-test/reporter-html.spec.ts | 65 +++++++++++++++++-- 5 files changed, 95 insertions(+), 24 deletions(-) diff --git a/packages/html-reporter/src/metadataView.tsx b/packages/html-reporter/src/metadataView.tsx index 03a5ed06f4..0f54bb4249 100644 --- a/packages/html-reporter/src/metadataView.tsx +++ b/packages/html-reporter/src/metadataView.tsx @@ -107,14 +107,24 @@ const InnerMetadataView = () => { const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => { const email = info['revision.email'] ? ` <${info['revision.email']}>` : ''; const author = `${info['revision.author'] || ''}${email}`; - const subject = info['revision.subject'] || ''; + + let subject = info['revision.subject'] || ''; + let link = info['revision.link']; + let shortSubject = info['revision.id']?.slice(0, 7) || 'unknown'; + + if (info['pull.link'] && info['pull.title']) { + subject = info['pull.title']; + link = info['pull.link']; + shortSubject = link ? 'Pull Request' : ''; + } + const shortTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(info['revision.timestamp']); const longTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'long' }).format(info['revision.timestamp']); return
- {info['revision.link'] ? ( - + {link ? ( + {subject} ) : @@ -130,18 +140,12 @@ const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => { Logs )} - {info['pull.link'] && ( - <> - · - Pull Request - - )}
- {!!info['revision.link'] ? ( - - {info['revision.id']?.slice(0, 7) || 'unknown'} + {link ? ( + + {shortSubject} - ) : !!info['revision.id'] && {info['revision.id'].slice(0, 7)}} + ) : !!shortSubject && {shortSubject}}
; }; diff --git a/packages/playwright/src/isomorphic/types.d.ts b/packages/playwright/src/isomorphic/types.d.ts index 213f350514..2619c0df33 100644 --- a/packages/playwright/src/isomorphic/types.d.ts +++ b/packages/playwright/src/isomorphic/types.d.ts @@ -25,5 +25,6 @@ export interface GitCommitInfo { 'pull.link'?: string; 'pull.diff'?: string; 'pull.base'?: string; + 'pull.title'?: string; 'ci.link'?: string; } diff --git a/packages/playwright/src/plugins/gitCommitInfoPlugin.ts b/packages/playwright/src/plugins/gitCommitInfoPlugin.ts index 29010183c7..7ade4a005c 100644 --- a/packages/playwright/src/plugins/gitCommitInfoPlugin.ts +++ b/packages/playwright/src/plugins/gitCommitInfoPlugin.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import fs from 'fs'; + import { createGuid, spawnAsync } from 'playwright-core/lib/utils'; import type { TestRunnerPlugin } from './'; @@ -33,7 +35,7 @@ export const gitCommitInfo = (options?: GitCommitInfoPluginOptions): TestRunnerP name: 'playwright:git-commit-info', setup: async (config: FullConfig, configDir: string) => { - const fromEnv = linksFromEnv(); + const fromEnv = await linksFromEnv(); const fromCLI = await gitStatusFromCLI(options?.directory || configDir, fromEnv); config.metadata = config.metadata || {}; config.metadata['git.commit.info'] = { ...fromEnv, ...fromCLI }; @@ -45,7 +47,7 @@ interface GitCommitInfoPluginOptions { directory?: string; } -function linksFromEnv() { +async function linksFromEnv() { const out: Partial = {}; // Jenkins: https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables if (process.env.BUILD_URL) @@ -55,15 +57,21 @@ function linksFromEnv() { out['revision.link'] = `${process.env.CI_PROJECT_URL}/-/commit/${process.env.CI_COMMIT_SHA}`; if (process.env.CI_JOB_URL) out['ci.link'] = process.env.CI_JOB_URL; - // GitHub: https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables + // GitHub: https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables if (process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_SHA) out['revision.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/commit/${process.env.GITHUB_SHA}`; if (process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID) out['ci.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; - if (process.env.GITHUB_REF_NAME && process.env.GITHUB_REF_NAME.endsWith('/merge')) { - const pullId = process.env.GITHUB_REF_NAME.substring(0, process.env.GITHUB_REF_NAME.indexOf('/merge')); - out['pull.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/pull/${pullId}`; - out['pull.base'] = process.env.GITHUB_BASE_REF; + if (process.env.GITHUB_EVENT_PATH) { + try { + const json = JSON.parse(await fs.promises.readFile(process.env.GITHUB_EVENT_PATH, 'utf8')); + if (json.pull_request) { + out['pull.title'] = json.pull_request.title; + out['pull.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/pull/${json.pull_request.number}`; + out['pull.base'] = json.pull_request.base.ref; + } + } catch { + } } return out; } diff --git a/tests/playwright-test/playwright-test-fixtures.ts b/tests/playwright-test/playwright-test-fixtures.ts index 789ed1feb6..43503497d2 100644 --- a/tests/playwright-test/playwright-test-fixtures.ts +++ b/tests/playwright-test/playwright-test-fixtures.ts @@ -227,6 +227,7 @@ export function cleanEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv { GITHUB_RUN_ID: undefined, GITHUB_SERVER_URL: undefined, GITHUB_SHA: undefined, + GITHUB_EVENT_PATH: undefined, // END: Reserved CI PW_TEST_HTML_REPORT_OPEN: undefined, PLAYWRIGHT_HTML_OPEN: undefined, diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index bf88f52ade..78ef741415 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -1221,11 +1221,8 @@ for (const useIntermediateMergeReport of [true, false] as const) { const result = await runInlineTest(files, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never', GITHUB_REPOSITORY: 'microsoft/playwright-example-for-test', - GITHUB_RUN_ID: 'example-run-id', GITHUB_SERVER_URL: 'https://playwright.dev', GITHUB_SHA: 'example-sha', - GITHUB_REF_NAME: '42/merge', - GITHUB_BASE_REF: 'HEAD~1', }); await showReport(); @@ -1235,9 +1232,69 @@ for (const useIntermediateMergeReport of [true, false] as const) { await expect(page.locator('.metadata-view')).toMatchAriaSnapshot(` - 'link "chore(html): make this test look nice"' - text: /^William on/ + - link /^[a-f0-9]{7}$/ + - text: 'foo : value1 bar : {"prop":"value2"} baz : ["value3",123]' + `); + }); + + test('should include metadata with populateGitInfo on GHA', async ({ runInlineTest, writeFiles, showReport, page }) => { + const files = { + 'uncommitted.txt': `uncommitted file`, + 'playwright.config.ts': ` + export default { + populateGitInfo: true, + metadata: { foo: 'value1', bar: { prop: 'value2' }, baz: ['value3', 123] } + }; + `, + 'example.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('sample', async ({}) => { expect(2).toBe(2); }); + `, + }; + const baseDir = await writeFiles(files); + + const execGit = async (args: string[]) => { + const { code, stdout, stderr } = await spawnAsync('git', args, { stdio: 'pipe', cwd: baseDir }); + if (!!code) + throw new Error(`Non-zero exit of:\n$ git ${args.join(' ')}\nConsole:\nstdout:\n${stdout}\n\nstderr:\n${stderr}\n\n`); + return; + }; + + await execGit(['init']); + await execGit(['config', '--local', 'user.email', 'shakespeare@example.local']); + await execGit(['config', '--local', 'user.name', 'William']); + await execGit(['add', 'playwright.config.ts']); + await execGit(['commit', '-m', 'init']); + await execGit(['add', '*.ts']); + await execGit(['commit', '-m', 'chore(html): make this test look nice']); + + const eventPath = path.join(baseDir, 'event.json'); + await fs.promises.writeFile(eventPath, JSON.stringify({ + pull_request: { + title: 'My PR', + number: 42, + base: { ref: 'main' }, + }, + })); + + const result = await runInlineTest(files, { reporter: 'dot,html' }, { + PLAYWRIGHT_HTML_OPEN: 'never', + GITHUB_REPOSITORY: 'microsoft/playwright-example-for-test', + GITHUB_RUN_ID: 'example-run-id', + GITHUB_SERVER_URL: 'https://playwright.dev', + GITHUB_SHA: 'example-sha', + GITHUB_EVENT_PATH: eventPath, + }); + + await showReport(); + + expect(result.exitCode).toBe(0); + await page.getByRole('button', { name: 'Metadata' }).click(); + await expect(page.locator('.metadata-view')).toMatchAriaSnapshot(` + - 'link "My PR"' + - text: /^William on/ - link "Logs" - link "Pull Request" - - link /^[a-f0-9]{7}$/ - text: 'foo : value1 bar : {"prop":"value2"} baz : ["value3",123]' `); }); From df6e3f043acc287443c479ce7901be336a6ee5e1 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 14 Feb 2025 11:58:37 +0100 Subject: [PATCH 5/9] devops: migrate away from merge.config.ts (#34802) --- .github/workflows/create_test_report.yml | 2 +- .github/workflows/merge.config.ts | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 .github/workflows/merge.config.ts diff --git a/.github/workflows/create_test_report.yml b/.github/workflows/create_test_report.yml index dfc6961c2c..3799f14159 100644 --- a/.github/workflows/create_test_report.yml +++ b/.github/workflows/create_test_report.yml @@ -33,7 +33,7 @@ jobs: - name: Merge reports run: | - npx playwright merge-reports --config .github/workflows/merge.config.ts ./all-blob-reports + npx playwright merge-reports --reporter=html,packages/playwright/lib/reporters/markdown.js ./all-blob-reports env: NODE_OPTIONS: --max-old-space-size=8192 diff --git a/.github/workflows/merge.config.ts b/.github/workflows/merge.config.ts deleted file mode 100644 index e8582ed521..0000000000 --- a/.github/workflows/merge.config.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default { - testDir: '../../tests', - reporter: [[require.resolve('../../packages/playwright/lib/reporters/markdown')], ['html']] -}; \ No newline at end of file From fe0b327770517d9440695b6c96e69ddc31c81386 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 14 Feb 2025 16:59:26 +0100 Subject: [PATCH 6/9] feat(ui): llm conversation about error (#34750) --- package-lock.json | 16 +- package.json | 1 + .../src/server/trace/viewer/traceViewer.ts | 10 + packages/trace-viewer/package.json | 3 +- packages/trace-viewer/src/sw/main.ts | 6 + .../trace-viewer/src/ui/aiConversation.css | 128 +++++++++ .../trace-viewer/src/ui/aiConversation.tsx | 84 ++++++ packages/trace-viewer/src/ui/errorsTab.tsx | 144 +++++++--- packages/trace-viewer/src/ui/llm.tsx | 267 ++++++++++++++++++ packages/trace-viewer/src/ui/uiModeView.tsx | 5 +- packages/trace-viewer/src/ui/workbench.tsx | 2 +- .../trace-viewer/src/ui/workbenchLoader.tsx | 5 +- packages/web/src/uiUtils.ts | 10 + tests/playwright-test/ui-mode-llm.spec.ts | 99 +++++++ 14 files changed, 731 insertions(+), 49 deletions(-) create mode 100644 packages/trace-viewer/src/ui/aiConversation.css create mode 100644 packages/trace-viewer/src/ui/aiConversation.tsx create mode 100644 packages/trace-viewer/src/ui/llm.tsx create mode 100644 tests/playwright-test/ui-mode-llm.spec.ts diff --git a/package-lock.json b/package-lock.json index eb69ce2f36..e1edd82bf3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "formidable": "^2.1.1", "immutable": "^4.3.7", "license-checker": "^25.0.1", + "markdown-to-jsx": "^7.7.3", "mime": "^3.0.0", "node-stream-zip": "^1.15.0", "react": "^18.1.0", @@ -5897,7 +5898,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -5953,6 +5953,18 @@ "semver": "bin/semver" } }, + "node_modules/markdown-to-jsx": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.7.3.tgz", + "integrity": "sha512-o35IhJDFP6Fv60zPy+hbvZSQMmgvSGdK5j8NRZ7FeZMY+Bgqw+dSg7SC1ZEzC26++CiOUCqkbq96/c3j/FfTEQ==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -6784,7 +6796,6 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -8975,6 +8986,7 @@ "packages/trace-viewer": { "version": "0.0.0", "dependencies": { + "markdown-to-jsx": "^7.7.3", "yaml": "^2.6.0" } }, diff --git a/package.json b/package.json index 8b54d54e6c..f27ad12a76 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "formidable": "^2.1.1", "immutable": "^4.3.7", "license-checker": "^25.0.1", + "markdown-to-jsx": "^7.7.3", "mime": "^3.0.0", "node-stream-zip": "^1.15.0", "react": "^18.1.0", diff --git a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts index e0f05bac41..3f322e7125 100644 --- a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts +++ b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts @@ -142,6 +142,16 @@ export async function installRootRedirect(server: HttpServer, traceUrls: string[ server.routePath('/', (_, response) => { response.statusCode = 302; response.setHeader('Location', urlPath); + + if (process.env.OPENAI_API_KEY) + response.appendHeader('Set-Cookie', `openai_api_key=${process.env.OPENAI_API_KEY}`); + if (process.env.OPENAI_BASE_URL) + response.appendHeader('Set-Cookie', `openai_base_url=${process.env.OPENAI_BASE_URL}`); + if (process.env.ANTHROPIC_API_KEY) + response.appendHeader('Set-Cookie', `anthropic_api_key=${process.env.ANTHROPIC_API_KEY}`); + if (process.env.ANTHROPIC_BASE_URL) + response.appendHeader('Set-Cookie', `anthropic_base_url=${process.env.ANTHROPIC_BASE_URL}`); + response.end(); return true; }); diff --git a/packages/trace-viewer/package.json b/packages/trace-viewer/package.json index 57a3dfd409..fde4eb6766 100644 --- a/packages/trace-viewer/package.json +++ b/packages/trace-viewer/package.json @@ -4,6 +4,7 @@ "version": "0.0.0", "type": "module", "dependencies": { - "yaml": "^2.6.0" + "yaml": "^2.6.0", + "markdown-to-jsx": "^7.7.3" } } diff --git a/packages/trace-viewer/src/sw/main.ts b/packages/trace-viewer/src/sw/main.ts index cd23082d91..cdce3261fe 100644 --- a/packages/trace-viewer/src/sw/main.ts +++ b/packages/trace-viewer/src/sw/main.ts @@ -78,6 +78,12 @@ async function doFetch(event: FetchEvent): Promise { if (event.request.url.startsWith('chrome-extension://')) return fetch(event.request); + if (event.request.headers.get('x-pw-serviceworker') === 'forward') { + const request = new Request(event.request); + request.headers.delete('x-pw-serviceworker'); + return fetch(request); + } + const request = event.request; const client = await self.clients.get(event.clientId); diff --git a/packages/trace-viewer/src/ui/aiConversation.css b/packages/trace-viewer/src/ui/aiConversation.css new file mode 100644 index 0000000000..214496017a --- /dev/null +++ b/packages/trace-viewer/src/ui/aiConversation.css @@ -0,0 +1,128 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.chat-container { + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; + color: #e0e0e0; +} + +.chat-disclaimer { + text-align: center; + color: var(--vscode-editorBracketMatch-border); + margin: 0px; +} + +.chat-container hr { + width: 100%; + border: none; + border-top: 1px solid var(--vscode-titleBar-inactiveBackground); +} + +.messages-container { + flex: 1; + overflow-y: auto; + padding: 16px; + + display: flex; + flex-direction: column; + gap: 16px; +} + +.message { + gap: 12px; + max-width: 85%; +} + +.user-message { + flex-direction: row-reverse; + margin-left: auto; + width: fit-content +} + +.message-icon { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--vscode-titleBar-inactiveBackground); + flex-shrink: 0; +} + +.message-content { + background-color: var(--vscode-titleBar-inactiveBackground); + color: var(--vscode-titleBar-activeForeground); + padding: 2px 8px; +} + +.message-content pre { + text-wrap: auto; + overflow-wrap: anywhere; +} + +.user-message .message-content { + background-color: var(--vscode-titleBar-activeBackground); +} + +/* Input form styles */ +.input-form { + position: sticky; + bottom: 0; + display: flex; + height: 64px; + gap: 8px; + padding: 10px; + background-color: var(--vscode-sideBar-background); + border-top: 1px solid var(--vscode-sideBarSectionHeader-border); +} + +.message-input { + flex: 1; + padding: 8px 12px; + border: 1px solid var(--vscode-settings-textInputBorder); + background-color: var(--vscode-settings-textInputBackground); + font-size: 14px; + outline: none; + transition: border-color 0.2s; +} + +.message-input:focus { + border-color: #0078d4; +} + +.send-button { + display: flex; + align-items: center; + justify-content: center; + padding: 8px; + background-color: var(--vscode-button-background); + border: none; + color: white; + cursor: pointer; + transition: background-color 0.2s; +} + +.send-button:hover { + background-color: var(--vscode-button-hoverBackground); +} + +.send-button:disabled { + background-color: var(--vscode-disabledForeground); + cursor: not-allowed; +} diff --git a/packages/trace-viewer/src/ui/aiConversation.tsx b/packages/trace-viewer/src/ui/aiConversation.tsx new file mode 100644 index 0000000000..548ed626f0 --- /dev/null +++ b/packages/trace-viewer/src/ui/aiConversation.tsx @@ -0,0 +1,84 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useCallback, useState } from 'react'; +import Markdown from 'markdown-to-jsx'; +import './aiConversation.css'; +import { clsx } from '@web/uiUtils'; +import { useLLMConversation } from './llm'; + +export function AIConversation({ conversationId }: { conversationId: string }) { + const [history, conversation] = useLLMConversation(conversationId); + const [input, setInput] = useState(''); + + const onSubmit = useCallback(() => { + setInput(content => { + conversation.send(content); + return ''; + }); + }, [conversation]); + + return ( +
+

Chat based on {conversation.chat.api.name}. Check for mistakes.

+
+
+ {history.filter(({ role }) => role !== 'developer').map((message, index) => ( +
+ {message.role === 'assistant' && ( +
+ +
+ )} +
+ {message.displayContent ?? message.content} +
+
+ ))} +
+ +
+