diff --git a/README.md b/README.md index 77900978dd665..1f8bc897fadbc 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-149.0.7827.3-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-150.0.2-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-26.4-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-149.0.7827.14-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-150.0.2-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-26.4-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 149.0.7827.3 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium1 149.0.7827.14 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 26.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 150.0.2 | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index 9697f25c49cd1..05fe4dc968db1 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -24759,8 +24759,36 @@ type Devices = { "Pixel 4a (5G) landscape": DeviceDescriptor; "Pixel 5": DeviceDescriptor; "Pixel 5 landscape": DeviceDescriptor; + "Pixel 6": DeviceDescriptor; + "Pixel 6 landscape": DeviceDescriptor; + "Pixel 6 Pro": DeviceDescriptor; + "Pixel 6 Pro landscape": DeviceDescriptor; + "Pixel 6a": DeviceDescriptor; + "Pixel 6a landscape": DeviceDescriptor; "Pixel 7": DeviceDescriptor; "Pixel 7 landscape": DeviceDescriptor; + "Pixel 7 Pro": DeviceDescriptor; + "Pixel 7 Pro landscape": DeviceDescriptor; + "Pixel 7a": DeviceDescriptor; + "Pixel 7a landscape": DeviceDescriptor; + "Pixel 8": DeviceDescriptor; + "Pixel 8 landscape": DeviceDescriptor; + "Pixel 8 Pro": DeviceDescriptor; + "Pixel 8 Pro landscape": DeviceDescriptor; + "Pixel 8a": DeviceDescriptor; + "Pixel 8a landscape": DeviceDescriptor; + "Pixel 9": DeviceDescriptor; + "Pixel 9 landscape": DeviceDescriptor; + "Pixel 9 Pro": DeviceDescriptor; + "Pixel 9 Pro landscape": DeviceDescriptor; + "Pixel 9 Pro XL": DeviceDescriptor; + "Pixel 9 Pro XL landscape": DeviceDescriptor; + "Pixel 10": DeviceDescriptor; + "Pixel 10 landscape": DeviceDescriptor; + "Pixel 10 Pro": DeviceDescriptor; + "Pixel 10 Pro landscape": DeviceDescriptor; + "Pixel 10 Pro XL": DeviceDescriptor; + "Pixel 10 Pro XL landscape": DeviceDescriptor; "Moto G4": DeviceDescriptor; "Moto G4 landscape": DeviceDescriptor; "Desktop Chrome HiDPI": DeviceDescriptor; diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index e81e9da9ce2b4..f8c2df5c831f1 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,16 +3,16 @@ "browsers": [ { "name": "chromium", - "revision": "1224", + "revision": "1225", "installByDefault": true, - "browserVersion": "149.0.7827.3", + "browserVersion": "149.0.7827.14", "title": "Chrome for Testing" }, { "name": "chromium-headless-shell", - "revision": "1224", + "revision": "1225", "installByDefault": true, - "browserVersion": "149.0.7827.3", + "browserVersion": "149.0.7827.14", "title": "Chrome Headless Shell" }, { diff --git a/packages/playwright-core/src/server/browser.ts b/packages/playwright-core/src/server/browser.ts index 0722c511ee41a..812d0f23df6ac 100644 --- a/packages/playwright-core/src/server/browser.ts +++ b/packages/playwright-core/src/server/browser.ts @@ -27,6 +27,7 @@ import { PlaywrightPipeServer } from '../remote/playwrightPipeServer'; import { PlaywrightWebSocketServer } from '../remote/playwrightWebSocketServer'; import { BrowserInfo, serverRegistry } from '../serverRegistry'; import { nullProgress } from './progress'; +import { TargetClosedError } from './errors'; import type * as types from './types'; import type { ProxySettings } from './types'; @@ -174,6 +175,8 @@ export abstract class Browser extends SdkObject { context.browserClosed(); if (this._defaultContext) this._defaultContext.browserClosed(); + for (const download of this._downloads.values()) + download.artifact.reportFinished(new TargetClosedError(undefined)); this.stopServer(nullProgress).catch(() => {}); this.emit(Browser.Events.Disconnected); this.instrumentation.onBrowserClose(this); diff --git a/packages/playwright-core/src/server/chromium/protocol.d.ts b/packages/playwright-core/src/server/chromium/protocol.d.ts index f2be2d97a3c46..6f3af1513f859 100644 --- a/packages/playwright-core/src/server/chromium/protocol.d.ts +++ b/packages/playwright-core/src/server/chromium/protocol.d.ts @@ -11578,7 +11578,7 @@ details; this boolean is true if that value is populated. /** * A fetch result for a device bound session creation or refresh. */ - export type DeviceBoundSessionFetchResult = "Success"|"KeyError"|"SigningError"|"ServerRequestedTermination"|"InvalidSessionId"|"InvalidChallenge"|"TooManyChallenges"|"InvalidFetcherUrl"|"InvalidRefreshUrl"|"TransientHttpError"|"ScopeOriginSameSiteMismatch"|"RefreshUrlSameSiteMismatch"|"MismatchedSessionId"|"MissingScope"|"NoCredentials"|"SubdomainRegistrationWellKnownUnavailable"|"SubdomainRegistrationUnauthorized"|"SubdomainRegistrationWellKnownMalformed"|"SessionProviderWellKnownUnavailable"|"RelyingPartyWellKnownUnavailable"|"FederatedKeyThumbprintMismatch"|"InvalidFederatedSessionUrl"|"InvalidFederatedKey"|"TooManyRelyingOriginLabels"|"BoundCookieSetForbidden"|"NetError"|"ProxyError"|"EmptySessionConfig"|"InvalidCredentialsConfig"|"InvalidCredentialsType"|"InvalidCredentialsEmptyName"|"InvalidCredentialsCookie"|"PersistentHttpError"|"RegistrationAttemptedChallenge"|"InvalidScopeOrigin"|"ScopeOriginContainsPath"|"RefreshInitiatorNotString"|"RefreshInitiatorInvalidHostPattern"|"InvalidScopeSpecification"|"MissingScopeSpecificationType"|"EmptyScopeSpecificationDomain"|"EmptyScopeSpecificationPath"|"InvalidScopeSpecificationType"|"InvalidScopeIncludeSite"|"MissingScopeIncludeSite"|"FederatedNotAuthorizedByProvider"|"FederatedNotAuthorizedByRelyingParty"|"SessionProviderWellKnownMalformed"|"SessionProviderWellKnownHasProviderOrigin"|"RelyingPartyWellKnownMalformed"|"RelyingPartyWellKnownHasRelyingOrigins"|"InvalidFederatedSessionProviderSessionMissing"|"InvalidFederatedSessionWrongProviderOrigin"|"InvalidCredentialsCookieCreationTime"|"InvalidCredentialsCookieName"|"InvalidCredentialsCookieParsing"|"InvalidCredentialsCookieUnpermittedAttribute"|"InvalidCredentialsCookieInvalidDomain"|"InvalidCredentialsCookiePrefix"|"InvalidScopeRulePath"|"InvalidScopeRuleHostPattern"|"ScopeRuleOriginScopedHostPatternMismatch"|"ScopeRuleSiteScopedHostPatternMismatch"|"SigningQuotaExceeded"|"InvalidConfigJson"|"InvalidFederatedSessionProviderFailedToRestoreKey"|"FailedToUnwrapKey"|"SessionDeletedDuringRefresh"; + export type DeviceBoundSessionFetchResult = "Success"|"KeyError"|"SigningError"|"TransientSigningError"|"ServerRequestedTermination"|"InvalidSessionId"|"InvalidChallenge"|"TooManyChallenges"|"InvalidFetcherUrl"|"InvalidRefreshUrl"|"TransientHttpError"|"ScopeOriginSameSiteMismatch"|"RefreshUrlSameSiteMismatch"|"MismatchedSessionId"|"MissingScope"|"NoCredentials"|"SubdomainRegistrationWellKnownUnavailable"|"SubdomainRegistrationUnauthorized"|"SubdomainRegistrationWellKnownMalformed"|"SessionProviderWellKnownUnavailable"|"RelyingPartyWellKnownUnavailable"|"FederatedKeyThumbprintMismatch"|"InvalidFederatedSessionUrl"|"InvalidFederatedKey"|"TooManyRelyingOriginLabels"|"BoundCookieSetForbidden"|"NetError"|"ProxyError"|"EmptySessionConfig"|"InvalidCredentialsConfig"|"InvalidCredentialsType"|"InvalidCredentialsEmptyName"|"InvalidCredentialsCookie"|"PersistentHttpError"|"RegistrationAttemptedChallenge"|"InvalidScopeOrigin"|"ScopeOriginContainsPath"|"RefreshInitiatorNotString"|"RefreshInitiatorInvalidHostPattern"|"InvalidScopeSpecification"|"MissingScopeSpecificationType"|"EmptyScopeSpecificationDomain"|"EmptyScopeSpecificationPath"|"InvalidScopeSpecificationType"|"InvalidScopeIncludeSite"|"MissingScopeIncludeSite"|"FederatedNotAuthorizedByProvider"|"FederatedNotAuthorizedByRelyingParty"|"SessionProviderWellKnownMalformed"|"SessionProviderWellKnownHasProviderOrigin"|"RelyingPartyWellKnownMalformed"|"RelyingPartyWellKnownHasRelyingOrigins"|"InvalidFederatedSessionProviderSessionMissing"|"InvalidFederatedSessionWrongProviderOrigin"|"InvalidCredentialsCookieCreationTime"|"InvalidCredentialsCookieName"|"InvalidCredentialsCookieParsing"|"InvalidCredentialsCookieUnpermittedAttribute"|"InvalidCredentialsCookieInvalidDomain"|"InvalidCredentialsCookiePrefix"|"InvalidScopeRulePath"|"InvalidScopeRuleHostPattern"|"ScopeRuleOriginScopedHostPatternMismatch"|"ScopeRuleSiteScopedHostPatternMismatch"|"SigningQuotaExceeded"|"InvalidConfigJson"|"InvalidFederatedSessionProviderFailedToRestoreKey"|"FailedToUnwrapKey"|"SessionDeletedDuringRefresh"; /** * Details about a failed device bound session network request. */ @@ -11628,7 +11628,7 @@ one. /** * The result of a refresh. */ - refreshResult: "Refreshed"|"RefreshedAsWaiter"|"InitializedService"|"Unreachable"|"ServerError"|"RefreshQuotaExceeded"|"FatalError"|"SigningQuotaExceeded"; + refreshResult: "Refreshed"|"InitializedService"|"Unreachable"|"ServerError"|"RefreshQuotaExceeded"|"FatalError"|"SigningQuotaExceeded"|"RefreshedAsWaiter"|"TransientSigningError"; /** * If there was a fetch attempt, the result of that. */ diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index f1ccb01c5bacf..3b042192fc258 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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 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/149.0.7827.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 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/149.0.7827.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 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/149.0.7827.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 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/149.0.7827.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Safari/537.36", "viewport": { "width": 1024, "height": 640 @@ -1208,7 +1208,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1219,7 +1219,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1230,7 +1230,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/149.0.7827.3 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1241,7 +1241,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/149.0.7827.3 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1252,7 +1252,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/149.0.7827.3 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1263,7 +1263,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/149.0.7827.3 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1274,7 +1274,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/149.0.7827.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Safari/537.36", "viewport": { "width": 800, "height": 1280 @@ -1285,7 +1285,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/149.0.7827.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Safari/537.36", "viewport": { "width": 1280, "height": 800 @@ -1296,7 +1296,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1307,7 +1307,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1318,7 +1318,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1329,7 +1329,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1340,7 +1340,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1351,7 +1351,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1362,7 +1362,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1373,7 +1373,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1384,7 +1384,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1395,7 +1395,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1406,7 +1406,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/149.0.7827.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Safari/537.36", "viewport": { "width": 600, "height": 960 @@ -1417,7 +1417,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/149.0.7827.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Safari/537.36", "viewport": { "width": 960, "height": 600 @@ -1472,7 +1472,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 411, "height": 731 @@ -1483,7 +1483,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 731, "height": 411 @@ -1494,7 +1494,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 411, "height": 823 @@ -1505,7 +1505,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 823, "height": 411 @@ -1516,7 +1516,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 393, "height": 786 @@ -1527,7 +1527,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/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 786, "height": 393 @@ -1538,7 +1538,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 353, "height": 745 @@ -1549,7 +1549,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 745, "height": 353 @@ -1560,7 +1560,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -1575,7 +1575,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "screen": { "height": 892, "width": 412 @@ -1590,7 +1590,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "screen": { "width": 393, "height": 851 @@ -1605,7 +1605,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "screen": { "width": 851, "height": 393 @@ -1619,8 +1619,98 @@ "hasTouch": true, "defaultBrowserType": "chromium" }, + "Pixel 6": { + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 412, + "height": 915 + }, + "viewport": { + "width": 412, + "height": 839 + }, + "deviceScaleFactor": 2.625, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 6 landscape": { + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 915, + "height": 412 + }, + "viewport": { + "width": 863, + "height": 360 + }, + "deviceScaleFactor": 2.625, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 6 Pro": { + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 412, + "height": 892 + }, + "viewport": { + "width": 412, + "height": 816 + }, + "deviceScaleFactor": 3.5, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 6 Pro landscape": { + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 892, + "height": 412 + }, + "viewport": { + "width": 840, + "height": 360 + }, + "deviceScaleFactor": 3.5, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 6a": { + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 412, + "height": 915 + }, + "viewport": { + "width": 412, + "height": 839 + }, + "deviceScaleFactor": 2.625, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 6a landscape": { + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 915, + "height": 412 + }, + "viewport": { + "width": 863, + "height": 360 + }, + "deviceScaleFactor": 2.625, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -1635,7 +1725,157 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", + "screen": { + "width": 915, + "height": 412 + }, + "viewport": { + "width": 863, + "height": 360 + }, + "deviceScaleFactor": 2.625, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 7 Pro": { + "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 412, + "height": 892 + }, + "viewport": { + "width": 412, + "height": 816 + }, + "deviceScaleFactor": 3.5, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 7 Pro landscape": { + "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 892, + "height": 412 + }, + "viewport": { + "width": 840, + "height": 360 + }, + "deviceScaleFactor": 3.5, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 7a": { + "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 412, + "height": 915 + }, + "viewport": { + "width": 412, + "height": 839 + }, + "deviceScaleFactor": 2.625, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 7a landscape": { + "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 915, + "height": 412 + }, + "viewport": { + "width": 863, + "height": 360 + }, + "deviceScaleFactor": 2.625, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 8": { + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 412, + "height": 915 + }, + "viewport": { + "width": 412, + "height": 839 + }, + "deviceScaleFactor": 2.625, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 8 landscape": { + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 915, + "height": 412 + }, + "viewport": { + "width": 863, + "height": 360 + }, + "deviceScaleFactor": 2.625, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 8 Pro": { + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 448, + "height": 997 + }, + "viewport": { + "width": 448, + "height": 921 + }, + "deviceScaleFactor": 3, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 8 Pro landscape": { + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 997, + "height": 448 + }, + "viewport": { + "width": 945, + "height": 396 + }, + "deviceScaleFactor": 3, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 8a": { + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 412, + "height": 915 + }, + "viewport": { + "width": 412, + "height": 839 + }, + "deviceScaleFactor": 2.625, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 8a landscape": { + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -1649,8 +1889,188 @@ "hasTouch": true, "defaultBrowserType": "chromium" }, + "Pixel 9": { + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 360, + "height": 808 + }, + "viewport": { + "width": 360, + "height": 732 + }, + "deviceScaleFactor": 3, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 9 landscape": { + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 808, + "height": 360 + }, + "viewport": { + "width": 756, + "height": 308 + }, + "deviceScaleFactor": 3, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 9 Pro": { + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 427, + "height": 952 + }, + "viewport": { + "width": 427, + "height": 876 + }, + "deviceScaleFactor": 3, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 9 Pro landscape": { + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 952, + "height": 427 + }, + "viewport": { + "width": 900, + "height": 375 + }, + "deviceScaleFactor": 3, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 9 Pro XL": { + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 448, + "height": 997 + }, + "viewport": { + "width": 448, + "height": 921 + }, + "deviceScaleFactor": 3, + "isMobile": true, + "hasTouch": true, + "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/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 997, + "height": 448 + }, + "viewport": { + "width": 945, + "height": 396 + }, + "deviceScaleFactor": 3, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 10": { + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 360, + "height": 808 + }, + "viewport": { + "width": 360, + "height": 732 + }, + "deviceScaleFactor": 3, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 10 landscape": { + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 808, + "height": 360 + }, + "viewport": { + "width": 756, + "height": 308 + }, + "deviceScaleFactor": 3, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 10 Pro": { + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 427, + "height": 952 + }, + "viewport": { + "width": 427, + "height": 876 + }, + "deviceScaleFactor": 3, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 10 Pro landscape": { + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 952, + "height": 427 + }, + "viewport": { + "width": 900, + "height": 375 + }, + "deviceScaleFactor": 3, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, + "Pixel 10 Pro XL": { + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 448, + "height": 997 + }, + "viewport": { + "width": 448, + "height": 921 + }, + "deviceScaleFactor": 3, + "isMobile": true, + "hasTouch": true, + "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/149.0.7827.3 Mobile Safari/537.36", + "screen": { + "width": 997, + "height": 448 + }, + "viewport": { + "width": 945, + "height": 396 + }, + "deviceScaleFactor": 3, + "isMobile": true, + "hasTouch": true, + "defaultBrowserType": "chromium" + }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1661,7 +2081,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1672,7 +2092,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Safari/537.36", "screen": { "width": 1792, "height": 1120 @@ -1687,7 +2107,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Safari/537.36 Edg/149.0.7827.3", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Safari/537.36 Edg/149.0.7827.14", "screen": { "width": 1792, "height": 1120 @@ -1732,7 +2152,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Safari/537.36", "screen": { "width": 1920, "height": 1080 @@ -1747,7 +2167,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.3 Safari/537.36 Edg/149.0.7827.3", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.14 Safari/537.36 Edg/149.0.7827.14", "screen": { "width": 1920, "height": 1080 diff --git a/packages/playwright-core/src/server/dispatchers/dispatcher.ts b/packages/playwright-core/src/server/dispatchers/dispatcher.ts index 7e91fc1e7f7e9..df40e458af6f5 100644 --- a/packages/playwright-core/src/server/dispatchers/dispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/dispatcher.ts @@ -144,11 +144,6 @@ export class Dispatcher {}); - } this._onDispose(); this._disposed = true; eventsHelper.removeEventListeners(this._eventListeners); diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index 75e24cc3f8a75..b392e18d49f5e 100644 --- a/packages/playwright-core/src/server/fetch.ts +++ b/packages/playwright-core/src/server/fetch.ts @@ -31,6 +31,7 @@ import { getUserAgent } from './userAgent'; import { BrowserContext, verifyClientCertificates } from './browserContext'; import { Cookie, CookieStore, domainMatches, parseRawCookie } from './cookieStore'; import { MultipartFormData } from './formData'; +import { TargetClosedError } from './errors'; import { SdkObject } from './instrumentation'; import { isAbortError } from './progress'; import { getMatchingTLSOptionsForOrigin, rewriteOpenSSLErrorIfNeeded } from './socksClientCertificatesInterceptor'; @@ -109,6 +110,7 @@ export abstract class APIRequestContext extends SdkObject { readonly fetchLog: Map = new Map(); protected static allInstances: Set = new Set(); _closeReason: string | undefined; + private _disposed = false; static findResponseBody(guid: string): Buffer | undefined { for (const request of APIRequestContext.allInstances) { @@ -147,6 +149,7 @@ export abstract class APIRequestContext extends SdkObject { abstract cookies(progress: Progress, url: URL): Promise; protected _disposeImpl() { + this._disposed = true; APIRequestContext.allInstances.delete(this); this.fetchResponses.clear(); this.fetchLog.clear(); @@ -328,6 +331,9 @@ export abstract class APIRequestContext extends SdkObject { }; this.emit(APIRequestContext.Events.Request, requestEvent); + if (this._disposed) + throw new TargetClosedError(this._closeReason || 'Request context disposed.'); + let destroyRequest: (() => void) | undefined; progress.setAllowConcurrentOrNestedRaces(true); const resultPromise = new Promise((fulfill, reject) => { @@ -530,7 +536,7 @@ export abstract class APIRequestContext extends SdkObject { listeners.push( eventsHelper.addEventListener(this, APIRequestContext.Events.Dispose, () => { - reject(new Error('Request context disposed.')); + reject(new TargetClosedError(this._closeReason || 'Request context disposed.')); request.destroy(); }) ); diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 4afb08a9d07d0..3ec233045373b 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -1447,7 +1447,17 @@ export class Frame extends SdkObject { } async waitForTimeout(progress: Progress, timeout: number) { - return progress.wait(timeout); + let timer: NodeJS.Timeout; + const promise = new Promise(f => timer = setTimeout(f, timeout)); + try { + // Make sure we react to page close or frame detach. + await progress.race(LongStandingScope.raceMultiple([ + this._page.openScope, + this._detachedScope, + ], promise)); + } finally { + clearTimeout(timer!); + } } async expect(progress: Progress, selector: string | undefined, options: FrameExpectParams): Promise { diff --git a/packages/playwright-core/src/server/network.ts b/packages/playwright-core/src/server/network.ts index eafa7f097be68..ce78b508798aa 100644 --- a/packages/playwright-core/src/server/network.ts +++ b/packages/playwright-core/src/server/network.ts @@ -214,12 +214,19 @@ export class Request extends SdkObject { this._isFavicon = url.endsWith('/favicon.ico') || !!redirectedFrom?._isFavicon; } + async raceWithPageClosure(progress: Progress, promise: Promise): Promise { + const scope = this._serviceWorker?.openScope ?? this._frame?._page.openScope; + if (scope) + return await progress.race(scope.race(promise)); + return await progress.race(promise); + } + async rawRequestHeaders(progress: Progress): Promise { - return await progress.race(this._rawRequestHeaders()); + return await this.raceWithPageClosure(progress, this._rawRequestHeaders()); } async response(progress: Progress): Promise { - return await progress.race(this._waitForResponse()); + return await this.raceWithPageClosure(progress, this._waitForResponse()); } _setFailureText(failureText: string) { @@ -539,27 +546,27 @@ export class Response extends SdkObject { } async body(progress: Progress): Promise { - return await progress.race(this.internalBody()); + return await this._request.raceWithPageClosure(progress, this.internalBody()); } async securityDetails(progress: Progress): Promise { - return await progress.race(this.internalSecurityDetails()); + return await this._request.raceWithPageClosure(progress, this.internalSecurityDetails()); } async serverAddr(progress: Progress): Promise { - return (await progress.race(this._serverAddrPromise)) || null; + return (await this._request.raceWithPageClosure(progress, this._serverAddrPromise)) || null; } async rawResponseHeaders(progress: Progress): Promise { - return await progress.race(this._rawResponseHeadersPromise); + return await this._request.raceWithPageClosure(progress, this._rawResponseHeadersPromise); } async httpVersion(progress: Progress): Promise { - return await progress.race(this._httpVersion()); + return await this._request.raceWithPageClosure(progress, this._httpVersion()); } async sizes(progress: Progress): Promise { - return await progress.race(this._sizes()); + return await this._request.raceWithPageClosure(progress, this._sizes()); } _serverAddrFinished(addr?: RemoteAddr) { diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index e67f56ac505c3..43ad5d0df1458 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -301,7 +301,7 @@ export class Recorder extends EventEmitter implements Instrume await this.setMode('inspecting'); return await selectorPromise; }; - return await progress.race(doPickLocator()); + return await progress.race(page.openScope.race(doPickLocator())); } finally { eventsHelper.removeEventListeners(listeners); this._pickLocatorPage = undefined; diff --git a/packages/playwright-core/src/tools/backend/devtools.ts b/packages/playwright-core/src/tools/backend/devtools.ts index 79e0fb9afc024..300793b84d903 100644 --- a/packages/playwright-core/src/tools/backend/devtools.ts +++ b/packages/playwright-core/src/tools/backend/devtools.ts @@ -22,7 +22,7 @@ import { libPath } from '../../package'; import { defineTabTool, defineTool } from './tool'; import { elementSchema, optionalElementSchema } from './snapshot'; -import type { SubmittedAnnotationFrame } from '@dashboard/dashboardChannel'; +import type { AnnotateResult } from '../dashboard/dashboardController'; const resume = defineTool({ capability: 'devtools', @@ -157,11 +157,12 @@ const annotate = defineTabTool({ response.addTextResult('No annotations were submitted.'); return; } - const { frames, feedback } = JSON.parse(text) as { frames: SubmittedAnnotationFrame[]; feedback: string }; - if (!frames || frames.length === 0) { + const result = JSON.parse(text) as AnnotateResult; + if (result.type !== 'submitted' || result.frames.length === 0) { response.addTextResult('No annotations were submitted.'); return; } + const { frames, feedback } = result; const date = new Date(); if (feedback) response.addTextResult(feedback); diff --git a/packages/playwright-core/src/tools/dashboard/dashboardApp.ts b/packages/playwright-core/src/tools/dashboard/dashboardApp.ts index 4c969c00da049..cf338ec2af472 100644 --- a/packages/playwright-core/src/tools/dashboard/dashboardApp.ts +++ b/packages/playwright-core/src/tools/dashboard/dashboardApp.ts @@ -32,7 +32,7 @@ import { RegistrySessionProvider } from './registrySessionProvider'; import { IdentitySessionProvider } from './identitySessionProvider'; import type * as api from '../../..'; -import type { SubmittedAnnotationFrame } from '@dashboard/dashboardChannel'; +import type { AnnotateResult } from './dashboardController'; import type { SessionProvider } from './sessionProvider'; // HMR: build-time flag — `true` in watch builds, `false` in release. esbuild @@ -43,7 +43,7 @@ declare const __PW_HMR__: boolean; type DashboardServer = { url: string; reveal: (options: DashboardOptions) => Promise; - triggerAnnotate: (socket: net.Socket) => Promise; + triggerAnnotate: (signal: AbortSignal) => Promise; close: () => Promise; }; @@ -53,18 +53,6 @@ async function startDashboardServer(provider: SessionProvider, options: Dashboar const connections = new Set(); let connectionLanded = new ManualPromise(); - const waitingSockets = new Set(); - - const submitAnnotation = (frames: SubmittedAnnotationFrame[], feedback: string) => { - if (waitingSockets.size === 0) - return; - const payload = JSON.stringify({ frames, feedback }); - for (const socket of waitingSockets) { - socket.write(payload); - socket.end(); - } - waitingSockets.clear(); - }; httpServer.createWebSocket(() => { let connection: DashboardConnection; @@ -75,7 +63,7 @@ async function startDashboardServer(provider: SessionProvider, options: Dashboar connectionLanded = new ManualPromise(); }, () => { connectionLanded.resolve(); - }, submitAnnotation); + }); connections.add(connection); return connection; }); @@ -108,24 +96,15 @@ async function startDashboardServer(provider: SessionProvider, options: Dashboar })); }; - const triggerAnnotate = async (socket: net.Socket) => { - waitingSockets.add(socket); - const cleanup = () => { - if (!waitingSockets.delete(socket)) - return; - if (waitingSockets.size === 0) { - for (const connection of connections) - connection.emitCancelAnnotate(); - } - }; - socket.on('close', cleanup); - socket.on('error', cleanup); - + const triggerAnnotate = async (cancellation: AbortSignal): Promise => { await connectionLanded; - if (waitingSockets.size === 0) - return; - for (const connection of connections) - connection.emitAnnotate(); + if (cancellation.aborted || connections.size === 0) + return { type: 'cancelled' }; + // Multiple dashboard connections is theoretical today (one UI per daemon), server mode does not support annotate. + // If two ever land, the first to submit wins but the losers stay in + // annotation mode until their UI reloads — revisit if that becomes a real + // scenario. + return await Promise.race([...connections].map(c => c.emitAnnotate({ signal: cancellation }))); }; const close = () => httpServer.stop(); @@ -364,10 +343,14 @@ async function startApp(server: net.Server, options: DashboardOptions) { } const { page, server: dashboard } = await statePromise; if (parsed.annotate) { + const cancellation = new AbortController(); + socket.on('close', () => cancellation.abort()); + socket.on('error', () => cancellation.abort()); try { await page?.bringToFront(); await dashboard.reveal(parsed); - await dashboard.triggerAnnotate(socket); + const result = await dashboard.triggerAnnotate(cancellation.signal); + socket.end(JSON.stringify(result)); } catch (e) { socket.end(e); } diff --git a/packages/playwright-core/src/tools/dashboard/dashboardController.ts b/packages/playwright-core/src/tools/dashboard/dashboardController.ts index dfa9bc2216f81..c87e35e720049 100644 --- a/packages/playwright-core/src/tools/dashboard/dashboardController.ts +++ b/packages/playwright-core/src/tools/dashboard/dashboardController.ts @@ -32,6 +32,10 @@ import type { SubmittedAnnotationFrame, Tab } from '@dashboard/dashboardChannel' import type { BrowserDescriptor } from '../../serverRegistry'; import type { SessionProvider } from './sessionProvider'; +export type AnnotateResult = + | { type: 'submitted', frames: SubmittedAnnotationFrame[], feedback: string } + | { type: 'cancelled' }; + export class DashboardConnection implements Transport { sendEvent?: (method: string, params: any) => void; close?: () => void; @@ -40,20 +44,18 @@ export class DashboardConnection implements Transport { private _attachedPage: AttachedPage | undefined; private _onclose: () => void; private _onconnected?: () => void; - private _onAnnotationSubmit?: (frames: SubmittedAnnotationFrame[], feedback: string) => void; private _pushTabsScheduled = false; private _visible = true; private _pendingReveal: { sessionName?: string; workspaceDir?: string; pageId?: string; done: ManualPromise } | undefined; - private _annotateWaitingForAttach = false; + private _pendingAnnotate: { resolve: (result: AnnotateResult) => void; dispose: () => void } | undefined; _recordingDir: string; _streams = new Map(); - constructor(provider: SessionProvider, onclose: () => void, onconnected?: () => void, onAnnotationSubmit?: (frames: SubmittedAnnotationFrame[], feedback: string) => void) { + constructor(provider: SessionProvider, onclose: () => void, onconnected?: () => void) { this._provider = provider; this._onclose = onclose; this._onconnected = onconnected; - this._onAnnotationSubmit = onAnnotationSubmit; this._recordingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'playwright-recordings-')); } @@ -84,6 +86,7 @@ export class DashboardConnection implements Transport { // Reject any in-flight reveal so callers awaiting it don't hang. this._pendingReveal?.done.reject(new Error('Dashboard connection closed')); this._pendingReveal = undefined; + this._resolvePendingAnnotate({ type: 'cancelled' }); for (const stream of this._streams.values()) { void stream.handle.close() .catch(() => {}) @@ -193,11 +196,11 @@ export class DashboardConnection implements Transport { } async submitAnnotation(params: { frames: SubmittedAnnotationFrame[]; feedback: string }) { - this._onAnnotationSubmit?.(params.frames, params.feedback); + this._resolvePendingAnnotate({ type: 'submitted', frames: params.frames, feedback: params.feedback }); } async cancelAnnotation() { - this._onAnnotationSubmit?.([], ''); + this._resolvePendingAnnotate({ type: 'cancelled' }); } async reveal(params: { path: string }) { @@ -245,18 +248,43 @@ export class DashboardConnection implements Transport { this.sendEvent?.('frame', { data, viewportWidth, viewportHeight }); } - emitAnnotate() { + emitAnnotate({ signal }: { signal: AbortSignal }): Promise { + return new Promise(resolve => { + if (signal.aborted) { + resolve({ type: 'cancelled' }); + return; + } + // Latest emitAnnotate supersedes any in-flight one on the same connection. + this._resolvePendingAnnotate({ type: 'cancelled' }); + const onAbort = () => { + if (this._pendingAnnotate !== pending) + return; + this._pendingAnnotate = undefined; + pending.dispose(); + this.sendEvent?.('cancelAnnotate', {}); + resolve({ type: 'cancelled' }); + }; + const pending: NonNullable = { + resolve, + dispose: () => signal.removeEventListener('abort', onAbort), + }; + this._pendingAnnotate = pending; + signal.addEventListener('abort', onAbort); + this._tryFireAnnotate(); + }); + } + + private _tryFireAnnotate() { // Defer until a page is attached so the client can fetch a screenshot. - if (!this._attachedPage) { - this._annotateWaitingForAttach = true; + if (!this._pendingAnnotate || !this._attachedPage) return; - } this.sendEvent?.('annotate', {}); } - emitCancelAnnotate() { - this._annotateWaitingForAttach = false; - this.sendEvent?.('cancelAnnotate', {}); + private _resolvePendingAnnotate(result: AnnotateResult) { + this._pendingAnnotate?.resolve(result); + this._pendingAnnotate?.dispose(); + this._pendingAnnotate = undefined; } artifactsDirFor(context: api.BrowserContext): string { @@ -326,10 +354,8 @@ export class DashboardConnection implements Transport { attached.dispose(); throw e; } - if (this._annotateWaitingForAttach && this._attachedPage === attached) { - this._annotateWaitingForAttach = false; - this.sendEvent?.('annotate', {}); - } + if (this._attachedPage === attached) + this._tryFireAnnotate(); } _handleAttachedPageClose(context: api.BrowserContext) { diff --git a/packages/playwright-core/types/protocol.d.ts b/packages/playwright-core/types/protocol.d.ts index f2be2d97a3c46..6f3af1513f859 100644 --- a/packages/playwright-core/types/protocol.d.ts +++ b/packages/playwright-core/types/protocol.d.ts @@ -11578,7 +11578,7 @@ details; this boolean is true if that value is populated. /** * A fetch result for a device bound session creation or refresh. */ - export type DeviceBoundSessionFetchResult = "Success"|"KeyError"|"SigningError"|"ServerRequestedTermination"|"InvalidSessionId"|"InvalidChallenge"|"TooManyChallenges"|"InvalidFetcherUrl"|"InvalidRefreshUrl"|"TransientHttpError"|"ScopeOriginSameSiteMismatch"|"RefreshUrlSameSiteMismatch"|"MismatchedSessionId"|"MissingScope"|"NoCredentials"|"SubdomainRegistrationWellKnownUnavailable"|"SubdomainRegistrationUnauthorized"|"SubdomainRegistrationWellKnownMalformed"|"SessionProviderWellKnownUnavailable"|"RelyingPartyWellKnownUnavailable"|"FederatedKeyThumbprintMismatch"|"InvalidFederatedSessionUrl"|"InvalidFederatedKey"|"TooManyRelyingOriginLabels"|"BoundCookieSetForbidden"|"NetError"|"ProxyError"|"EmptySessionConfig"|"InvalidCredentialsConfig"|"InvalidCredentialsType"|"InvalidCredentialsEmptyName"|"InvalidCredentialsCookie"|"PersistentHttpError"|"RegistrationAttemptedChallenge"|"InvalidScopeOrigin"|"ScopeOriginContainsPath"|"RefreshInitiatorNotString"|"RefreshInitiatorInvalidHostPattern"|"InvalidScopeSpecification"|"MissingScopeSpecificationType"|"EmptyScopeSpecificationDomain"|"EmptyScopeSpecificationPath"|"InvalidScopeSpecificationType"|"InvalidScopeIncludeSite"|"MissingScopeIncludeSite"|"FederatedNotAuthorizedByProvider"|"FederatedNotAuthorizedByRelyingParty"|"SessionProviderWellKnownMalformed"|"SessionProviderWellKnownHasProviderOrigin"|"RelyingPartyWellKnownMalformed"|"RelyingPartyWellKnownHasRelyingOrigins"|"InvalidFederatedSessionProviderSessionMissing"|"InvalidFederatedSessionWrongProviderOrigin"|"InvalidCredentialsCookieCreationTime"|"InvalidCredentialsCookieName"|"InvalidCredentialsCookieParsing"|"InvalidCredentialsCookieUnpermittedAttribute"|"InvalidCredentialsCookieInvalidDomain"|"InvalidCredentialsCookiePrefix"|"InvalidScopeRulePath"|"InvalidScopeRuleHostPattern"|"ScopeRuleOriginScopedHostPatternMismatch"|"ScopeRuleSiteScopedHostPatternMismatch"|"SigningQuotaExceeded"|"InvalidConfigJson"|"InvalidFederatedSessionProviderFailedToRestoreKey"|"FailedToUnwrapKey"|"SessionDeletedDuringRefresh"; + export type DeviceBoundSessionFetchResult = "Success"|"KeyError"|"SigningError"|"TransientSigningError"|"ServerRequestedTermination"|"InvalidSessionId"|"InvalidChallenge"|"TooManyChallenges"|"InvalidFetcherUrl"|"InvalidRefreshUrl"|"TransientHttpError"|"ScopeOriginSameSiteMismatch"|"RefreshUrlSameSiteMismatch"|"MismatchedSessionId"|"MissingScope"|"NoCredentials"|"SubdomainRegistrationWellKnownUnavailable"|"SubdomainRegistrationUnauthorized"|"SubdomainRegistrationWellKnownMalformed"|"SessionProviderWellKnownUnavailable"|"RelyingPartyWellKnownUnavailable"|"FederatedKeyThumbprintMismatch"|"InvalidFederatedSessionUrl"|"InvalidFederatedKey"|"TooManyRelyingOriginLabels"|"BoundCookieSetForbidden"|"NetError"|"ProxyError"|"EmptySessionConfig"|"InvalidCredentialsConfig"|"InvalidCredentialsType"|"InvalidCredentialsEmptyName"|"InvalidCredentialsCookie"|"PersistentHttpError"|"RegistrationAttemptedChallenge"|"InvalidScopeOrigin"|"ScopeOriginContainsPath"|"RefreshInitiatorNotString"|"RefreshInitiatorInvalidHostPattern"|"InvalidScopeSpecification"|"MissingScopeSpecificationType"|"EmptyScopeSpecificationDomain"|"EmptyScopeSpecificationPath"|"InvalidScopeSpecificationType"|"InvalidScopeIncludeSite"|"MissingScopeIncludeSite"|"FederatedNotAuthorizedByProvider"|"FederatedNotAuthorizedByRelyingParty"|"SessionProviderWellKnownMalformed"|"SessionProviderWellKnownHasProviderOrigin"|"RelyingPartyWellKnownMalformed"|"RelyingPartyWellKnownHasRelyingOrigins"|"InvalidFederatedSessionProviderSessionMissing"|"InvalidFederatedSessionWrongProviderOrigin"|"InvalidCredentialsCookieCreationTime"|"InvalidCredentialsCookieName"|"InvalidCredentialsCookieParsing"|"InvalidCredentialsCookieUnpermittedAttribute"|"InvalidCredentialsCookieInvalidDomain"|"InvalidCredentialsCookiePrefix"|"InvalidScopeRulePath"|"InvalidScopeRuleHostPattern"|"ScopeRuleOriginScopedHostPatternMismatch"|"ScopeRuleSiteScopedHostPatternMismatch"|"SigningQuotaExceeded"|"InvalidConfigJson"|"InvalidFederatedSessionProviderFailedToRestoreKey"|"FailedToUnwrapKey"|"SessionDeletedDuringRefresh"; /** * Details about a failed device bound session network request. */ @@ -11628,7 +11628,7 @@ one. /** * The result of a refresh. */ - refreshResult: "Refreshed"|"RefreshedAsWaiter"|"InitializedService"|"Unreachable"|"ServerError"|"RefreshQuotaExceeded"|"FatalError"|"SigningQuotaExceeded"; + refreshResult: "Refreshed"|"InitializedService"|"Unreachable"|"ServerError"|"RefreshQuotaExceeded"|"FatalError"|"SigningQuotaExceeded"|"RefreshedAsWaiter"|"TransientSigningError"; /** * If there was a fetch attempt, the result of that. */ diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 9697f25c49cd1..05fe4dc968db1 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -24759,8 +24759,36 @@ type Devices = { "Pixel 4a (5G) landscape": DeviceDescriptor; "Pixel 5": DeviceDescriptor; "Pixel 5 landscape": DeviceDescriptor; + "Pixel 6": DeviceDescriptor; + "Pixel 6 landscape": DeviceDescriptor; + "Pixel 6 Pro": DeviceDescriptor; + "Pixel 6 Pro landscape": DeviceDescriptor; + "Pixel 6a": DeviceDescriptor; + "Pixel 6a landscape": DeviceDescriptor; "Pixel 7": DeviceDescriptor; "Pixel 7 landscape": DeviceDescriptor; + "Pixel 7 Pro": DeviceDescriptor; + "Pixel 7 Pro landscape": DeviceDescriptor; + "Pixel 7a": DeviceDescriptor; + "Pixel 7a landscape": DeviceDescriptor; + "Pixel 8": DeviceDescriptor; + "Pixel 8 landscape": DeviceDescriptor; + "Pixel 8 Pro": DeviceDescriptor; + "Pixel 8 Pro landscape": DeviceDescriptor; + "Pixel 8a": DeviceDescriptor; + "Pixel 8a landscape": DeviceDescriptor; + "Pixel 9": DeviceDescriptor; + "Pixel 9 landscape": DeviceDescriptor; + "Pixel 9 Pro": DeviceDescriptor; + "Pixel 9 Pro landscape": DeviceDescriptor; + "Pixel 9 Pro XL": DeviceDescriptor; + "Pixel 9 Pro XL landscape": DeviceDescriptor; + "Pixel 10": DeviceDescriptor; + "Pixel 10 landscape": DeviceDescriptor; + "Pixel 10 Pro": DeviceDescriptor; + "Pixel 10 Pro landscape": DeviceDescriptor; + "Pixel 10 Pro XL": DeviceDescriptor; + "Pixel 10 Pro XL landscape": DeviceDescriptor; "Moto G4": DeviceDescriptor; "Moto G4 landscape": DeviceDescriptor; "Desktop Chrome HiDPI": DeviceDescriptor; diff --git a/packages/utils/network.ts b/packages/utils/network.ts index de32b45d7f840..5f721242eb277 100644 --- a/packages/utils/network.ts +++ b/packages/utils/network.ts @@ -59,6 +59,10 @@ export function httpRequest(params: HTTPRequestParams, onResponse: (r: http.Inco const parsedProxyURL = normalizeProxyURL(proxyURL); if (params.url.startsWith('http:')) { options.path = url.toString(); + const headers = options.headers || {}; + if (!Object.keys(headers).some(header => header.toLowerCase() === 'host')) + headers.host = url.host; + options.headers = headers; url = parsedProxyURL; } else { options.agent = new HttpsProxyAgent(parsedProxyURL); diff --git a/tests/config/proxy.ts b/tests/config/proxy.ts index 697f39f956dff..1e534268dfc04 100644 --- a/tests/config/proxy.ts +++ b/tests/config/proxy.ts @@ -38,6 +38,7 @@ export class TestProxy { connectHosts: string[] = []; requestUrls: string[] = []; + requestHosts: string[] = []; wsUrls: string[] = []; private readonly _server: ProxyServer; @@ -69,6 +70,7 @@ export class TestProxy { forwardTo(port: number, options?: { allowConnectRequests?: boolean, removePrefix?: string, preserveHostname?: boolean }) { this._prependHandler('request', (req: IncomingMessage) => { this.requestUrls.push(req.url); + this.requestHosts.push(req.headers.host); const url = new URL(req.url, `http://${req.headers.host}`); if (options?.preserveHostname) url.port = '' + port; @@ -116,6 +118,7 @@ export class TestProxy { reset() { this.connectHosts = []; this.requestUrls = []; + this.requestHosts = []; for (const { event, handler } of this._handlers) this._server.removeListener(event, handler); this._handlers = []; diff --git a/tests/library/browsercontext-fetch.spec.ts b/tests/library/browsercontext-fetch.spec.ts index 864f34fa51ac8..871e435b6df70 100644 --- a/tests/library/browsercontext-fetch.spec.ts +++ b/tests/library/browsercontext-fetch.spec.ts @@ -1256,7 +1256,7 @@ it('should abort requests when browser context closes', async ({ contextFactory, server.waitForRequest('/empty.html').then(() => context.close()) ]); expect(error instanceof Error).toBeTruthy(); - expect(error.message).toContain(kTargetClosedErrorMessage); + expect(error.message).toMatch(/Request context disposed|Target page, context or browser has been closed/); await connectionClosed; }); diff --git a/tests/library/chromium/connect-over-cdp.spec.ts b/tests/library/chromium/connect-over-cdp.spec.ts index c395082b25617..52397b0d02030 100644 --- a/tests/library/chromium/connect-over-cdp.spec.ts +++ b/tests/library/chromium/connect-over-cdp.spec.ts @@ -486,6 +486,25 @@ test('should use env proxy with connectOverCDP discovery request', async ({ brow } }); +test('should send target Host header when using env HTTP proxy with connectOverCDP', async ({ browserType, server, proxyServer, mode }) => { + test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/40811' }); + test.skip(mode !== 'default'); // Out of process transport does not allow us to set env vars dynamically. + proxyServer.forwardTo(server.PORT); + + const oldValue = process.env.HTTP_PROXY; + try { + process.env.HTTP_PROXY = proxyServer.URL; + const error = await browserType.connectOverCDP(server.PREFIX).catch(e => e); + expect(error.message).toContain(`Unexpected status 404 when connecting to ${server.PREFIX}/json/version/`); + expect(proxyServer.requestHosts).toEqual([new URL(server.PREFIX).host]); + } finally { + if (oldValue === undefined) + delete process.env.HTTP_PROXY; + else + process.env.HTTP_PROXY = oldValue; + } +}); + test('should be able to connect via localhost', async ({ browserType }, testInfo) => { const port = 9339 + testInfo.workerIndex; const browserServer = await browserType.launch({ diff --git a/tests/library/global-fetch.spec.ts b/tests/library/global-fetch.spec.ts index c307d3d83cf1a..402f6d7368d92 100644 --- a/tests/library/global-fetch.spec.ts +++ b/tests/library/global-fetch.spec.ts @@ -18,7 +18,6 @@ import os from 'os'; import * as util from 'util'; import { getPlaywrightVersion } from '../../packages/playwright-core/lib/coreBundle'; import { expect, playwrightTest as base } from '../config/browserTest'; -import { kTargetClosedErrorMessage } from '../config/errors'; const it = base.extend({ context: async ({}, use) => { @@ -324,7 +323,7 @@ it('should abort redirected requests when context is disposed', async ({ playwri server.waitForRequest('/test').then(() => request.dispose()) ]); expect(result instanceof Error).toBeTruthy(); - expect(result.message).toContain(kTargetClosedErrorMessage); + expect(result.message).toMatch(/Request context disposed|Target page, context or browser has been closed/); await connectionClosed; await request.dispose(); }); diff --git a/tests/playwright-test/playwright.fetch.spec.ts b/tests/playwright-test/playwright.fetch.spec.ts index 3b2e32af153db..8fe962218fc78 100644 --- a/tests/playwright-test/playwright.fetch.spec.ts +++ b/tests/playwright-test/playwright.fetch.spec.ts @@ -91,6 +91,8 @@ test('should hint unrouteAll if failed in the handler', async ({ runInlineTest, await page.route('**/empty.html', async route => { await route.continue(); await closedPromise; + // Wait for the page to actually close before calling route.fetch(). + await new Promise(f => setTimeout(f, 500)); await route.fetch(); }); await page.goto('${server.EMPTY_PAGE}'); @@ -99,7 +101,7 @@ test('should hint unrouteAll if failed in the handler', async ({ runInlineTest, test('second test', async ({ page }) => { // Wait enough for the worker to be killed. - await new Promise(f => setTimeout(f, 1000)); + await new Promise(f => setTimeout(f, 2000)); }); `, }, { workers: 1 }); diff --git a/tests/stress/playwright.config.ts b/tests/stress/playwright.config.ts index dcf5a0813632c..4c89a52544aba 100644 --- a/tests/stress/playwright.config.ts +++ b/tests/stress/playwright.config.ts @@ -17,6 +17,7 @@ import { defineConfig } from '@playwright/test'; process.env.PWTEST_UNDER_TEST = '1'; +const channel = process.env.PWTEST_CHANNEL as any; export default defineConfig({ forbidOnly: !!process.env.CI, @@ -25,7 +26,8 @@ export default defineConfig({ { name: 'chromium', use: { - browserName: 'chromium' + browserName: 'chromium', + channel, }, }, @@ -33,13 +35,15 @@ export default defineConfig({ name: 'firefox', use: { browserName: 'firefox', + channel, }, }, { name: 'webkit', use: { - browserName: 'webkit' + browserName: 'webkit', + channel, }, }, ]