diff --git a/.github/workflows/create_test_report.yml b/.github/workflows/create_test_report.yml index ddf32c64b17d2..7850e4f1c07b1 100644 --- a/.github/workflows/create_test_report.yml +++ b/.github/workflows/create_test_report.yml @@ -10,10 +10,11 @@ jobs: pull-requests: write checks: write statuses: write - id-token: write # This is required for OIDC login (azure/login) to succeed contents: read # This is required for actions/checkout to succeed if: ${{ github.event.workflow_run.event == 'pull_request' || github.event.workflow_run.event == 'push' }} runs-on: ubuntu-latest + env: + MARKDOWN_OUTPUT_FILE: ${{ github.workspace }}/report.md steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 @@ -32,28 +33,48 @@ jobs: run: | npx playwright merge-reports --config .github/workflows/merge.config.ts ./all-blob-reports env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_OPTIONS: --max-old-space-size=8192 - HTML_REPORT_URL: 'https://mspwblobreport.z1.web.core.windows.net/run-${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}-${{ github.sha }}/index.html' WORKFLOW_RUN_CONCLUSION: ${{ github.event.workflow_run.conclusion }} - - name: Azure Login - uses: azure/login@v3 + - name: Upload HTML report + id: upload-report + uses: actions/upload-artifact@v7 with: - client-id: ${{ secrets.AZURE_BLOB_REPORTS_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_BLOB_REPORTS_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_BLOB_REPORTS_SUBSCRIPTION_ID }} + path: playwright-report/index.html + archive: false # Upload as a single, browser-openable file (no zip) + retention-days: 30 - - name: Upload HTML report to Azure - id: upload-report + # The triggering workflow ran on a pull_request event, but workflow_run.pull_requests is + # empty for PRs from forks, so resolve the number from the head repo + branch instead. + - name: Resolve PR number + if: ${{ github.event.workflow_run.event == 'pull_request' }} + id: pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - REPORT_DIR='run-${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}-${{ github.sha }}' - azcopy cp --recursive "./playwright-report/*" "https://mspwblobreport.blob.core.windows.net/\$web/$REPORT_DIR" - REPORT_URL="https://mspwblobreport.z1.web.core.windows.net/$REPORT_DIR/index.html" - echo "Report url: $REPORT_URL" - echo "reportUrl=$REPORT_URL" >> "$GITHUB_OUTPUT" + NUMBER=$(gh pr view --repo "${{ github.repository }}" \ + "${{ github.event.workflow_run.head_repository.owner.login }}:${{ github.event.workflow_run.head_branch }}" \ + --json number --jq '.number' 2>/dev/null || true) + echo "number=$NUMBER" >> "$GITHUB_OUTPUT" + + - name: Post report comment to PR + if: ${{ steps.pr.outputs.number }} + uses: actions/github-script@v8 env: - AZCOPY_AUTO_LOGIN_TYPE: AZCLI + HTML_REPORT_URL: ${{ steps.upload-report.outputs.artifact-url }} + PR_NUMBER: ${{ steps.pr.outputs.number }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { postReportComment } = require('./tests/config/postReportComment'); + await postReportComment({ + github, + context, + core, + reportFile: process.env.MARKDOWN_OUTPUT_FILE, + prNumber: +process.env.PR_NUMBER, + reportUrl: process.env.HTML_REPORT_URL, + }); - name: Publish Report URL as Commit Status uses: actions/github-script@v9 @@ -64,6 +85,6 @@ jobs: repo: context.repo.repo, sha: '${{ github.event.workflow_run.head_sha }}', state: 'success', - target_url: '${{ steps.upload-report.outputs.reportUrl }}', + target_url: '${{ steps.upload-report.outputs.artifact-url }}', context: 'HTML Report (${{ github.event.workflow_run.name }})', }); diff --git a/.github/workflows/merge.config.ts b/.github/workflows/merge.config.ts index 222b728f7c134..5d4588b2a4a52 100644 --- a/.github/workflows/merge.config.ts +++ b/.github/workflows/merge.config.ts @@ -1,4 +1,4 @@ export default { testDir: '../../tests', - reporter: [[require.resolve('../../tests/config/ghaMarkdownReporter')], ['html']] -}; \ No newline at end of file + reporter: [[require.resolve('../../tests/config/markdownReporter')], ['html']], +}; diff --git a/README.md b/README.md index e17f0f357cded..f94183a0c9cd9 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-150.0.7871.24-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-151.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-26.5-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-informational)](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-150.0.7871.46-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-151.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-26.5-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-informational)](https://aka.ms/playwright/discord) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) @@ -296,7 +296,7 @@ The [Playwright VS Code extension](https://marketplace.visualstudio.com/items?it | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium1 150.0.7871.24 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium1 150.0.7871.46 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 26.5 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 151.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/packages/injected/src/injectedScript.ts b/packages/injected/src/injectedScript.ts index 6c265ae8ff9dc..17af70020bee0 100644 --- a/packages/injected/src/injectedScript.ts +++ b/packages/injected/src/injectedScript.ts @@ -43,20 +43,9 @@ import type { SelectorEngine, SelectorRoot } from './selectorEngine'; import type { GenerateSelectorOptions } from './selectorGenerator'; import type { ElementText, TextMatcher } from './selectorUtils'; import type { Builtins } from './utilityScript'; +import type { ExpectedTextValue, Point, Rect } from '@protocol/structs'; -type ExpectedTextValue = { - string?: string, - regexSource?: string, - regexFlags?: string, - matchSubstring?: boolean, - ignoreCase?: boolean, - normalizeWhiteSpace?: boolean, -}; - -type Point = { x: number, y: number }; -type Rect = Point & { width: number, height: number }; - export type FrameExpectParams = { selector?: string, expression: string, diff --git a/packages/injected/src/storageScript.ts b/packages/injected/src/storageScript.ts index 13ec974c011e7..2583b2c3c3e63 100644 --- a/packages/injected/src/storageScript.ts +++ b/packages/injected/src/storageScript.ts @@ -16,42 +16,9 @@ import { parseEvaluationResultValue, serializeAsCallArgument } from '@isomorphic/utilityScriptSerializers'; -type NameValue = { name: string, value: string }; - -type IndexedDBDatabase = { - name: string, - version: number, - stores: { - name: string, - autoIncrement: boolean, - keyPath?: string, - keyPathArray?: string[], - records: { - key?: any, - keyEncoded?: any, - value?: any, - valueEncoded?: any, - }[], - indexes: { - name: string, - keyPath?: string, - keyPathArray?: string[], - multiEntry: boolean, - unique: boolean, - }[], - }[], -}; - -type SetOriginStorage = { - origin: string, - localStorage: NameValue[], - indexedDB?: IndexedDBDatabase[], -}; - -export type SerializedStorage = { - localStorage: NameValue[], - indexedDB?: IndexedDBDatabase[], -}; +import type { IndexedDBDatabase, OriginStorage, SetOriginStorage } from '@protocol/structs'; + +export type SerializedStorage = Omit; export class StorageScript { private _isFirefox: boolean; diff --git a/packages/isomorphic/trace/traceUtils.ts b/packages/isomorphic/trace/traceUtils.ts index aa11c397aca4f..9873a63950f7b 100644 --- a/packages/isomorphic/trace/traceUtils.ts +++ b/packages/isomorphic/trace/traceUtils.ts @@ -15,11 +15,7 @@ */ import type { StackFrame } from '../stackTrace'; - -type ClientSideCallMetadata = { - id: number, - stack?: StackFrame[], -}; +import type { ClientSideCallMetadata } from '@protocol/structs'; export type SerializedStackFrame = [number, number, number, string]; export type SerializedStack = [number, SerializedStackFrame[]]; diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 8235994ac505c..5261809f8319b 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,16 +3,16 @@ "browsers": [ { "name": "chromium", - "revision": "1229", + "revision": "1231", "installByDefault": true, - "browserVersion": "150.0.7871.24", + "browserVersion": "150.0.7871.46", "title": "Chrome for Testing" }, { "name": "chromium-headless-shell", - "revision": "1229", + "revision": "1231", "installByDefault": true, - "browserVersion": "150.0.7871.24", + "browserVersion": "150.0.7871.46", "title": "Chrome Headless Shell" }, { diff --git a/packages/playwright-core/src/client/channels.d.ts b/packages/playwright-core/src/client/channels.d.ts index c66c5f2b5f249..d797efe34ae34 100644 --- a/packages/playwright-core/src/client/channels.d.ts +++ b/packages/playwright-core/src/client/channels.d.ts @@ -15,10 +15,8 @@ */ // This file is generated by generate_channels.js, do not edit manually. -export type Binary = Buffer; - -export interface Channel { -} +import type { Binary, Channel, AndroidWebView, AndroidSelector, AndroidElementInfo, APIResponse, Metadata, ClientSideCallMetadata, SDKLanguage, WaitInfo, SetNetworkCookie, NetworkCookie, ResourceTiming, SecurityDetails, RequestSizes, RemoteAddr, ExpectedTextValue, SelectorEngine, FormField, LifecycleEvent, ConsoleMessagesFilter, RecorderSource, IndexedDBDatabase, SetOriginStorage, OriginStorage, RecordHarOptions, SerializedValue, SerializedArgument, SerializedError, StackFrame, VirtualCredential, Point, Rect, URLPattern, NameValue } from '@protocol/structs'; +export type { Binary, Channel, AndroidWebView, AndroidSelector, AndroidElementInfo, APIResponse, Metadata, ClientSideCallMetadata, SDKLanguage, WaitInfo, SetNetworkCookie, NetworkCookie, ResourceTiming, SecurityDetails, RequestSizes, RemoteAddr, ExpectedTextValue, SelectorEngine, FormField, LifecycleEvent, ConsoleMessagesFilter, RecorderSource, IndexedDBDatabase, SetOriginStorage, OriginStorage, RecordHarOptions, SerializedValue, SerializedArgument, SerializedError, StackFrame, VirtualCredential, Point, Rect, URLPattern, NameValue } from '@protocol/structs'; // ----------- Initializer Traits ----------- export type InitializerTraits = @@ -584,56 +582,6 @@ export interface AndroidDeviceEvents { 'webViewRemoved': AndroidDeviceWebViewRemovedEvent; } -export type AndroidWebView = { - pid: number, - pkg: string, - socketName: string, -}; - -export type AndroidSelector = { - checkable?: boolean, - checked?: boolean, - clazz?: string, - clickable?: boolean, - depth?: number, - desc?: string, - enabled?: boolean, - focusable?: boolean, - focused?: boolean, - hasChild?: { - androidSelector: AndroidSelector, - }, - hasDescendant?: { - androidSelector: AndroidSelector, - maxDepth?: number, - }, - longClickable?: boolean, - pkg?: string, - res?: string, - scrollable?: boolean, - selected?: boolean, - text?: string, -}; - -export type AndroidElementInfo = { - children?: AndroidElementInfo[], - clazz: string, - desc: string, - res: string, - pkg: string, - text: string, - bounds: Rect, - checkable: boolean, - checked: boolean, - clickable: boolean, - enabled: boolean, - focusable: boolean, - focused: boolean, - longClickable: boolean, - scrollable: boolean, - selected: boolean, -}; - // ----------- APIRequestContext ----------- export type APIRequestContextInitializer = { tracing: TracingChannel, @@ -728,16 +676,6 @@ export type APIRequestContextDisposeResult = void; export interface APIRequestContextEvents { } -export type APIResponse = { - fetchUid: string, - url: string, - status: number, - statusText: string, - headers: NameValue[], - securityDetails?: SecurityDetails, - serverAddr?: RemoteAddr, -}; - // ----------- Artifact ----------- export type ArtifactInitializer = { absolutePath: string, @@ -2092,23 +2030,6 @@ export type BrowserTypeConnectToWorkerResult = { export interface BrowserTypeEvents { } -export type Metadata = { - location?: { - file: string, - line?: number, - column?: number, - }, - title?: string, - internal?: boolean, - stepId?: string, -}; - -export type ClientSideCallMetadata = { - id: number, - stack?: StackFrame[], -}; - -export type SDKLanguage = 'javascript' | 'python' | 'java' | 'csharp'; // ----------- Disposable ----------- export type DisposableInitializer = {}; export interface DisposableEventTarget { @@ -2124,14 +2045,6 @@ export type DisposableDisposeResult = void; export interface DisposableEvents { } -export type WaitInfo = { - waitId: string, - phase: 'before' | 'after' | 'log', - event?: string, - message?: string, - error?: string, -}; - // ----------- Electron ----------- export type ElectronInitializer = {}; export interface ElectronEventTarget { @@ -3713,33 +3626,6 @@ export type LocalUtilsGlobToRegexResult = { export interface LocalUtilsEvents { } -export type SetNetworkCookie = { - name: string, - value: string, - url?: string, - domain?: string, - path?: string, - expires?: number, - httpOnly?: boolean, - secure?: boolean, - sameSite?: 'Strict' | 'Lax' | 'None', - partitionKey?: string, - _crHasCrossSiteAncestor?: boolean, -}; - -export type NetworkCookie = { - name: string, - value: string, - domain: string, - path: string, - expires: number, - httpOnly: boolean, - secure: boolean, - sameSite: 'Strict' | 'Lax' | 'None', - partitionKey?: string, - _crHasCrossSiteAncestor?: boolean, -}; - // ----------- Request ----------- export type RequestInitializer = { frame?: FrameChannel, @@ -3921,17 +3807,6 @@ export interface WebSocketRouteEvents { 'closeServer': WebSocketRouteCloseServerEvent; } -export type ResourceTiming = { - startTime: number, - domainLookupStart: number, - domainLookupEnd: number, - connectStart: number, - secureConnectionStart: number, - connectEnd: number, - requestStart: number, - responseStart: number, -}; - // ----------- Response ----------- export type ResponseInitializer = { request: RequestChannel, @@ -3987,26 +3862,6 @@ export type ResponseSizesResult = { export interface ResponseEvents { } -export type SecurityDetails = { - issuer?: string, - protocol?: string, - subjectName?: string, - validFrom?: number, - validTo?: number, -}; - -export type RequestSizes = { - requestBodySize: number, - requestHeadersSize: number, - responseBodySize: number, - responseHeadersSize: number, -}; - -export type RemoteAddr = { - ipAddress: string, - port: number, -}; - // ----------- WebSocket ----------- export type WebSocketInitializer = { url: string, @@ -5110,93 +4965,6 @@ export interface JsonPipeEvents { 'closed': JsonPipeClosedEvent; } -export type ExpectedTextValue = { - string?: string, - regexSource?: string, - regexFlags?: string, - matchSubstring?: boolean, - ignoreCase?: boolean, - normalizeWhiteSpace?: boolean, -}; - -export type SelectorEngine = { - name: string, - source: string, - contentScript?: boolean, -}; - -export type FormField = { - name: string, - value?: string, - file?: { - name: string, - mimeType?: string, - buffer: Binary, - }, -}; - -export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle' | 'commit'; -export type ConsoleMessagesFilter = 'all' | 'since-navigation'; -export type RecorderSource = { - isRecorded: boolean, - id: string, - label: string, - text: string, - language: string, - highlight: { - line: number, - type: string, - }[], - revealLine?: number, - group?: string, -}; - -export type IndexedDBDatabase = { - name: string, - version: number, - stores: { - name: string, - autoIncrement: boolean, - keyPath?: string, - keyPathArray?: string[], - records: { - key?: any, - keyEncoded?: any, - value?: any, - valueEncoded?: any, - }[], - indexes: { - name: string, - keyPath?: string, - keyPathArray?: string[], - multiEntry: boolean, - unique: boolean, - }[], - }[], -}; - -export type SetOriginStorage = { - origin: string, - localStorage: NameValue[], - indexedDB?: IndexedDBDatabase[], -}; - -export type OriginStorage = { - origin: string, - localStorage: NameValue[], - indexedDB?: IndexedDBDatabase[], -}; - -export type RecordHarOptions = { - content?: 'embed' | 'attach' | 'omit', - mode?: 'full' | 'minimal', - urlGlob?: string, - urlRegexSource?: string, - urlRegexFlags?: string, - harPath?: string, - resourcesDir?: string, -}; - // ----------- CDPSession ----------- export type CDPSessionInitializer = {}; export interface CDPSessionEventTarget { @@ -5339,94 +5107,6 @@ export type DialogDismissResult = void; export interface DialogEvents { } -export type SerializedValue = { - n?: number, - b?: boolean, - s?: string, - v?: 'null' | 'undefined' | 'NaN' | 'Infinity' | '-Infinity' | '-0', - d?: string, - u?: string, - bi?: string, - ta?: { - b: Binary, - k: 'i8' | 'ui8' | 'ui8c' | 'i16' | 'ui16' | 'i32' | 'ui32' | 'f32' | 'f64' | 'bi64' | 'bui64', - }, - e?: { - m: string, - n: string, - s: string, - }, - r?: { - p: string, - f: string, - }, - a?: SerializedValue[], - o?: { - k: string, - v: SerializedValue, - }[], - h?: number, - id?: number, - ref?: number, -}; - -export type SerializedArgument = { - value: SerializedValue, - handles: Channel[], -}; - -export type SerializedError = { - error?: { - message: string, - name: string, - stack?: string, - }, - value?: SerializedValue, -}; - -export type StackFrame = { - file: string, - line: number, - column: number, - function?: string, -}; - -export type VirtualCredential = { - id: string, - rpId: string, - userHandle: string, - privateKey: string, - publicKey: string, -}; - -export type Point = { - x: number, - y: number, -}; - -export type Rect = { - x: number, - y: number, - width: number, - height: number, -}; - -export type URLPattern = { - hash: string, - hostname: string, - password: string, - pathname: string, - port: string, - protocol: string, - search: string, - username: string, -}; - -export type NameValue = { - name: string, - value: string, -}; - // ----------- Tracing ----------- export type TracingInitializer = {}; export interface TracingEventTarget { diff --git a/packages/playwright-core/src/protocol/serializers.ts b/packages/playwright-core/src/protocol/serializers.ts index ccf795af15987..020421c646381 100644 --- a/packages/playwright-core/src/protocol/serializers.ts +++ b/packages/playwright-core/src/protocol/serializers.ts @@ -14,36 +14,7 @@ * limitations under the License. */ -export type SerializedValue = { - n?: number, - b?: boolean, - s?: string, - v?: 'null' | 'undefined' | 'NaN' | 'Infinity' | '-Infinity' | '-0', - d?: string, - u?: string, - bi?: string, - ta?: { - b: Buffer, - k: 'i8' | 'ui8' | 'ui8c' | 'i16' | 'ui16' | 'i32' | 'ui32' | 'f32' | 'f64' | 'bi64' | 'bui64', - }, - e?: { - m: string, - n: string, - s: string, - }, - r?: { - p: string, - f: string, - }, - a?: SerializedValue[], - o?: { - k: string, - v: SerializedValue, - }[], - h?: number, - id?: number, - ref?: number, -}; +import type { SerializedValue } from '@protocol/structs'; export function parseSerializedValue(value: SerializedValue, handles: any[] | undefined): any { return innerParseSerializedValue(value, handles, new Map(), []); diff --git a/packages/playwright-core/src/server/channels.d.ts b/packages/playwright-core/src/server/channels.d.ts index 153920cac69b2..a28946f3b63a1 100644 --- a/packages/playwright-core/src/server/channels.d.ts +++ b/packages/playwright-core/src/server/channels.d.ts @@ -18,10 +18,8 @@ import type { Progress } from './progress'; -export type Binary = Buffer; - -export interface Channel { -} +import type { Binary, Channel, AndroidWebView, AndroidSelector, AndroidElementInfo, APIResponse, Metadata, ClientSideCallMetadata, SDKLanguage, WaitInfo, SetNetworkCookie, NetworkCookie, ResourceTiming, SecurityDetails, RequestSizes, RemoteAddr, ExpectedTextValue, SelectorEngine, FormField, LifecycleEvent, ConsoleMessagesFilter, RecorderSource, IndexedDBDatabase, SetOriginStorage, OriginStorage, RecordHarOptions, SerializedValue, SerializedArgument, SerializedError, StackFrame, VirtualCredential, Point, Rect, URLPattern, NameValue } from '@protocol/structs'; +export type { Binary, Channel, AndroidWebView, AndroidSelector, AndroidElementInfo, APIResponse, Metadata, ClientSideCallMetadata, SDKLanguage, WaitInfo, SetNetworkCookie, NetworkCookie, ResourceTiming, SecurityDetails, RequestSizes, RemoteAddr, ExpectedTextValue, SelectorEngine, FormField, LifecycleEvent, ConsoleMessagesFilter, RecorderSource, IndexedDBDatabase, SetOriginStorage, OriginStorage, RecordHarOptions, SerializedValue, SerializedArgument, SerializedError, StackFrame, VirtualCredential, Point, Rect, URLPattern, NameValue } from '@protocol/structs'; // ----------- Initializer Traits ----------- export type InitializerTraits = @@ -587,56 +585,6 @@ export interface AndroidDeviceEvents { 'webViewRemoved': AndroidDeviceWebViewRemovedEvent; } -export type AndroidWebView = { - pid: number, - pkg: string, - socketName: string, -}; - -export type AndroidSelector = { - checkable?: boolean, - checked?: boolean, - clazz?: string, - clickable?: boolean, - depth?: number, - desc?: string, - enabled?: boolean, - focusable?: boolean, - focused?: boolean, - hasChild?: { - androidSelector: AndroidSelector, - }, - hasDescendant?: { - androidSelector: AndroidSelector, - maxDepth?: number, - }, - longClickable?: boolean, - pkg?: string, - res?: string, - scrollable?: boolean, - selected?: boolean, - text?: string, -}; - -export type AndroidElementInfo = { - children?: AndroidElementInfo[], - clazz: string, - desc: string, - res: string, - pkg: string, - text: string, - bounds: Rect, - checkable: boolean, - checked: boolean, - clickable: boolean, - enabled: boolean, - focusable: boolean, - focused: boolean, - longClickable: boolean, - scrollable: boolean, - selected: boolean, -}; - // ----------- APIRequestContext ----------- export type APIRequestContextInitializer = { tracing: TracingChannel, @@ -731,16 +679,6 @@ export type APIRequestContextDisposeResult = void; export interface APIRequestContextEvents { } -export type APIResponse = { - fetchUid: string, - url: string, - status: number, - statusText: string, - headers: NameValue[], - securityDetails?: SecurityDetails, - serverAddr?: RemoteAddr, -}; - // ----------- Artifact ----------- export type ArtifactInitializer = { absolutePath: string, @@ -2095,23 +2033,6 @@ export type BrowserTypeConnectToWorkerResult = { export interface BrowserTypeEvents { } -export type Metadata = { - location?: { - file: string, - line?: number, - column?: number, - }, - title?: string, - internal?: boolean, - stepId?: string, -}; - -export type ClientSideCallMetadata = { - id: number, - stack?: StackFrame[], -}; - -export type SDKLanguage = 'javascript' | 'python' | 'java' | 'csharp'; // ----------- Disposable ----------- export type DisposableInitializer = {}; export interface DisposableEventTarget { @@ -2127,14 +2048,6 @@ export type DisposableDisposeResult = void; export interface DisposableEvents { } -export type WaitInfo = { - waitId: string, - phase: 'before' | 'after' | 'log', - event?: string, - message?: string, - error?: string, -}; - // ----------- Electron ----------- export type ElectronInitializer = {}; export interface ElectronEventTarget { @@ -3716,33 +3629,6 @@ export type LocalUtilsGlobToRegexResult = { export interface LocalUtilsEvents { } -export type SetNetworkCookie = { - name: string, - value: string, - url?: string, - domain?: string, - path?: string, - expires?: number, - httpOnly?: boolean, - secure?: boolean, - sameSite?: 'Strict' | 'Lax' | 'None', - partitionKey?: string, - _crHasCrossSiteAncestor?: boolean, -}; - -export type NetworkCookie = { - name: string, - value: string, - domain: string, - path: string, - expires: number, - httpOnly: boolean, - secure: boolean, - sameSite: 'Strict' | 'Lax' | 'None', - partitionKey?: string, - _crHasCrossSiteAncestor?: boolean, -}; - // ----------- Request ----------- export type RequestInitializer = { frame?: FrameChannel, @@ -3924,17 +3810,6 @@ export interface WebSocketRouteEvents { 'closeServer': WebSocketRouteCloseServerEvent; } -export type ResourceTiming = { - startTime: number, - domainLookupStart: number, - domainLookupEnd: number, - connectStart: number, - secureConnectionStart: number, - connectEnd: number, - requestStart: number, - responseStart: number, -}; - // ----------- Response ----------- export type ResponseInitializer = { request: RequestChannel, @@ -3990,26 +3865,6 @@ export type ResponseSizesResult = { export interface ResponseEvents { } -export type SecurityDetails = { - issuer?: string, - protocol?: string, - subjectName?: string, - validFrom?: number, - validTo?: number, -}; - -export type RequestSizes = { - requestBodySize: number, - requestHeadersSize: number, - responseBodySize: number, - responseHeadersSize: number, -}; - -export type RemoteAddr = { - ipAddress: string, - port: number, -}; - // ----------- WebSocket ----------- export type WebSocketInitializer = { url: string, @@ -5113,93 +4968,6 @@ export interface JsonPipeEvents { 'closed': JsonPipeClosedEvent; } -export type ExpectedTextValue = { - string?: string, - regexSource?: string, - regexFlags?: string, - matchSubstring?: boolean, - ignoreCase?: boolean, - normalizeWhiteSpace?: boolean, -}; - -export type SelectorEngine = { - name: string, - source: string, - contentScript?: boolean, -}; - -export type FormField = { - name: string, - value?: string, - file?: { - name: string, - mimeType?: string, - buffer: Binary, - }, -}; - -export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle' | 'commit'; -export type ConsoleMessagesFilter = 'all' | 'since-navigation'; -export type RecorderSource = { - isRecorded: boolean, - id: string, - label: string, - text: string, - language: string, - highlight: { - line: number, - type: string, - }[], - revealLine?: number, - group?: string, -}; - -export type IndexedDBDatabase = { - name: string, - version: number, - stores: { - name: string, - autoIncrement: boolean, - keyPath?: string, - keyPathArray?: string[], - records: { - key?: any, - keyEncoded?: any, - value?: any, - valueEncoded?: any, - }[], - indexes: { - name: string, - keyPath?: string, - keyPathArray?: string[], - multiEntry: boolean, - unique: boolean, - }[], - }[], -}; - -export type SetOriginStorage = { - origin: string, - localStorage: NameValue[], - indexedDB?: IndexedDBDatabase[], -}; - -export type OriginStorage = { - origin: string, - localStorage: NameValue[], - indexedDB?: IndexedDBDatabase[], -}; - -export type RecordHarOptions = { - content?: 'embed' | 'attach' | 'omit', - mode?: 'full' | 'minimal', - urlGlob?: string, - urlRegexSource?: string, - urlRegexFlags?: string, - harPath?: string, - resourcesDir?: string, -}; - // ----------- CDPSession ----------- export type CDPSessionInitializer = {}; export interface CDPSessionEventTarget { @@ -5342,94 +5110,6 @@ export type DialogDismissResult = void; export interface DialogEvents { } -export type SerializedValue = { - n?: number, - b?: boolean, - s?: string, - v?: 'null' | 'undefined' | 'NaN' | 'Infinity' | '-Infinity' | '-0', - d?: string, - u?: string, - bi?: string, - ta?: { - b: Binary, - k: 'i8' | 'ui8' | 'ui8c' | 'i16' | 'ui16' | 'i32' | 'ui32' | 'f32' | 'f64' | 'bi64' | 'bui64', - }, - e?: { - m: string, - n: string, - s: string, - }, - r?: { - p: string, - f: string, - }, - a?: SerializedValue[], - o?: { - k: string, - v: SerializedValue, - }[], - h?: number, - id?: number, - ref?: number, -}; - -export type SerializedArgument = { - value: SerializedValue, - handles: Channel[], -}; - -export type SerializedError = { - error?: { - message: string, - name: string, - stack?: string, - }, - value?: SerializedValue, -}; - -export type StackFrame = { - file: string, - line: number, - column: number, - function?: string, -}; - -export type VirtualCredential = { - id: string, - rpId: string, - userHandle: string, - privateKey: string, - publicKey: string, -}; - -export type Point = { - x: number, - y: number, -}; - -export type Rect = { - x: number, - y: number, - width: number, - height: number, -}; - -export type URLPattern = { - hash: string, - hostname: string, - password: string, - pathname: string, - port: string, - protocol: string, - search: string, - username: string, -}; - -export type NameValue = { - name: string, - value: string, -}; - // ----------- Tracing ----------- export type TracingInitializer = {}; export interface TracingEventTarget { diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index 1a3787c1379ce..7af8c4d97220e 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/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 658, "height": 320 @@ -176,7 +176,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S24": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 360, "height": 780 @@ -187,7 +187,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S24 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 780, "height": 360 @@ -198,7 +198,7 @@ "defaultBrowserType": "chromium" }, "Galaxy A55": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 480, "height": 1040 @@ -209,7 +209,7 @@ "defaultBrowserType": "chromium" }, "Galaxy A55 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 1040, "height": 480 @@ -220,7 +220,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", "viewport": { "width": 712, "height": 1138 @@ -231,7 +231,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/150.0.7871.24 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", "viewport": { "width": 1138, "height": 712 @@ -242,7 +242,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S9": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", "viewport": { "width": 640, "height": 1024 @@ -253,7 +253,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S9 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", "viewport": { "width": 1024, "height": 640 @@ -264,7 +264,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 928, "height": 1080 @@ -279,7 +279,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 1080, "height": 928 @@ -294,7 +294,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 6 Cover": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 484, "height": 1188 @@ -309,7 +309,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 6 Cover landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 1188, "height": 484 @@ -324,7 +324,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 984, "height": 1092 @@ -339,7 +339,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 1092, "height": 984 @@ -354,7 +354,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 7 Cover": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 360, "height": 840 @@ -369,7 +369,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 7 Cover landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 840, "height": 360 @@ -384,7 +384,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 360, "height": 880 @@ -399,7 +399,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 880, "height": 360 @@ -414,7 +414,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 6 Cover": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 360, "height": 374 @@ -429,7 +429,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 6 Cover landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 374, "height": 360 @@ -444,7 +444,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 360, "height": 840 @@ -459,7 +459,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 840, "height": 360 @@ -474,7 +474,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 7 Cover": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 474, "height": 524 @@ -489,7 +489,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 7 Cover landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 524, "height": 474 @@ -1748,7 +1748,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1759,7 +1759,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1770,7 +1770,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1781,7 +1781,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1792,7 +1792,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1803,7 +1803,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1814,7 +1814,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/150.0.7871.24 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", "viewport": { "width": 800, "height": 1280 @@ -1825,7 +1825,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/150.0.7871.24 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", "viewport": { "width": 1280, "height": 800 @@ -1836,7 +1836,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/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1847,7 +1847,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/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1858,7 +1858,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1869,7 +1869,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/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1880,7 +1880,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1891,7 +1891,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1902,7 +1902,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/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1913,7 +1913,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/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1924,7 +1924,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1935,7 +1935,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1946,7 +1946,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/150.0.7871.24 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", "viewport": { "width": 600, "height": 960 @@ -1957,7 +1957,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/150.0.7871.24 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", "viewport": { "width": 960, "height": 600 @@ -2012,7 +2012,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 411, "height": 731 @@ -2023,7 +2023,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 731, "height": 411 @@ -2034,7 +2034,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 411, "height": 823 @@ -2045,7 +2045,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 823, "height": 411 @@ -2056,7 +2056,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 393, "height": 786 @@ -2067,7 +2067,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/150.0.7871.24 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/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 786, "height": 393 @@ -2078,7 +2078,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 353, "height": 745 @@ -2089,7 +2089,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 745, "height": 353 @@ -2100,7 +2100,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -2115,7 +2115,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "height": 892, "width": 412 @@ -2130,7 +2130,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 393, "height": 851 @@ -2145,7 +2145,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 851, "height": 393 @@ -2160,7 +2160,7 @@ "defaultBrowserType": "chromium" }, "Pixel 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -2175,7 +2175,7 @@ "defaultBrowserType": "chromium" }, "Pixel 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -2190,7 +2190,7 @@ "defaultBrowserType": "chromium" }, "Pixel 6 Pro": { - "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -2205,7 +2205,7 @@ "defaultBrowserType": "chromium" }, "Pixel 6 Pro landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 892, "height": 412 @@ -2220,7 +2220,7 @@ "defaultBrowserType": "chromium" }, "Pixel 6a": { - "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -2235,7 +2235,7 @@ "defaultBrowserType": "chromium" }, "Pixel 6a landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -2250,7 +2250,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -2265,7 +2265,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -2280,7 +2280,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 Pro": { - "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -2295,7 +2295,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 Pro landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 892, "height": 412 @@ -2310,7 +2310,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7a": { - "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -2325,7 +2325,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7a landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -2340,7 +2340,7 @@ "defaultBrowserType": "chromium" }, "Pixel 8": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -2355,7 +2355,7 @@ "defaultBrowserType": "chromium" }, "Pixel 8 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -2370,7 +2370,7 @@ "defaultBrowserType": "chromium" }, "Pixel 8 Pro": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 448, "height": 997 @@ -2385,7 +2385,7 @@ "defaultBrowserType": "chromium" }, "Pixel 8 Pro landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 997, "height": 448 @@ -2400,7 +2400,7 @@ "defaultBrowserType": "chromium" }, "Pixel 8a": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -2415,7 +2415,7 @@ "defaultBrowserType": "chromium" }, "Pixel 8a landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -2430,7 +2430,7 @@ "defaultBrowserType": "chromium" }, "Pixel 9": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 360, "height": 808 @@ -2445,7 +2445,7 @@ "defaultBrowserType": "chromium" }, "Pixel 9 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 808, "height": 360 @@ -2460,7 +2460,7 @@ "defaultBrowserType": "chromium" }, "Pixel 9 Pro": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 427, "height": 952 @@ -2475,7 +2475,7 @@ "defaultBrowserType": "chromium" }, "Pixel 9 Pro landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 952, "height": 427 @@ -2490,7 +2490,7 @@ "defaultBrowserType": "chromium" }, "Pixel 9 Pro XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 448, "height": 997 @@ -2505,7 +2505,7 @@ "defaultBrowserType": "chromium" }, "Pixel 9 Pro XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 997, "height": 448 @@ -2520,7 +2520,7 @@ "defaultBrowserType": "chromium" }, "Pixel 10": { - "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 360, "height": 808 @@ -2535,7 +2535,7 @@ "defaultBrowserType": "chromium" }, "Pixel 10 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 808, "height": 360 @@ -2550,7 +2550,7 @@ "defaultBrowserType": "chromium" }, "Pixel 10 Pro": { - "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 427, "height": 952 @@ -2565,7 +2565,7 @@ "defaultBrowserType": "chromium" }, "Pixel 10 Pro landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 952, "height": 427 @@ -2580,7 +2580,7 @@ "defaultBrowserType": "chromium" }, "Pixel 10 Pro XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 448, "height": 997 @@ -2595,7 +2595,7 @@ "defaultBrowserType": "chromium" }, "Pixel 10 Pro XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "screen": { "width": 997, "height": 448 @@ -2610,7 +2610,7 @@ "defaultBrowserType": "chromium" }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -2621,7 +2621,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -2632,7 +2632,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", "screen": { "width": 1792, "height": 1120 @@ -2647,7 +2647,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Safari/537.36 Edg/150.0.7871.24", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36 Edg/150.0.7871.46", "screen": { "width": 1792, "height": 1120 @@ -2692,7 +2692,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", "screen": { "width": 1920, "height": 1080 @@ -2707,7 +2707,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.24 Safari/537.36 Edg/150.0.7871.24", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36 Edg/150.0.7871.46", "screen": { "width": 1920, "height": 1080 diff --git a/packages/protocol/src/structs.d.ts b/packages/protocol/src/structs.d.ts new file mode 100644 index 0000000000000..a44c990626d31 --- /dev/null +++ b/packages/protocol/src/structs.d.ts @@ -0,0 +1,344 @@ +/** + * 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. + */ + +// This file is generated by generate_channels.js, do not edit manually. + +export type Binary = Buffer; + +export interface Channel { +} + +export type AndroidWebView = { + pid: number, + pkg: string, + socketName: string, +}; + +export type AndroidSelector = { + checkable?: boolean, + checked?: boolean, + clazz?: string, + clickable?: boolean, + depth?: number, + desc?: string, + enabled?: boolean, + focusable?: boolean, + focused?: boolean, + hasChild?: { + androidSelector: AndroidSelector, + }, + hasDescendant?: { + androidSelector: AndroidSelector, + maxDepth?: number, + }, + longClickable?: boolean, + pkg?: string, + res?: string, + scrollable?: boolean, + selected?: boolean, + text?: string, +}; + +export type AndroidElementInfo = { + children?: AndroidElementInfo[], + clazz: string, + desc: string, + res: string, + pkg: string, + text: string, + bounds: Rect, + checkable: boolean, + checked: boolean, + clickable: boolean, + enabled: boolean, + focusable: boolean, + focused: boolean, + longClickable: boolean, + scrollable: boolean, + selected: boolean, +}; + +export type APIResponse = { + fetchUid: string, + url: string, + status: number, + statusText: string, + headers: NameValue[], + securityDetails?: SecurityDetails, + serverAddr?: RemoteAddr, +}; + +export type Metadata = { + location?: { + file: string, + line?: number, + column?: number, + }, + title?: string, + internal?: boolean, + stepId?: string, +}; + +export type ClientSideCallMetadata = { + id: number, + stack?: StackFrame[], +}; + +export type SDKLanguage = 'javascript' | 'python' | 'java' | 'csharp'; + +export type WaitInfo = { + waitId: string, + phase: 'before' | 'after' | 'log', + event?: string, + message?: string, + error?: string, +}; + +export type SetNetworkCookie = { + name: string, + value: string, + url?: string, + domain?: string, + path?: string, + expires?: number, + httpOnly?: boolean, + secure?: boolean, + sameSite?: 'Strict' | 'Lax' | 'None', + partitionKey?: string, + _crHasCrossSiteAncestor?: boolean, +}; + +export type NetworkCookie = { + name: string, + value: string, + domain: string, + path: string, + expires: number, + httpOnly: boolean, + secure: boolean, + sameSite: 'Strict' | 'Lax' | 'None', + partitionKey?: string, + _crHasCrossSiteAncestor?: boolean, +}; + +export type ResourceTiming = { + startTime: number, + domainLookupStart: number, + domainLookupEnd: number, + connectStart: number, + secureConnectionStart: number, + connectEnd: number, + requestStart: number, + responseStart: number, +}; + +export type SecurityDetails = { + issuer?: string, + protocol?: string, + subjectName?: string, + validFrom?: number, + validTo?: number, +}; + +export type RequestSizes = { + requestBodySize: number, + requestHeadersSize: number, + responseBodySize: number, + responseHeadersSize: number, +}; + +export type RemoteAddr = { + ipAddress: string, + port: number, +}; + +export type ExpectedTextValue = { + string?: string, + regexSource?: string, + regexFlags?: string, + matchSubstring?: boolean, + ignoreCase?: boolean, + normalizeWhiteSpace?: boolean, +}; + +export type SelectorEngine = { + name: string, + source: string, + contentScript?: boolean, +}; + +export type FormField = { + name: string, + value?: string, + file?: { + name: string, + mimeType?: string, + buffer: Binary, + }, +}; + +export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle' | 'commit'; + +export type ConsoleMessagesFilter = 'all' | 'since-navigation'; + +export type RecorderSource = { + isRecorded: boolean, + id: string, + label: string, + text: string, + language: string, + highlight: { + line: number, + type: string, + }[], + revealLine?: number, + group?: string, +}; + +export type IndexedDBDatabase = { + name: string, + version: number, + stores: { + name: string, + autoIncrement: boolean, + keyPath?: string, + keyPathArray?: string[], + records: { + key?: any, + keyEncoded?: any, + value?: any, + valueEncoded?: any, + }[], + indexes: { + name: string, + keyPath?: string, + keyPathArray?: string[], + multiEntry: boolean, + unique: boolean, + }[], + }[], +}; + +export type SetOriginStorage = { + origin: string, + localStorage: NameValue[], + indexedDB?: IndexedDBDatabase[], +}; + +export type OriginStorage = { + origin: string, + localStorage: NameValue[], + indexedDB?: IndexedDBDatabase[], +}; + +export type RecordHarOptions = { + content?: 'embed' | 'attach' | 'omit', + mode?: 'full' | 'minimal', + urlGlob?: string, + urlRegexSource?: string, + urlRegexFlags?: string, + harPath?: string, + resourcesDir?: string, +}; + +export type SerializedValue = { + n?: number, + b?: boolean, + s?: string, + v?: 'null' | 'undefined' | 'NaN' | 'Infinity' | '-Infinity' | '-0', + d?: string, + u?: string, + bi?: string, + ta?: { + b: Binary, + k: 'i8' | 'ui8' | 'ui8c' | 'i16' | 'ui16' | 'i32' | 'ui32' | 'f32' | 'f64' | 'bi64' | 'bui64', + }, + e?: { + m: string, + n: string, + s: string, + }, + r?: { + p: string, + f: string, + }, + a?: SerializedValue[], + o?: { + k: string, + v: SerializedValue, + }[], + h?: number, + id?: number, + ref?: number, +}; + +export type SerializedArgument = { + value: SerializedValue, + handles: Channel[], +}; + +export type SerializedError = { + error?: { + message: string, + name: string, + stack?: string, + }, + value?: SerializedValue, +}; + +export type StackFrame = { + file: string, + line: number, + column: number, + function?: string, +}; + +export type VirtualCredential = { + id: string, + rpId: string, + userHandle: string, + privateKey: string, + publicKey: string, +}; + +export type Point = { + x: number, + y: number, +}; + +export type Rect = { + x: number, + y: number, + width: number, + height: number, +}; + +export type URLPattern = { + hash: string, + hostname: string, + password: string, + pathname: string, + port: string, + protocol: string, + search: string, + username: string, +}; + +export type NameValue = { + name: string, + value: string, +}; + diff --git a/tests/config/ghaMarkdownReporter.ts b/tests/config/markdownReporter.ts similarity index 50% rename from tests/config/ghaMarkdownReporter.ts rename to tests/config/markdownReporter.ts index 548e2a2452772..3d477e4a9786c 100644 --- a/tests/config/ghaMarkdownReporter.ts +++ b/tests/config/markdownReporter.ts @@ -17,19 +17,19 @@ import fs from 'fs'; import path from 'path'; -import type { MetadataWithCommitInfo } from '@testIsomorphic/types'; -import type { IssueCommentEdge, Repository } from '@octokit/graphql-schema'; import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestError } from '@playwright/test/reporter'; type MarkdownReporterOptions = { configDir: string, // TODO: make it public? - outputFile?: string; }; +// Produces the markdown summary of a merged report and writes it to a file. The actual PR +// comment is posted by a separate workflow step (see postReportComment.js) once the HTML +// report has been uploaded as an artifact and its URL is known. class MarkdownReporter implements Reporter { private _options: MarkdownReporterOptions; private _fatalErrors: TestError[] = []; - protected _config!: FullConfig; + private _config!: FullConfig; private _suite!: Suite; constructor(options: MarkdownReporterOptions) { @@ -82,24 +82,24 @@ class MarkdownReporter implements Reporter { lines.push(`**${summary.expected} passed${skipped}${didNotRun}**`); lines.push(``); - await this.publishReport(lines.join('\n')); + await this._writeReport(lines.join('\n')); } - protected async publishReport(report: string): Promise { - const maybeRelativeFile = this._options.outputFile || 'report.md'; + private async _writeReport(report: string): Promise { + const maybeRelativeFile = process.env.MARKDOWN_OUTPUT_FILE || 'report.md'; const reportFile = path.resolve(this._options.configDir, maybeRelativeFile); await fs.promises.mkdir(path.dirname(reportFile), { recursive: true }); await fs.promises.writeFile(reportFile, report); } - protected _incompleteRunWarning(): string | undefined { + private _incompleteRunWarning(): string | undefined { const conclusion = process.env.WORKFLOW_RUN_CONCLUSION; if (!conclusion || conclusion === 'success' || conclusion === 'failure') return undefined; return `> [!WARNING]\n> The triggering workflow run ended with status \`${conclusion}\`. Results below may be incomplete — blob reports from cancelled or timed-out shards are missing, so passing/failing counts do not reflect the full test suite.`; } - protected _generateSummary() { + private _generateSummary() { let didNotRun = 0; let skipped = 0; let expected = 0; @@ -158,134 +158,4 @@ function formatTestTitle(rootDir: string, test: TestCase): string { return `${testTitle}${extraTags.length ? ' ' + formattedTags : ''}`; } -class GHAMarkdownReporter extends MarkdownReporter { - private octokit: ReturnType; - private context: typeof import('@actions/github').context; - private core: typeof import('@actions/core'); - - override async publishReport(report: string) { - this.core = await import('@actions/core'); - const token = process.env.GITHUB_TOKEN || this.core.getInput('github-token'); - if (!token) { - this.core.setFailed('Missing "github-token" input'); - throw new Error('Missing "github-token" input'); - } - const { context, getOctokit } = await import('@actions/github'); - this.context = context; - this.octokit = getOctokit(token); - - this.core.info('Publishing report to PR.'); - const { prNumber, prHref } = this.pullRequestFromMetadata(); - if (!prNumber) { - this.core.info(`No PR number found, skipping GHA comment. PR href: ${prHref}`); - return; - } - this.core.info(`Posting comment to PR ${prHref}`); - - const prNodeId = await this.collapsePreviousComments(prNumber); - if (!prNodeId) { - this.core.warning(`No PR node ID found, skipping GHA comment. PR href: ${prHref}`); - return; - } - await this.addNewReportComment(prNodeId, report); - } - - private async collapsePreviousComments(prNumber: number) { - const { owner, repo } = this.context.repo; - const data = await this.octokit.graphql<{ repository: Repository }>(` - query($owner: String!, $repo: String!, $prNumber: Int!) { - repository(owner: $owner, name: $repo) { - pullRequest(number: $prNumber) { - id - comments(last: 100) { - nodes { - id - body - author { - __typename - login - } - } - } - } - } - } - `, { owner, repo, prNumber }); - const comments = data.repository.pullRequest?.comments.nodes?.filter(comment => - comment?.author?.__typename === 'Bot' && - comment?.author?.login === 'github-actions' && - comment.body?.includes(this._magicComment())); - const prId = data.repository.pullRequest?.id; - if (!comments?.length) - return prId; - const variableDecls = comments.map((_, i) => `$id${i}: ID!`).join(', '); - const mutations = comments.map((_, i) => - `m${i}: minimizeComment(input: { subjectId: $id${i}, classifier: OUTDATED }) { clientMutationId }`); - const subjectIds = Object.fromEntries(comments.map((comment, i) => [`id${i}`, comment!.id])); - await this.octokit.graphql(` - mutation(${variableDecls}) { - ${mutations.join('\n')} - } - `, subjectIds); - return prId; - } - - private _magicComment() { - return ``; - } - - private _workflowRunName() { - // When used via 'workflow_run' event. - const workflowRunName = this.context.payload.workflow_run?.name; - if (workflowRunName) - return workflowRunName; - // When used via 'pull_request'/'push' event. - // This is the name of the workflow file, e.g. 'ci.yml' or name if set. - return process.env.GITHUB_WORKFLOW; - } - - private async addNewReportComment(prNodeId: string, report: string) { - const reportUrl = process.env.HTML_REPORT_URL; - const mergeWorkflowUrl = `${this.context.serverUrl}/${this.context.repo.owner}/${this.context.repo.repo}/actions/runs/${this.context.runId}`; - - const body = formatComment([ - this._magicComment(), - `### ${reportUrl ? `[Test results](${reportUrl})` : 'Test results'} for "${this._workflowRunName()}"`, - report, - '', - '---', - '', - `Merge [workflow run](${mergeWorkflowUrl}).` - ]); - - const response = await this.octokit.graphql<{ addComment: { commentEdge: IssueCommentEdge } }>(` - mutation($subjectId: ID!, $body: String!) { - addComment(input: {subjectId: $subjectId, body: $body}) { - commentEdge { - node { - ... on IssueComment { - url - } - } - } - } - } - `, { subjectId: prNodeId, body }); - this.core.info(`Posted comment: ${response.addComment.commentEdge.node?.url}`); - } - - private pullRequestFromMetadata() { - const metadata = this._config.metadata as MetadataWithCommitInfo; - const prHref = metadata.ci?.prHref; - return { prNumber: parseInt(prHref?.split('/').pop() ?? '', 10), prHref }; - } -} - -function formatComment(lines: string[]) { - let body = lines.join('\n'); - if (body.length > 65535) - body = body.substring(0, 65000) + `... ${body.length - 65000} more characters`; - return body; -} - -export default GHAMarkdownReporter; +export default MarkdownReporter; diff --git a/tests/config/postReportComment.js b/tests/config/postReportComment.js new file mode 100644 index 0000000000000..55378d3a1880d --- /dev/null +++ b/tests/config/postReportComment.js @@ -0,0 +1,129 @@ +/** + * 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. + */ + +const fs = require('fs'); +const path = require('path'); + +/** + * @param {{ github: any, context: any, core: any, reportFile: string, prNumber: number, reportUrl?: string }} params + */ +async function postReportComment({ github, context, core, reportFile, prNumber, reportUrl }) { + if (!prNumber) { + core.info('No PR number provided, skipping GHA comment.'); + return; + } + const report = fs.readFileSync(path.resolve(process.cwd(), reportFile), 'utf8'); + core.info(`Posting comment to PR #${prNumber}`); + + const prNodeId = await collapsePreviousComments(github, context, prNumber); + if (!prNodeId) { + core.warning(`No PR node ID found for #${prNumber}, skipping GHA comment.`); + return; + } + const url = await addNewReportComment(github, context, prNodeId, report, reportUrl); + core.info(`Posted comment: ${url}`); +} + +function workflowRunName(context) { + // When used via 'workflow_run' event. + const name = context.payload.workflow_run?.name; + if (name) + return name; + // When used via 'pull_request'/'push' event. + return process.env.GITHUB_WORKFLOW; +} + +function magicComment(context) { + return ``; +} + +async function collapsePreviousComments(github, context, prNumber) { + const { owner, repo } = context.repo; + const data = await github.graphql(` + query($owner: String!, $repo: String!, $prNumber: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $prNumber) { + id + comments(last: 100) { + nodes { + id + body + author { + __typename + login + } + } + } + } + } + } + `, { owner, repo, prNumber }); + const comments = data.repository.pullRequest?.comments.nodes?.filter(comment => + comment?.author?.__typename === 'Bot' && + comment?.author?.login === 'github-actions' && + comment.body?.includes(magicComment(context))); + const prId = data.repository.pullRequest?.id; + if (!comments?.length) + return prId; + const variableDecls = comments.map((_, i) => `$id${i}: ID!`).join(', '); + const mutations = comments.map((_, i) => + `m${i}: minimizeComment(input: { subjectId: $id${i}, classifier: OUTDATED }) { clientMutationId }`); + const subjectIds = Object.fromEntries(comments.map((comment, i) => [`id${i}`, comment.id])); + await github.graphql(` + mutation(${variableDecls}) { + ${mutations.join('\n')} + } + `, subjectIds); + return prId; +} + +async function addNewReportComment(github, context, prNodeId, report, reportUrl) { + const mergeWorkflowUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + + const body = formatComment([ + magicComment(context), + `### ${reportUrl ? `[Test results](${reportUrl})` : 'Test results'} for "${workflowRunName(context)}"`, + report, + '', + '---', + '', + `Merge [workflow run](${mergeWorkflowUrl}).` + ]); + + const response = await github.graphql(` + mutation($subjectId: ID!, $body: String!) { + addComment(input: {subjectId: $subjectId, body: $body}) { + commentEdge { + node { + ... on IssueComment { + url + } + } + } + } + } + `, { subjectId: prNodeId, body }); + return response.addComment.commentEdge.node?.url; +} + +function formatComment(lines) { + let body = lines.join('\n'); + if (body.length > 65535) + body = body.substring(0, 65000) + `... ${body.length - 65000} more characters`; + return body; +} + +module.exports = { postReportComment }; diff --git a/tsconfig.json b/tsconfig.json index a027e586c8ff4..c26c7fc9c6851 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,7 @@ "@injected/*": ["./packages/injected/src/*"], "@isomorphic/*": ["./packages/isomorphic/*"], "@utils/*": ["./packages/utils/*"], + "@protocol/*": ["./packages/protocol/src/*"], "@recorder/*": ["./packages/recorder/src/*"], "@testIsomorphic/*": ["./packages/playwright/src/isomorphic/*"], "@trace/*": ["./packages/trace/src/*"], diff --git a/utils/generate_channels.js b/utils/generate_channels.js index cb03c6430ab63..374d334fa8a34 100755 --- a/utils/generate_channels.js +++ b/utils/generate_channels.js @@ -131,10 +131,8 @@ function channelsHeader(target) { import type { Progress } from './progress'; ` : '') + ` -export type Binary = Buffer; - -export interface Channel { -} +import type { Binary, Channel, ${structNames.join(', ')} } from '@protocol/structs'; +export type { Binary, Channel, ${structNames.join(', ')} } from '@protocol/structs'; `]; } @@ -197,6 +195,13 @@ for (const [name, value] of Object.entries(protocol)) { mixins.set(name, value); } +// Struct types (objects and enums) are identical for the client and the server, +// so they are generated once into a shared structs.d.ts file that both the +// client and the server channels.d.ts import and re-export. +const structNames = Object.entries(protocol) + .filter(([, value]) => value.type === 'object' || value.type === 'enum') + .map(([name]) => name); + const derivedClasses = new Map(); for (const [name, item] of Object.entries(protocol)) { if (item.type === 'interface' && item.extends) { @@ -371,12 +376,8 @@ function generateChannels(target) { } else if (item.type === 'object') { const inner = objectType(item.properties, ''); - channels_ts.push(`export type ${name} = ${inner.ts};`); - channels_ts.push(``); addScheme(name, inner.scheme); } else if (item.type === 'enum') { - const ts = item.literals.map(literal => `'${literal}'`).join(' | '); - channels_ts.push(`export type ${name} = ${ts};`) addScheme(name, `tEnum([${item.literals.map(literal => `'${literal}'`).join(', ')}])`); } } @@ -384,6 +385,50 @@ function generateChannels(target) { return channels_ts; } +function structsHeader() { + return `/** + * 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. + */ + +// This file is generated by ${path.basename(__filename).split(path.sep).join(path.posix.sep)}, do not edit manually. + +export type Binary = Buffer; + +export interface Channel { +} +`; +} + +function generateStructs() { + const structs_ts = [structsHeader()]; + for (const [name, item] of Object.entries(protocol)) { + if (item.type === 'object') { + const inner = objectType(item.properties, ''); + structs_ts.push(`export type ${name} = ${inner.ts};`); + structs_ts.push(``); + } else if (item.type === 'enum') { + const ts = item.literals.map(literal => `'${literal}'`).join(' | '); + structs_ts.push(`export type ${name} = ${ts};`); + structs_ts.push(``); + } + } + return structs_ts; +} + +const structs_ts = generateStructs(); + const client_channels_ts = generateChannels('Channel'); metainfo_ts.push(`export type MethodMetainfo = { internal?: boolean, title?: string, slowMo?: boolean, snapshot?: boolean, pause?: boolean, isAutoWaiting?: boolean, input?: boolean, group?: string }; @@ -416,6 +461,7 @@ function writeFile(filePath, content) { fs.writeFileSync(filePath, content, 'utf8'); } +writeFile(path.join(__dirname, '..', 'packages', 'protocol', 'src', 'structs.d.ts'), structs_ts.join('\n') + '\n'); writeFile(path.join(__dirname, '..', 'packages', 'playwright-core', 'src', 'client', 'channels.d.ts'), client_channels_ts.join('\n') + '\n'); writeFile(path.join(__dirname, '..', 'packages', 'playwright-core', 'src', 'server', 'channels.d.ts'), server_channels_ts.join('\n') + '\n'); writeFile(path.join(__dirname, '..', 'packages', 'isomorphic', 'protocolMetainfo.ts'), metainfoContent);