diff --git a/README.md b/README.md
index 77900978dd665..1f8bc897fadbc 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# 🎠Playwright
-[](https://www.npmjs.com/package/playwright) [](https://www.chromium.org/Home) [](https://www.mozilla.org/en-US/firefox/new/) [](https://webkit.org/) [](https://aka.ms/playwright/discord)
+[](https://www.npmjs.com/package/playwright) [](https://www.chromium.org/Home) [](https://www.mozilla.org/en-US/firefox/new/) [](https://webkit.org/) [](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,
},
},
]