diff --git a/README.md b/README.md index f94183a0c9cd9..324f49bd4234e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🎭 Playwright -[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-150.0.7871.46-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-151.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-26.5-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-informational)](https://aka.ms/playwright/discord) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-151.0.7922.10-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-151.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-26.5-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-informational)](https://aka.ms/playwright/discord) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) @@ -296,7 +296,7 @@ The [Playwright VS Code extension](https://marketplace.visualstudio.com/items?it | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium1 150.0.7871.46 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium1 151.0.7922.10 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 26.5 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 151.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/packages/isomorphic/deviceDescriptorsSource.json b/packages/isomorphic/deviceDescriptorsSource.json index 7af8c4d97220e..098f5cb0f29c0 100644 --- a/packages/isomorphic/deviceDescriptorsSource.json +++ b/packages/isomorphic/deviceDescriptorsSource.json @@ -110,7 +110,7 @@ "defaultBrowserType": "webkit" }, "Galaxy S5": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -121,7 +121,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -132,7 +132,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 360, "height": 740 @@ -143,7 +143,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 740, "height": 360 @@ -154,7 +154,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 320, "height": 658 @@ -165,7 +165,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+ landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 658, "height": 320 @@ -176,7 +176,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S24": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 360, "height": 780 @@ -187,7 +187,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S24 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 780, "height": 360 @@ -198,7 +198,7 @@ "defaultBrowserType": "chromium" }, "Galaxy A55": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 480, "height": 1040 @@ -209,7 +209,7 @@ "defaultBrowserType": "chromium" }, "Galaxy A55 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 1040, "height": 480 @@ -220,7 +220,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Safari/537.36", "viewport": { "width": 712, "height": 1138 @@ -231,7 +231,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Safari/537.36", "viewport": { "width": 1138, "height": 712 @@ -242,7 +242,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S9": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Safari/537.36", "viewport": { "width": 640, "height": 1024 @@ -253,7 +253,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S9 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Safari/537.36", "viewport": { "width": 1024, "height": 640 @@ -264,7 +264,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 928, "height": 1080 @@ -279,7 +279,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 1080, "height": 928 @@ -294,7 +294,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 6 Cover": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 484, "height": 1188 @@ -309,7 +309,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 6 Cover landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 1188, "height": 484 @@ -324,7 +324,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 984, "height": 1092 @@ -339,7 +339,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 1092, "height": 984 @@ -354,7 +354,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 7 Cover": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 360, "height": 840 @@ -369,7 +369,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Fold 7 Cover landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 840, "height": 360 @@ -384,7 +384,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 360, "height": 880 @@ -399,7 +399,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 880, "height": 360 @@ -414,7 +414,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 6 Cover": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 360, "height": 374 @@ -429,7 +429,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 6 Cover landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 374, "height": 360 @@ -444,7 +444,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 360, "height": 840 @@ -459,7 +459,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 840, "height": 360 @@ -474,7 +474,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 7 Cover": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 474, "height": 524 @@ -489,7 +489,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Z Flip 7 Cover landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 524, "height": 474 @@ -1748,7 +1748,7 @@ "defaultBrowserType": "webkit" }, "LG Optimus L70": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1759,7 +1759,7 @@ "defaultBrowserType": "chromium" }, "LG Optimus L70 landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1770,7 +1770,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1781,7 +1781,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1792,7 +1792,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1803,7 +1803,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1814,7 +1814,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Safari/537.36", "viewport": { "width": 800, "height": 1280 @@ -1825,7 +1825,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Safari/537.36", "viewport": { "width": 1280, "height": 800 @@ -1836,7 +1836,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1847,7 +1847,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1858,7 +1858,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1869,7 +1869,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1880,7 +1880,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1891,7 +1891,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1902,7 +1902,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1913,7 +1913,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1924,7 +1924,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1935,7 +1935,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1946,7 +1946,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Safari/537.36", "viewport": { "width": 600, "height": 960 @@ -1957,7 +1957,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Safari/537.36", "viewport": { "width": 960, "height": 600 @@ -2012,7 +2012,7 @@ "defaultBrowserType": "webkit" }, "Pixel 2": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 411, "height": 731 @@ -2023,7 +2023,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 731, "height": 411 @@ -2034,7 +2034,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 411, "height": 823 @@ -2045,7 +2045,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 823, "height": 411 @@ -2056,7 +2056,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 393, "height": 786 @@ -2067,7 +2067,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 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/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 786, "height": 393 @@ -2078,7 +2078,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 353, "height": 745 @@ -2089,7 +2089,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 745, "height": 353 @@ -2100,7 +2100,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -2115,7 +2115,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "height": 892, "width": 412 @@ -2130,7 +2130,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 393, "height": 851 @@ -2145,7 +2145,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 851, "height": 393 @@ -2160,7 +2160,7 @@ "defaultBrowserType": "chromium" }, "Pixel 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -2175,7 +2175,7 @@ "defaultBrowserType": "chromium" }, "Pixel 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -2190,7 +2190,7 @@ "defaultBrowserType": "chromium" }, "Pixel 6 Pro": { - "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -2205,7 +2205,7 @@ "defaultBrowserType": "chromium" }, "Pixel 6 Pro landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 892, "height": 412 @@ -2220,7 +2220,7 @@ "defaultBrowserType": "chromium" }, "Pixel 6a": { - "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -2235,7 +2235,7 @@ "defaultBrowserType": "chromium" }, "Pixel 6a landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 6a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -2250,7 +2250,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -2265,7 +2265,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -2280,7 +2280,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 Pro": { - "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -2295,7 +2295,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 Pro landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 892, "height": 412 @@ -2310,7 +2310,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7a": { - "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -2325,7 +2325,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7a landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -2340,7 +2340,7 @@ "defaultBrowserType": "chromium" }, "Pixel 8": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -2355,7 +2355,7 @@ "defaultBrowserType": "chromium" }, "Pixel 8 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -2370,7 +2370,7 @@ "defaultBrowserType": "chromium" }, "Pixel 8 Pro": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 448, "height": 997 @@ -2385,7 +2385,7 @@ "defaultBrowserType": "chromium" }, "Pixel 8 Pro landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 997, "height": 448 @@ -2400,7 +2400,7 @@ "defaultBrowserType": "chromium" }, "Pixel 8a": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -2415,7 +2415,7 @@ "defaultBrowserType": "chromium" }, "Pixel 8a landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 8a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -2430,7 +2430,7 @@ "defaultBrowserType": "chromium" }, "Pixel 9": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 360, "height": 808 @@ -2445,7 +2445,7 @@ "defaultBrowserType": "chromium" }, "Pixel 9 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 808, "height": 360 @@ -2460,7 +2460,7 @@ "defaultBrowserType": "chromium" }, "Pixel 9 Pro": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 427, "height": 952 @@ -2475,7 +2475,7 @@ "defaultBrowserType": "chromium" }, "Pixel 9 Pro landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 952, "height": 427 @@ -2490,7 +2490,7 @@ "defaultBrowserType": "chromium" }, "Pixel 9 Pro XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 448, "height": 997 @@ -2505,7 +2505,7 @@ "defaultBrowserType": "chromium" }, "Pixel 9 Pro XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 997, "height": 448 @@ -2520,7 +2520,7 @@ "defaultBrowserType": "chromium" }, "Pixel 10": { - "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 360, "height": 808 @@ -2535,7 +2535,7 @@ "defaultBrowserType": "chromium" }, "Pixel 10 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 808, "height": 360 @@ -2550,7 +2550,7 @@ "defaultBrowserType": "chromium" }, "Pixel 10 Pro": { - "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 427, "height": 952 @@ -2565,7 +2565,7 @@ "defaultBrowserType": "chromium" }, "Pixel 10 Pro landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 952, "height": 427 @@ -2580,7 +2580,7 @@ "defaultBrowserType": "chromium" }, "Pixel 10 Pro XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 448, "height": 997 @@ -2595,7 +2595,7 @@ "defaultBrowserType": "chromium" }, "Pixel 10 Pro XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "screen": { "width": 997, "height": 448 @@ -2610,7 +2610,7 @@ "defaultBrowserType": "chromium" }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -2621,7 +2621,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -2632,7 +2632,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Safari/537.36", "screen": { "width": 1792, "height": 1120 @@ -2647,7 +2647,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36 Edg/150.0.7871.46", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Safari/537.36 Edg/151.0.7922.10", "screen": { "width": 1792, "height": 1120 @@ -2692,7 +2692,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Safari/537.36", "screen": { "width": 1920, "height": 1080 @@ -2707,7 +2707,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.7871.46 Safari/537.36 Edg/150.0.7871.46", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.10 Safari/537.36 Edg/151.0.7922.10", "screen": { "width": 1920, "height": 1080 diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 87575d0c8996d..261420d8fe256 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,16 +3,16 @@ "browsers": [ { "name": "chromium", - "revision": "1231", + "revision": "1232", "installByDefault": true, - "browserVersion": "150.0.7871.46", + "browserVersion": "151.0.7922.10", "title": "Chrome for Testing" }, { "name": "chromium-headless-shell", - "revision": "1231", + "revision": "1232", "installByDefault": true, - "browserVersion": "150.0.7871.46", + "browserVersion": "151.0.7922.10", "title": "Chrome Headless Shell" }, { @@ -45,7 +45,7 @@ }, { "name": "webkit", - "revision": "2323", + "revision": "2325", "installByDefault": true, "revisionOverrides": { "mac14": "2251", diff --git a/packages/playwright-core/src/server/chromium/protocol.d.ts b/packages/playwright-core/src/server/chromium/protocol.d.ts index e3d5785deafe0..858642160b282 100644 --- a/packages/playwright-core/src/server/chromium/protocol.d.ts +++ b/packages/playwright-core/src/server/chromium/protocol.d.ts @@ -345,6 +345,28 @@ including nodes that are ignored for accessibility. * A domain for ad-related metrics and data. */ export namespace Ads { + /** + * Ad frame data. + */ + export interface AdFrameData { + /** + * The DevTools frame token. + */ + frameId: Page.FrameId; + /** + * The initial origin of the frame. To minimize the payload size, this is +only sent once per frame. + */ + initialOrigin?: string; + /** + * The network bytes of the frame. + */ + networkBytes: number; + /** + * The CPU time of the frame, in milliseconds. + */ + cpuTime: number; + } /** * Ad metrics for a page. */ @@ -376,6 +398,14 @@ duration of the page. * The total ad network bytes. */ totalAdNetworkBytes: number; + /** + * The list of ad frames that have been updated since the last event. + */ + updateAdFrames: AdFrameData[]; + /** + * The list of ad frame IDs that have been removed since the last event. + */ + removeAdFrames: Page.FrameId[]; } @@ -1035,7 +1065,7 @@ third_party/blink/public/mojom/devtools/inspector_issue.mojom. Should be updated alongside EmailVerificationRequestResult in third_party/blink/public/mojom/devtools/inspector_issue.mojom. */ - export type EmailVerificationRequestIssueReason = "InvalidEmail"|"DnsFetchFailed"|"DnsInvalidRecord"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"WellKnownMissingIssuanceEndpoint"|"WellKnownIssuanceEndpointCrossOrigin"|"WellKnownUnsupportedSigningAlgorithm"|"TokenHttpNotFound"|"TokenNoResponse"|"TokenInvalidResponse"|"TokenInvalidContentType"|"TokenMalformedSdJwt"|"TokenInvalidSdJwt"|"KeyBindingSigningFailed"|"RpOriginIsOpaque"|"WellKnownMissingAccountsEndpoint"|"UserLoggedOut"|"WellKnownAccountsEndpointCrossOrigin"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsInvalidContentType"|"AccountsEmptyList"|"EmailVerificationWellKnownHttpNotFound"|"EmailVerificationWellKnownNoResponse"|"EmailVerificationWellKnownInvalidResponse"|"EmailVerificationWellKnownInvalidContentType"|"JwksHttpNotFound"|"JwksInvalidResponse"|"TokenVerificationSdJwtUnsupportedHeaderAlg"|"TokenVerificationSdJwtMissingIss"|"TokenVerificationSdJwtMissingIat"|"TokenVerificationSdJwtMissingCnf"|"TokenVerificationSdJwtMissingEmail"|"TokenVerificationSdJwtInvalidIssuedAt"|"TokenVerificationSdJwtInvalidIssuer"|"TokenVerificationSdJwtJwksMissingKeys"|"TokenVerificationSdJwtSignatureFailed"|"TokenVerificationSdJwtInvalidEmailVerified"|"TokenVerificationSdJwtInvalidEmail"|"TokenVerificationSdJwtInvalidHolderKey"|"TokenVerificationKbInvalidTyp"|"TokenVerificationKbMissingAud"|"TokenVerificationKbMissingNonce"|"TokenVerificationKbMissingIat"|"TokenVerificationKbMissingSdHash"|"TokenVerificationKbInvalidIssuedAt"|"TokenVerificationKbInvalidAudience"|"TokenVerificationKbInvalidNonce"|"TokenVerificationKbInvalidSdHash"|"TokenVerificationKbMissingCnf"|"TokenVerificationKbSignatureFailed"; + export type EmailVerificationRequestIssueReason = "InvalidEmail"|"DnsFetchFailed"|"DnsInvalidRecord"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"WellKnownMissingIssuanceEndpoint"|"WellKnownIssuanceEndpointCrossOrigin"|"WellKnownUnsupportedSigningAlgorithm"|"TokenHttpNotFound"|"TokenNoResponse"|"TokenInvalidResponse"|"TokenInvalidContentType"|"TokenMalformedSdJwt"|"TokenInvalidSdJwt"|"KeyBindingSigningFailed"|"RpOriginIsOpaque"|"WellKnownMissingAccountsEndpoint"|"UserLoggedOut"|"WellKnownAccountsEndpointCrossOrigin"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsInvalidContentType"|"AccountsEmptyList"|"EmailVerificationWellKnownHttpNotFound"|"EmailVerificationWellKnownNoResponse"|"EmailVerificationWellKnownInvalidResponse"|"EmailVerificationWellKnownInvalidContentType"|"JwksHttpNotFound"|"JwksInvalidResponse"|"TokenVerificationSdJwtUnsupportedHeaderAlg"|"TokenVerificationSdJwtInvalidTyp"|"TokenVerificationSdJwtMissingIss"|"TokenVerificationSdJwtMissingIat"|"TokenVerificationSdJwtMissingCnf"|"TokenVerificationSdJwtMissingEmail"|"TokenVerificationSdJwtInvalidIssuedAt"|"TokenVerificationSdJwtInvalidIssuer"|"TokenVerificationSdJwtJwksMissingKeys"|"TokenVerificationSdJwtSignatureFailed"|"TokenVerificationSdJwtInvalidEmailVerified"|"TokenVerificationSdJwtInvalidEmail"|"TokenVerificationSdJwtInvalidHolderKey"|"TokenVerificationKbInvalidTyp"|"TokenVerificationKbMissingAud"|"TokenVerificationKbMissingNonce"|"TokenVerificationKbMissingIat"|"TokenVerificationKbMissingSdHash"|"TokenVerificationKbInvalidIssuedAt"|"TokenVerificationKbInvalidAudience"|"TokenVerificationKbInvalidNonce"|"TokenVerificationKbInvalidSdHash"|"TokenVerificationKbMissingCnf"|"TokenVerificationKbSignatureFailed"; /** * This issue tracks client hints related issues. It's used to deprecate old features, encourage the use of new ones, and provide general guidance. @@ -1128,7 +1158,7 @@ re-identify users. */ sourceCodeLocation?: SourceCodeLocation; } - export type PermissionElementIssueType = "InvalidType"|"FencedFrameDisallowed"|"CspFrameAncestorsMissing"|"PermissionsPolicyBlocked"|"PaddingRightUnsupported"|"PaddingBottomUnsupported"|"InsetBoxShadowUnsupported"|"RequestInProgress"|"UntrustedEvent"|"RegistrationFailed"|"TypeNotSupported"|"InvalidTypeActivation"|"SecurityChecksFailed"|"ActivationDisabled"|"GeolocationDeprecated"|"InvalidDisplayStyle"|"NonOpaqueColor"|"LowContrast"|"FontSizeTooSmall"|"FontSizeTooLarge"|"InvalidSizeValue"; + export type PermissionElementIssueType = "InvalidType"|"FencedFrameDisallowed"|"CspFrameAncestorsMissing"|"PermissionsPolicyBlocked"|"PaddingRightUnsupported"|"PaddingBottomUnsupported"|"InsetBoxShadowUnsupported"|"RequestInProgress"|"UntrustedEvent"|"RegistrationFailed"|"TypeNotSupported"|"InvalidTypeActivation"|"SecurityChecksFailed"|"ActivationDisabled"|"GeolocationDeprecated"|"InvalidDisplayStyle"|"NonOpaqueColor"|"LowContrast"|"FontSizeTooSmall"|"FontSizeTooLarge"|"InvalidSizeValue"|"NonSecureContext"|"MissingTransientUserActivation"; /** * This issue warns about improper usage of the element. */ @@ -2456,6 +2486,27 @@ inspector" rules), "regular" for regular stylesheets. */ specificity?: Specificity; } + /** + * Contribution of an individual simple selector to specificity. + */ + export interface SpecificityComponent { + /** + * The simple selector text that contributes to specificity. + */ + text: string; + /** + * The a component contribution. + */ + a: number; + /** + * The b component contribution. + */ + b: number; + /** + * The c component contribution. + */ + c: number; + } /** * Specificity: https://drafts.csswg.org/selectors/#specificity-rules @@ -2474,6 +2525,10 @@ pseudo-classes. * The c component, which represents the number of type selectors and pseudo-elements. */ c: number; + /** + * Per-simple-selector contributions used to explain this specificity. + */ + components?: SpecificityComponent[]; } /** * Selector list data. @@ -4354,7 +4409,7 @@ front-end. /** * Pseudo element type. */ - export type PseudoType = "first-line"|"first-letter"|"checkmark"|"before"|"after"|"expand-icon"|"picker-icon"|"interest-button"|"marker"|"backdrop"|"column"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-marker-group"|"scroll-button"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-group-children"|"view-transition-old"|"view-transition-new"|"placeholder"|"file-selector-button"|"details-content"|"picker"|"permission-icon"|"overscroll-area-parent"; + export type PseudoType = "first-line"|"first-letter"|"checkmark"|"before"|"after"|"expand-icon"|"picker-icon"|"interest-button"|"marker"|"backdrop"|"column"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-marker-group"|"scroll-button"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-group-children"|"view-transition-old"|"view-transition-new"|"placeholder"|"file-selector-button"|"details-content"|"picker"|"select-listbox"|"permission-icon"|"overscroll-area-parent"|"overscroll-backdrop"|"skeleton"; /** * Shadow root type. */ @@ -5787,6 +5842,13 @@ and keeps it open until disabled. popover if it was previously force-opened. */ enable: boolean; + /** + * Optional ID of the element invoking this popover, used to establish the implicit anchor. +If not provided, it will fall back to the first invoker in the document, preferring +elements with a popovertarget attribute over those with a commandfor attribute. Note that +if there are multiple invokers, this is just an estimate. + */ + invokerNodeId?: BackendNodeId; } export type forceShowPopoverReturnValue = { /** @@ -10955,7 +11017,7 @@ This is a temporary ability and it will be removed in the future. /** * Types of reasons why a cookie should have been blocked by 3PCD but is exempted for the request. */ - export type CookieExemptionReason = "None"|"UserSetting"|"TPCDMetadata"|"TPCDDeprecationTrial"|"TopLevelTPCDDeprecationTrial"|"TPCDHeuristics"|"EnterprisePolicy"|"StorageAccess"|"TopLevelStorageAccess"|"Scheme"|"SameSiteNoneCookiesInSandbox"; + export type CookieExemptionReason = "None"|"UserSetting"|"EnterprisePolicy"|"StorageAccess"|"TopLevelStorageAccess"|"Scheme"|"SameSiteNoneCookiesInSandbox"; /** * A cookie which was not stored from a response with the corresponding reason. */ @@ -13611,6 +13673,51 @@ Page reload is required before the new cookie behavior will be observed */ outlineColor?: DOM.RGBA; } + /** + * Supported display cutout shapes. + */ + export type DisplayCutoutShape = "pill"|"notch"|"circle"|"rectangle"; + /** + * Configuration for a display cutout. + */ + export interface DisplayCutoutConfig { + /** + * A rectangle representing the cutout bounds. + */ + rect: DOM.Rect; + /** + * Shape used to draw the cutout. + */ + shape: DisplayCutoutShape; + /** + * Border radius for rounded cutout shapes. + */ + borderRadius?: number; + /** + * Upper shoulder radius for notch cutout shapes. + */ + upperRadius?: number; + /** + * Lower transition radius for notch cutout shapes. + */ + lowerRadius?: number; + /** + * Center x coordinate for circle cutout shapes. + */ + cx?: number; + /** + * Center y coordinate for circle cutout shapes. + */ + cy?: number; + /** + * Radius for circle cutout shapes. + */ + radius?: number; + /** + * The cutout fill color (default: black). + */ + contentColor?: DOM.RGBA; + } /** * Configuration for Window Controls Overlay */ @@ -14116,6 +14223,17 @@ Backend then generates 'inspectNodeRequested' event upon element selection. } export type setShowHingeReturnValue = { } + /** + * Add a display cutout overlay. + */ + export type setShowDisplayCutoutParameters = { + /** + * display cutout data, null means hide display cutout + */ + displayCutoutConfig?: DisplayCutoutConfig; + } + export type setShowDisplayCutoutReturnValue = { + } /** * Show elements in isolation mode with overlays. */ @@ -14345,7 +14463,7 @@ supported yet. in services/network/public/cpp/permissions_policy/permissions_policy_features.json5. LINT.IfChange(PermissionsPolicyFeature) */ - export type PermissionsPolicyFeature = "accelerometer"|"all-screens-capture"|"ambient-light-sensor"|"aria-notify"|"attribution-reporting"|"autofill"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-high-entropy-values"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"controlled-frame"|"cross-origin-isolated"|"deferred-fetch"|"deferred-fetch-minimal"|"device-attributes"|"digital-credentials-create"|"digital-credentials-get"|"direct-sockets"|"direct-sockets-multicast"|"direct-sockets-private"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"language-detector"|"language-model"|"local-fonts"|"local-network"|"local-network-access"|"loopback-network"|"magnetometer"|"manual-text"|"media-playback-while-not-visible"|"microphone"|"midi"|"on-device-speech-recognition"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"record-ad-auction-events"|"rewriter"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"summarizer"|"sync-xhr"|"tools"|"translator"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-app-installation"|"web-printing"|"web-share"|"window-management"|"writer"|"xr-spatial-tracking"; + export type PermissionsPolicyFeature = "accelerometer"|"all-screens-capture"|"ambient-light-sensor"|"aria-notify"|"attribution-reporting"|"autofill"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-high-entropy-values"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"controlled-frame"|"cross-origin-isolated"|"deferred-fetch"|"deferred-fetch-minimal"|"device-attributes"|"digital-credentials-create"|"digital-credentials-get"|"direct-sockets"|"direct-sockets-multicast"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"language-detector"|"language-model"|"local-fonts"|"local-network"|"local-network-access"|"loopback-network"|"magnetometer"|"manual-text"|"media-playback-while-not-visible"|"microphone"|"midi"|"on-device-speech-recognition"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"record-ad-auction-events"|"rewriter"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"summarizer"|"sync-xhr"|"tools"|"translator"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-app-installation"|"webnn"|"web-printing"|"web-share"|"window-management"|"writer"|"xr-spatial-tracking"; /** * Reason for a permissions policy feature to be disabled. */ @@ -15531,6 +15649,15 @@ iframes, shadow DOM, external resources, and element-inline styles. option, use with caution. */ grantUniveralAccess?: boolean; + /** + * An optional content security policy to set for the isolated world. +If omitted, any existing CSP for the world will be cleared. +Note that clearing or updating the CSP does not immediately affect the active +context in the same document because LocalDOMWindow caches the +ContentSecurityPolicy object. The change takes effect on subsequent +navigations when a new window context is created. + */ + contentSecurityPolicy?: string; } export type createIsolatedWorldReturnValue = { /** @@ -16681,7 +16808,7 @@ status is shared by prefetchStatusUpdated and prerenderStatusUpdated. * TODO(https://crbug.com/1384419): revisit the list of PrefetchStatus and filter out the ones that aren't necessary to the developers. */ - export type PrefetchStatus = "PrefetchAllowed"|"PrefetchFailedIneligibleRedirect"|"PrefetchFailedInvalidRedirect"|"PrefetchFailedMIMENotSupported"|"PrefetchFailedNetError"|"PrefetchFailedNon2XX"|"PrefetchEvictedAfterBrowsingDataRemoved"|"PrefetchEvictedAfterCandidateRemoved"|"PrefetchEvictedForNewerPrefetch"|"PrefetchHeldback"|"PrefetchIneligibleRetryAfter"|"PrefetchIsPrivacyDecoy"|"PrefetchIsStale"|"PrefetchNotEligibleBrowserContextOffTheRecord"|"PrefetchNotEligibleDataSaverEnabled"|"PrefetchNotEligibleExistingProxy"|"PrefetchNotEligibleHostIsNonUnique"|"PrefetchNotEligibleNonDefaultStoragePartition"|"PrefetchNotEligibleSameSiteCrossOriginPrefetchRequiredProxy"|"PrefetchNotEligibleSchemeIsNotHttps"|"PrefetchNotEligibleUserHasCookies"|"PrefetchNotEligibleUserHasServiceWorker"|"PrefetchNotEligibleUserHasServiceWorkerNoFetchHandler"|"PrefetchNotEligibleRedirectFromServiceWorker"|"PrefetchNotEligibleRedirectToServiceWorker"|"PrefetchNotEligibleBatterySaverEnabled"|"PrefetchNotEligiblePreloadingDisabled"|"PrefetchNotFinishedInTime"|"PrefetchNotStarted"|"PrefetchNotUsedCookiesChanged"|"PrefetchProxyNotAvailable"|"PrefetchResponseUsed"|"PrefetchSuccessfulButNotUsed"|"PrefetchNotUsedProbeFailed"|"PrefetchCancelledOnUserNavigation"; + export type PrefetchStatus = "PrefetchAllowed"|"PrefetchFailedIneligibleRedirect"|"PrefetchFailedInvalidRedirect"|"PrefetchFailedMIMENotSupported"|"PrefetchFailedNetError"|"PrefetchFailedNon2XX"|"PrefetchEvictedAfterBrowsingDataRemoved"|"PrefetchEvictedAfterCandidateRemoved"|"PrefetchEvictedForNewerPrefetch"|"PrefetchHeldback"|"PrefetchIneligibleRetryAfter"|"PrefetchIsPrivacyDecoy"|"PrefetchIsStale"|"PrefetchNotEligibleBlockedByConnectionAllowlist"|"PrefetchNotEligibleBrowserContextOffTheRecord"|"PrefetchNotEligibleDataSaverEnabled"|"PrefetchNotEligibleExistingProxy"|"PrefetchNotEligibleHostIsNonUnique"|"PrefetchNotEligibleNonDefaultStoragePartition"|"PrefetchNotEligibleSameSiteCrossOriginPrefetchRequiredProxy"|"PrefetchNotEligibleSchemeIsNotHttps"|"PrefetchNotEligibleUserHasCookies"|"PrefetchNotEligibleUserHasServiceWorker"|"PrefetchNotEligibleUserHasServiceWorkerNoFetchHandler"|"PrefetchNotEligibleRedirectFromServiceWorker"|"PrefetchNotEligibleRedirectToServiceWorker"|"PrefetchNotEligibleBatterySaverEnabled"|"PrefetchNotEligiblePreloadingDisabled"|"PrefetchNotFinishedInTime"|"PrefetchNotStarted"|"PrefetchNotUsedCookiesChanged"|"PrefetchProxyNotAvailable"|"PrefetchResponseUsed"|"PrefetchSuccessfulButNotUsed"|"PrefetchNotUsedProbeFailed"|"PrefetchCancelledOnUserNavigation"; /** * Information of headers to be displayed when the header mismatch occurred. */ @@ -18986,13 +19113,11 @@ present in the tab UI strip. Cannot be created with `forTab: true`, `newWindow: */ hidden?: boolean; /** - * If specified, the option is used to determine if the new target should -be focused or not. By default, the focus behavior depends on the -value of the background field. For example, background=false and focus=false -will result in the target tab being opened but the browser window remain -unchanged (if it was in the background, it will remain in the background) -and background=false with focus=undefined will result in the window being focused. -Using background: true and focus: true is not supported and will result in an error. + * If specified, determines whether the new target should be focused. +By default, the focus behavior depends on the `background` parameter: +- If `background` is false (default) and `focus` is omitted, the new target is focused and the browser window is brought to the foreground. +- If `background` is false and `focus` is false, the target is opened but the browser window's focus remains unchanged (e.g., if the window was in the background, it stays there). +- If `background` is true, setting `focus` to true is not supported and will result in an error. */ focus?: boolean; } @@ -19455,6 +19580,21 @@ are ignored. * Backend type (defaults to `auto`) */ tracingBackend?: TracingBackend; + /** + * Maximum width and height (in pixels) of each captured screenshot. +Only used when the `disabled-by-default-devtools.screenshot` category is +enabled. Defaults to 500. The combined memory footprint of screenshots +(`screenshotMaxSize` * `screenshotMaxSize` * 4 * `screenshotMaxCount`) +is clamped to the existing per-session budget. + */ + screenshotMaxSize?: number; + /** + * Maximum number of screenshots captured during a single tracing session. +Only used when the `disabled-by-default-devtools.screenshot` category is +enabled. Defaults to 450. Clamped together with `screenshotMaxSize` to +stay within the per-session screenshot memory budget. + */ + screenshotMaxCount?: number; } export type startReturnValue = { } @@ -23755,6 +23895,7 @@ Error was thrown. "Overlay.setShowWebVitals": Overlay.setShowWebVitalsParameters; "Overlay.setShowViewportSizeOnResize": Overlay.setShowViewportSizeOnResizeParameters; "Overlay.setShowHinge": Overlay.setShowHingeParameters; + "Overlay.setShowDisplayCutout": Overlay.setShowDisplayCutoutParameters; "Overlay.setShowIsolatedElements": Overlay.setShowIsolatedElementsParameters; "Overlay.setShowWindowControlsOverlay": Overlay.setShowWindowControlsOverlayParameters; "PWA.getOsAppState": PWA.getOsAppStateParameters; @@ -24425,6 +24566,7 @@ Error was thrown. "Overlay.setShowWebVitals": Overlay.setShowWebVitalsReturnValue; "Overlay.setShowViewportSizeOnResize": Overlay.setShowViewportSizeOnResizeReturnValue; "Overlay.setShowHinge": Overlay.setShowHingeReturnValue; + "Overlay.setShowDisplayCutout": Overlay.setShowDisplayCutoutReturnValue; "Overlay.setShowIsolatedElements": Overlay.setShowIsolatedElementsReturnValue; "Overlay.setShowWindowControlsOverlay": Overlay.setShowWindowControlsOverlayReturnValue; "PWA.getOsAppState": PWA.getOsAppStateReturnValue; diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index 6cd606894ee2a..1d843a9ff7ae8 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -384,8 +384,8 @@ const DOWNLOAD_PATHS: Record = { 'mac14-arm64': 'builds/webkit/%s/webkit-mac-14-arm64.zip', 'mac15': 'builds/webkit/%s/webkit-mac-15.zip', 'mac15-arm64': 'builds/webkit/%s/webkit-mac-15-arm64.zip', - 'mac26': 'builds/webkit/%s/webkit-mac-15.zip', - 'mac26-arm64': 'builds/webkit/%s/webkit-mac-15-arm64.zip', + 'mac26': 'builds/webkit/%s/webkit-mac-26.zip', + 'mac26-arm64': 'builds/webkit/%s/webkit-mac-26-arm64.zip', 'win64': 'builds/webkit/%s/webkit-win64.zip', }, 'ffmpeg': { diff --git a/packages/playwright-core/src/server/webkit/wkBrowser.ts b/packages/playwright-core/src/server/webkit/wkBrowser.ts index a46a7e51729e3..12fbb53f99c12 100644 --- a/packages/playwright-core/src/server/webkit/wkBrowser.ts +++ b/packages/playwright-core/src/server/webkit/wkBrowser.ts @@ -251,7 +251,7 @@ export class WKBrowserContext extends BrowserContext { async doGetCookies(urls: string[]): Promise { const { cookies } = await this._browser._browserSession.send('Playwright.getAllCookies', { browserContextId: this._browserContextId }); return network.filterCookies(cookies.map((c: channels.NetworkCookie) => { - const { name, value, domain, path, expires, httpOnly, secure, sameSite } = c; + const { name, value, domain, path, expires, httpOnly, secure, sameSite, partitionKey } = c; const copy: channels.NetworkCookie = { name, value, @@ -262,13 +262,15 @@ export class WKBrowserContext extends BrowserContext { secure, sameSite, }; + if (partitionKey) + copy.partitionKey = partitionKey; return copy; }), urls); } async addCookies(cookies: channels.SetNetworkCookie[]) { const cc = network.rewriteCookies(cookies).map(c => { - const { name, value, domain, path, expires, httpOnly, secure, sameSite } = c; + const { name, value, domain, path, expires, httpOnly, secure, sameSite, partitionKey } = c; const copy: Protocol.Playwright.SetCookieParam = { name, value, @@ -280,6 +282,8 @@ export class WKBrowserContext extends BrowserContext { sameSite, session: expires === -1 || expires === undefined, }; + if (partitionKey) + copy.partitionKey = partitionKey; return copy; }); await this._browser._browserSession.send('Playwright.setCookies', { cookies: cc, browserContextId: this._browserContextId }); diff --git a/packages/playwright-core/src/tools/backend/find.ts b/packages/playwright-core/src/tools/backend/find.ts index 703dd0e3a9e36..c29b01dfbf9f6 100644 --- a/packages/playwright-core/src/tools/backend/find.ts +++ b/packages/playwright-core/src/tools/backend/find.ts @@ -26,7 +26,7 @@ const find = defineTabTool({ schema: { name: 'browser_find', title: 'Find in page snapshot', - description: 'Search the accessibility snapshot of the current page for text or a regular expression. Returns matching snapshot nodes with a few lines of surrounding context (like search snippets), which is cheaper than capturing the whole snapshot when you only need to locate an element and its ref.', + description: 'Search the accessibility snapshot of the current page for text or a regular expression. Returns matching snapshot nodes with a few lines of surrounding context (like search snippets), each shown under its path from the root of the tree, which is cheaper than capturing the whole snapshot when you only need to locate an element and its ref.', inputSchema: z.object({ text: z.string().optional().describe('Plain text to search for in the page snapshot (case-insensitive substring match). Provide either text or regex, not both.'), regex: z.string().optional().refine(v => !v || isValidRegex(v), { message: 'Invalid regular expression' }).describe('Regular expression to search for in the page snapshot. Matching is case-sensitive by default; wrap the pattern in slashes to add flags, e.g. "/error/i" for case-insensitive. Provide either text or regex, not both.'), @@ -61,6 +61,7 @@ const find = defineTabTool({ const snapshot = await tab.page.ariaSnapshot({ mode: 'ai' }); const lines = snapshot.split('\n'); + const indents = lines.map(indentOf); const matchedLines: number[] = []; for (let i = 0; i < lines.length; i++) { if (matches(lines[i])) @@ -84,15 +85,31 @@ const find = defineTabTool({ windows.push({ start, end }); } - const snippets = windows.map(window => lines.slice(window.start, window.end + 1).join('\n')); + const path = new Set(); + for (const match of matchedLines) { + path.add(match); + for (const ancestor of ancestorIndices(lines, indents, match)) + path.add(ancestor); + } + + const snippets = windows.map(window => { + const indices = ancestorIndices(lines, indents, window.start); + for (let i = window.start; i <= window.end; i++) + indices.push(i); + const out: string[] = []; + for (let i = 0; i < indices.length; i++) { + const index = indices[i]; + if (i > 0 && index > indices[i - 1] + 1 && !path.has(index) && !path.has(indices[i - 1])) + out.push(' '.repeat(indents[index]) + '...'); + out.push(lines[index]); + } + return out.join('\n'); + }); const matchWord = matchedLines.length === 1 ? 'match' : 'matches'; response.addTextResult(`Found ${matchedLines.length} ${matchWord} for ${query}:\n\n${snippets.join('\n\n----\n\n')}`); }, }); -// Accept either a bare pattern or a `/pattern/flags` literal, mirroring the -// test runner's forceRegExp. Matching is line-oriented, so the global flag is -// dropped: it only makes `.test()` stateful without changing which lines match. function compileRegex(source: string): RegExp { const literal = /^\/(.*)\/([a-z]*)$/.exec(source); const pattern = literal ? literal[1] : source; @@ -109,6 +126,24 @@ function isValidRegex(source: string): boolean { } } +function indentOf(line: string): number { + return line.length - line.trimStart().length; +} + +function ancestorIndices(lines: string[], indents: number[], index: number): number[] { + const result: number[] = []; + let indent = indents[index]; + for (let i = index - 1; i >= 0 && indent > 0; i--) { + if (!lines[i].trim()) + continue; + if (indents[i] < indent) { + result.push(i); + indent = indents[i]; + } + } + return result.reverse(); +} + export default [ find, ]; diff --git a/packages/playwright-core/src/tools/cli-client/program.ts b/packages/playwright-core/src/tools/cli-client/program.ts index 0b87a30bae33a..095d375f24097 100644 --- a/packages/playwright-core/src/tools/cli-client/program.ts +++ b/packages/playwright-core/src/tools/cli-client/program.ts @@ -52,7 +52,9 @@ type AttachOptions = { type OpenOptions = { browser?: string; config?: string; + device?: string; headed?: boolean; + mobile?: boolean; persistent?: boolean; profile?: string; }; diff --git a/packages/playwright-core/src/tools/cli-client/session.ts b/packages/playwright-core/src/tools/cli-client/session.ts index e9d79783b0885..bbcf54cf41d90 100644 --- a/packages/playwright-core/src/tools/cli-client/session.ts +++ b/packages/playwright-core/src/tools/cli-client/session.ts @@ -125,6 +125,10 @@ export class Session { ]; if (cliArgs.headed) args.push('--headed'); + if (cliArgs.mobile) + args.push('--mobile'); + if (cliArgs.device) + args.push(`--device=${cliArgs.device}`); if (cliArgs.browser) args.push(`--browser=${cliArgs.browser}`); if (cliArgs.persistent) diff --git a/packages/playwright-core/src/tools/cli-client/skill/SKILL.md b/packages/playwright-core/src/tools/cli-client/skill/SKILL.md index 987cd3a69d3ec..6f34a187590d6 100644 --- a/packages/playwright-core/src/tools/cli-client/skill/SKILL.md +++ b/packages/playwright-core/src/tools/cli-client/skill/SKILL.md @@ -215,6 +215,12 @@ playwright-cli open --browser=firefox playwright-cli open --browser=webkit playwright-cli open --browser=msedge +# Emulate a generic mobile device (Pixel 10 for Chromium, iPhone 17 for WebKit). +# Prefer this when a mobile layout is acceptable: mobile pages are usually +# lighter, so snapshots are smaller and cheaper. +playwright-cli open --mobile +playwright-cli open --device="iPhone 15" + # Use persistent profile (by default profile is in-memory) playwright-cli open --persistent # Use persistent profile with custom directory diff --git a/packages/playwright-core/src/tools/cli-daemon/commands.ts b/packages/playwright-core/src/tools/cli-daemon/commands.ts index ec87f4101e027..3e34390a7686c 100644 --- a/packages/playwright-core/src/tools/cli-daemon/commands.ts +++ b/packages/playwright-core/src/tools/cli-daemon/commands.ts @@ -48,7 +48,9 @@ const open = declareCommand({ options: z.object({ browser: z.string().optional().describe('Browser or chrome channel to use, possible values: chrome, firefox, webkit, msedge.'), config: z.string().optional().describe('Path to the configuration file, defaults to .playwright/cli.config.json'), + device: z.string().optional().describe('Emulate a specific device, for example "iPhone 15".'), headed: z.boolean().optional().describe('Run browser in headed mode'), + mobile: z.boolean().optional().describe('Emulate a generic mobile device (Pixel 10 for Chromium, iPhone 17 for WebKit). Mobile pages are usually lighter, which saves tokens.'), persistent: z.boolean().optional().describe('Use persistent browser profile'), profile: z.string().optional().describe('Path to a persistent user data directory.'), }), diff --git a/packages/playwright-core/src/tools/cli-daemon/program.ts b/packages/playwright-core/src/tools/cli-daemon/program.ts index 1ff7ac722de0b..b9e8930306277 100644 --- a/packages/playwright-core/src/tools/cli-daemon/program.ts +++ b/packages/playwright-core/src/tools/cli-daemon/program.ts @@ -34,6 +34,8 @@ import type { Command } from 'commander'; export function decorateProgram(program: Command) { program.argument('[session-name]', 'name of the session to create or connect to', 'default') .option('--headed', 'run in headed mode (non-headless)') + .option('--device ', 'emulate a specific device, for example "iPhone 15"') + .option('--mobile', 'emulate a generic mobile device (Pixel 10 for Chromium, iPhone 17 for WebKit)') .option('--extension', 'run with the extension') .option('--browser ', 'browser to use (chromium, chrome, firefox, webkit)') .option('--persistent', 'use a persistent browser context') diff --git a/packages/playwright-core/src/tools/mcp/config.ts b/packages/playwright-core/src/tools/mcp/config.ts index f1994e4ea0a37..47c8c40335f5e 100644 --- a/packages/playwright-core/src/tools/mcp/config.ts +++ b/packages/playwright-core/src/tools/mcp/config.ts @@ -58,6 +58,7 @@ export type CLIOptions = { initPage?: string[]; isolated?: boolean; imageResponses?: 'allow' | 'omit'; + mobile?: boolean; sandbox?: boolean; outputDir?: string; outputMaxSize?: number; @@ -155,7 +156,9 @@ export async function resolveCLIConfigForCLI(daemonProfilesDir: string, sessionN cdpEndpoint: options.cdp, config: options.config, browser: options.browser, + device: options.device, headless: options.headed ? false : undefined, + mobile: options.mobile, extension: options.extension, userDataDir: options.profile, snapshotMode: 'full', @@ -212,7 +215,7 @@ async function validateBrowserConfig(browser: MergedConfig['browser']): Promise< browser.launchOptions.channel = 'chrome'; } - if (browser.browserName === 'chromium' && browser.launchOptions.chromiumSandbox === undefined) { + if (browserName === 'chromium' && browser.launchOptions.chromiumSandbox === undefined) { if (process.platform === 'linux') browser.launchOptions.chromiumSandbox = browser.launchOptions.channel !== 'chromium' && browser.launchOptions.channel !== 'chrome-for-testing'; else @@ -291,11 +294,20 @@ function configFromCLIOptions(cliOptions: CLIOptions): Config & { configFile?: s if (cliOptions.sandbox !== undefined) launchOptions.chromiumSandbox = cliOptions.sandbox; - if (cliOptions.device && cliOptions.cdpEndpoint) + let device = cliOptions.device; + if (cliOptions.mobile) { + if (device) + throw new Error('Cannot use --mobile together with --device, pick one.'); + if (browserName === 'firefox') + throw new Error('--mobile is not supported with the Firefox browser.'); + device = browserName === 'webkit' ? 'iPhone 17' : 'Pixel 10'; + } + + if (device && cliOptions.cdpEndpoint) throw new Error('Device emulation is not supported with cdpEndpoint.'); // Context options - const contextOptions: playwrightTypes.BrowserContextOptions = cliOptions.device ? playwright.devices[cliOptions.device] : {}; + const contextOptions: playwrightTypes.BrowserContextOptions = device ? playwright.devices[device] : {}; if (cliOptions.proxyServer) { const proxy: playwrightTypes.LaunchOptions['proxy'] = { server: cliOptions.proxyServer }; @@ -409,6 +421,7 @@ export function configFromEnv(env?: NodeJS.ProcessEnv): Config & { configFile?: options.isolated = envToBoolean(e.PLAYWRIGHT_MCP_ISOLATED); if (e.PLAYWRIGHT_MCP_IMAGE_RESPONSES) options.imageResponses = enumParser<'allow' | 'omit'>('--image-responses', ['allow', 'omit'], e.PLAYWRIGHT_MCP_IMAGE_RESPONSES); + options.mobile = envToBoolean(e.PLAYWRIGHT_MCP_MOBILE); options.sandbox = envToBoolean(e.PLAYWRIGHT_MCP_SANDBOX); options.outputDir = envToString(e.PLAYWRIGHT_MCP_OUTPUT_DIR); options.outputMaxSize = numberParser(e.PLAYWRIGHT_MCP_OUTPUT_MAX_SIZE); diff --git a/packages/playwright-core/src/tools/mcp/program.ts b/packages/playwright-core/src/tools/mcp/program.ts index f074c961e2b41..8501160c1a3a5 100644 --- a/packages/playwright-core/src/tools/mcp/program.ts +++ b/packages/playwright-core/src/tools/mcp/program.ts @@ -46,6 +46,7 @@ export function decorateMCPCommand(command: Command) { .option('--config ', 'path to the configuration file.') .option('--console-level ', 'level of console messages to return: "error", "warning", "info", "debug". Each level includes the messages of more severe levels.', enumParser.bind(null, '--console-level', ['error', 'warning', 'info', 'debug'])) .option('--device ', 'device to emulate, for example: "iPhone 15"') + .option('--mobile', 'emulate a generic mobile device (Pixel 10 for Chromium, iPhone 17 for WebKit). Mobile pages are usually lighter, which saves tokens. Cannot be combined with --device.') .option('--executable-path ', 'path to the browser executable.') .option('--extension', 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright Extension" to be installed.') .option('--endpoint ', 'Bound browser endpoint to connect to.') diff --git a/packages/playwright-core/types/protocol.d.ts b/packages/playwright-core/types/protocol.d.ts index e3d5785deafe0..858642160b282 100644 --- a/packages/playwright-core/types/protocol.d.ts +++ b/packages/playwright-core/types/protocol.d.ts @@ -345,6 +345,28 @@ including nodes that are ignored for accessibility. * A domain for ad-related metrics and data. */ export namespace Ads { + /** + * Ad frame data. + */ + export interface AdFrameData { + /** + * The DevTools frame token. + */ + frameId: Page.FrameId; + /** + * The initial origin of the frame. To minimize the payload size, this is +only sent once per frame. + */ + initialOrigin?: string; + /** + * The network bytes of the frame. + */ + networkBytes: number; + /** + * The CPU time of the frame, in milliseconds. + */ + cpuTime: number; + } /** * Ad metrics for a page. */ @@ -376,6 +398,14 @@ duration of the page. * The total ad network bytes. */ totalAdNetworkBytes: number; + /** + * The list of ad frames that have been updated since the last event. + */ + updateAdFrames: AdFrameData[]; + /** + * The list of ad frame IDs that have been removed since the last event. + */ + removeAdFrames: Page.FrameId[]; } @@ -1035,7 +1065,7 @@ third_party/blink/public/mojom/devtools/inspector_issue.mojom. Should be updated alongside EmailVerificationRequestResult in third_party/blink/public/mojom/devtools/inspector_issue.mojom. */ - export type EmailVerificationRequestIssueReason = "InvalidEmail"|"DnsFetchFailed"|"DnsInvalidRecord"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"WellKnownMissingIssuanceEndpoint"|"WellKnownIssuanceEndpointCrossOrigin"|"WellKnownUnsupportedSigningAlgorithm"|"TokenHttpNotFound"|"TokenNoResponse"|"TokenInvalidResponse"|"TokenInvalidContentType"|"TokenMalformedSdJwt"|"TokenInvalidSdJwt"|"KeyBindingSigningFailed"|"RpOriginIsOpaque"|"WellKnownMissingAccountsEndpoint"|"UserLoggedOut"|"WellKnownAccountsEndpointCrossOrigin"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsInvalidContentType"|"AccountsEmptyList"|"EmailVerificationWellKnownHttpNotFound"|"EmailVerificationWellKnownNoResponse"|"EmailVerificationWellKnownInvalidResponse"|"EmailVerificationWellKnownInvalidContentType"|"JwksHttpNotFound"|"JwksInvalidResponse"|"TokenVerificationSdJwtUnsupportedHeaderAlg"|"TokenVerificationSdJwtMissingIss"|"TokenVerificationSdJwtMissingIat"|"TokenVerificationSdJwtMissingCnf"|"TokenVerificationSdJwtMissingEmail"|"TokenVerificationSdJwtInvalidIssuedAt"|"TokenVerificationSdJwtInvalidIssuer"|"TokenVerificationSdJwtJwksMissingKeys"|"TokenVerificationSdJwtSignatureFailed"|"TokenVerificationSdJwtInvalidEmailVerified"|"TokenVerificationSdJwtInvalidEmail"|"TokenVerificationSdJwtInvalidHolderKey"|"TokenVerificationKbInvalidTyp"|"TokenVerificationKbMissingAud"|"TokenVerificationKbMissingNonce"|"TokenVerificationKbMissingIat"|"TokenVerificationKbMissingSdHash"|"TokenVerificationKbInvalidIssuedAt"|"TokenVerificationKbInvalidAudience"|"TokenVerificationKbInvalidNonce"|"TokenVerificationKbInvalidSdHash"|"TokenVerificationKbMissingCnf"|"TokenVerificationKbSignatureFailed"; + export type EmailVerificationRequestIssueReason = "InvalidEmail"|"DnsFetchFailed"|"DnsInvalidRecord"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"WellKnownMissingIssuanceEndpoint"|"WellKnownIssuanceEndpointCrossOrigin"|"WellKnownUnsupportedSigningAlgorithm"|"TokenHttpNotFound"|"TokenNoResponse"|"TokenInvalidResponse"|"TokenInvalidContentType"|"TokenMalformedSdJwt"|"TokenInvalidSdJwt"|"KeyBindingSigningFailed"|"RpOriginIsOpaque"|"WellKnownMissingAccountsEndpoint"|"UserLoggedOut"|"WellKnownAccountsEndpointCrossOrigin"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsInvalidContentType"|"AccountsEmptyList"|"EmailVerificationWellKnownHttpNotFound"|"EmailVerificationWellKnownNoResponse"|"EmailVerificationWellKnownInvalidResponse"|"EmailVerificationWellKnownInvalidContentType"|"JwksHttpNotFound"|"JwksInvalidResponse"|"TokenVerificationSdJwtUnsupportedHeaderAlg"|"TokenVerificationSdJwtInvalidTyp"|"TokenVerificationSdJwtMissingIss"|"TokenVerificationSdJwtMissingIat"|"TokenVerificationSdJwtMissingCnf"|"TokenVerificationSdJwtMissingEmail"|"TokenVerificationSdJwtInvalidIssuedAt"|"TokenVerificationSdJwtInvalidIssuer"|"TokenVerificationSdJwtJwksMissingKeys"|"TokenVerificationSdJwtSignatureFailed"|"TokenVerificationSdJwtInvalidEmailVerified"|"TokenVerificationSdJwtInvalidEmail"|"TokenVerificationSdJwtInvalidHolderKey"|"TokenVerificationKbInvalidTyp"|"TokenVerificationKbMissingAud"|"TokenVerificationKbMissingNonce"|"TokenVerificationKbMissingIat"|"TokenVerificationKbMissingSdHash"|"TokenVerificationKbInvalidIssuedAt"|"TokenVerificationKbInvalidAudience"|"TokenVerificationKbInvalidNonce"|"TokenVerificationKbInvalidSdHash"|"TokenVerificationKbMissingCnf"|"TokenVerificationKbSignatureFailed"; /** * This issue tracks client hints related issues. It's used to deprecate old features, encourage the use of new ones, and provide general guidance. @@ -1128,7 +1158,7 @@ re-identify users. */ sourceCodeLocation?: SourceCodeLocation; } - export type PermissionElementIssueType = "InvalidType"|"FencedFrameDisallowed"|"CspFrameAncestorsMissing"|"PermissionsPolicyBlocked"|"PaddingRightUnsupported"|"PaddingBottomUnsupported"|"InsetBoxShadowUnsupported"|"RequestInProgress"|"UntrustedEvent"|"RegistrationFailed"|"TypeNotSupported"|"InvalidTypeActivation"|"SecurityChecksFailed"|"ActivationDisabled"|"GeolocationDeprecated"|"InvalidDisplayStyle"|"NonOpaqueColor"|"LowContrast"|"FontSizeTooSmall"|"FontSizeTooLarge"|"InvalidSizeValue"; + export type PermissionElementIssueType = "InvalidType"|"FencedFrameDisallowed"|"CspFrameAncestorsMissing"|"PermissionsPolicyBlocked"|"PaddingRightUnsupported"|"PaddingBottomUnsupported"|"InsetBoxShadowUnsupported"|"RequestInProgress"|"UntrustedEvent"|"RegistrationFailed"|"TypeNotSupported"|"InvalidTypeActivation"|"SecurityChecksFailed"|"ActivationDisabled"|"GeolocationDeprecated"|"InvalidDisplayStyle"|"NonOpaqueColor"|"LowContrast"|"FontSizeTooSmall"|"FontSizeTooLarge"|"InvalidSizeValue"|"NonSecureContext"|"MissingTransientUserActivation"; /** * This issue warns about improper usage of the element. */ @@ -2456,6 +2486,27 @@ inspector" rules), "regular" for regular stylesheets. */ specificity?: Specificity; } + /** + * Contribution of an individual simple selector to specificity. + */ + export interface SpecificityComponent { + /** + * The simple selector text that contributes to specificity. + */ + text: string; + /** + * The a component contribution. + */ + a: number; + /** + * The b component contribution. + */ + b: number; + /** + * The c component contribution. + */ + c: number; + } /** * Specificity: https://drafts.csswg.org/selectors/#specificity-rules @@ -2474,6 +2525,10 @@ pseudo-classes. * The c component, which represents the number of type selectors and pseudo-elements. */ c: number; + /** + * Per-simple-selector contributions used to explain this specificity. + */ + components?: SpecificityComponent[]; } /** * Selector list data. @@ -4354,7 +4409,7 @@ front-end. /** * Pseudo element type. */ - export type PseudoType = "first-line"|"first-letter"|"checkmark"|"before"|"after"|"expand-icon"|"picker-icon"|"interest-button"|"marker"|"backdrop"|"column"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-marker-group"|"scroll-button"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-group-children"|"view-transition-old"|"view-transition-new"|"placeholder"|"file-selector-button"|"details-content"|"picker"|"permission-icon"|"overscroll-area-parent"; + export type PseudoType = "first-line"|"first-letter"|"checkmark"|"before"|"after"|"expand-icon"|"picker-icon"|"interest-button"|"marker"|"backdrop"|"column"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-marker-group"|"scroll-button"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-group-children"|"view-transition-old"|"view-transition-new"|"placeholder"|"file-selector-button"|"details-content"|"picker"|"select-listbox"|"permission-icon"|"overscroll-area-parent"|"overscroll-backdrop"|"skeleton"; /** * Shadow root type. */ @@ -5787,6 +5842,13 @@ and keeps it open until disabled. popover if it was previously force-opened. */ enable: boolean; + /** + * Optional ID of the element invoking this popover, used to establish the implicit anchor. +If not provided, it will fall back to the first invoker in the document, preferring +elements with a popovertarget attribute over those with a commandfor attribute. Note that +if there are multiple invokers, this is just an estimate. + */ + invokerNodeId?: BackendNodeId; } export type forceShowPopoverReturnValue = { /** @@ -10955,7 +11017,7 @@ This is a temporary ability and it will be removed in the future. /** * Types of reasons why a cookie should have been blocked by 3PCD but is exempted for the request. */ - export type CookieExemptionReason = "None"|"UserSetting"|"TPCDMetadata"|"TPCDDeprecationTrial"|"TopLevelTPCDDeprecationTrial"|"TPCDHeuristics"|"EnterprisePolicy"|"StorageAccess"|"TopLevelStorageAccess"|"Scheme"|"SameSiteNoneCookiesInSandbox"; + export type CookieExemptionReason = "None"|"UserSetting"|"EnterprisePolicy"|"StorageAccess"|"TopLevelStorageAccess"|"Scheme"|"SameSiteNoneCookiesInSandbox"; /** * A cookie which was not stored from a response with the corresponding reason. */ @@ -13611,6 +13673,51 @@ Page reload is required before the new cookie behavior will be observed */ outlineColor?: DOM.RGBA; } + /** + * Supported display cutout shapes. + */ + export type DisplayCutoutShape = "pill"|"notch"|"circle"|"rectangle"; + /** + * Configuration for a display cutout. + */ + export interface DisplayCutoutConfig { + /** + * A rectangle representing the cutout bounds. + */ + rect: DOM.Rect; + /** + * Shape used to draw the cutout. + */ + shape: DisplayCutoutShape; + /** + * Border radius for rounded cutout shapes. + */ + borderRadius?: number; + /** + * Upper shoulder radius for notch cutout shapes. + */ + upperRadius?: number; + /** + * Lower transition radius for notch cutout shapes. + */ + lowerRadius?: number; + /** + * Center x coordinate for circle cutout shapes. + */ + cx?: number; + /** + * Center y coordinate for circle cutout shapes. + */ + cy?: number; + /** + * Radius for circle cutout shapes. + */ + radius?: number; + /** + * The cutout fill color (default: black). + */ + contentColor?: DOM.RGBA; + } /** * Configuration for Window Controls Overlay */ @@ -14116,6 +14223,17 @@ Backend then generates 'inspectNodeRequested' event upon element selection. } export type setShowHingeReturnValue = { } + /** + * Add a display cutout overlay. + */ + export type setShowDisplayCutoutParameters = { + /** + * display cutout data, null means hide display cutout + */ + displayCutoutConfig?: DisplayCutoutConfig; + } + export type setShowDisplayCutoutReturnValue = { + } /** * Show elements in isolation mode with overlays. */ @@ -14345,7 +14463,7 @@ supported yet. in services/network/public/cpp/permissions_policy/permissions_policy_features.json5. LINT.IfChange(PermissionsPolicyFeature) */ - export type PermissionsPolicyFeature = "accelerometer"|"all-screens-capture"|"ambient-light-sensor"|"aria-notify"|"attribution-reporting"|"autofill"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-high-entropy-values"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"controlled-frame"|"cross-origin-isolated"|"deferred-fetch"|"deferred-fetch-minimal"|"device-attributes"|"digital-credentials-create"|"digital-credentials-get"|"direct-sockets"|"direct-sockets-multicast"|"direct-sockets-private"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"language-detector"|"language-model"|"local-fonts"|"local-network"|"local-network-access"|"loopback-network"|"magnetometer"|"manual-text"|"media-playback-while-not-visible"|"microphone"|"midi"|"on-device-speech-recognition"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"record-ad-auction-events"|"rewriter"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"summarizer"|"sync-xhr"|"tools"|"translator"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-app-installation"|"web-printing"|"web-share"|"window-management"|"writer"|"xr-spatial-tracking"; + export type PermissionsPolicyFeature = "accelerometer"|"all-screens-capture"|"ambient-light-sensor"|"aria-notify"|"attribution-reporting"|"autofill"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-high-entropy-values"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"controlled-frame"|"cross-origin-isolated"|"deferred-fetch"|"deferred-fetch-minimal"|"device-attributes"|"digital-credentials-create"|"digital-credentials-get"|"direct-sockets"|"direct-sockets-multicast"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"language-detector"|"language-model"|"local-fonts"|"local-network"|"local-network-access"|"loopback-network"|"magnetometer"|"manual-text"|"media-playback-while-not-visible"|"microphone"|"midi"|"on-device-speech-recognition"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"record-ad-auction-events"|"rewriter"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"summarizer"|"sync-xhr"|"tools"|"translator"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-app-installation"|"webnn"|"web-printing"|"web-share"|"window-management"|"writer"|"xr-spatial-tracking"; /** * Reason for a permissions policy feature to be disabled. */ @@ -15531,6 +15649,15 @@ iframes, shadow DOM, external resources, and element-inline styles. option, use with caution. */ grantUniveralAccess?: boolean; + /** + * An optional content security policy to set for the isolated world. +If omitted, any existing CSP for the world will be cleared. +Note that clearing or updating the CSP does not immediately affect the active +context in the same document because LocalDOMWindow caches the +ContentSecurityPolicy object. The change takes effect on subsequent +navigations when a new window context is created. + */ + contentSecurityPolicy?: string; } export type createIsolatedWorldReturnValue = { /** @@ -16681,7 +16808,7 @@ status is shared by prefetchStatusUpdated and prerenderStatusUpdated. * TODO(https://crbug.com/1384419): revisit the list of PrefetchStatus and filter out the ones that aren't necessary to the developers. */ - export type PrefetchStatus = "PrefetchAllowed"|"PrefetchFailedIneligibleRedirect"|"PrefetchFailedInvalidRedirect"|"PrefetchFailedMIMENotSupported"|"PrefetchFailedNetError"|"PrefetchFailedNon2XX"|"PrefetchEvictedAfterBrowsingDataRemoved"|"PrefetchEvictedAfterCandidateRemoved"|"PrefetchEvictedForNewerPrefetch"|"PrefetchHeldback"|"PrefetchIneligibleRetryAfter"|"PrefetchIsPrivacyDecoy"|"PrefetchIsStale"|"PrefetchNotEligibleBrowserContextOffTheRecord"|"PrefetchNotEligibleDataSaverEnabled"|"PrefetchNotEligibleExistingProxy"|"PrefetchNotEligibleHostIsNonUnique"|"PrefetchNotEligibleNonDefaultStoragePartition"|"PrefetchNotEligibleSameSiteCrossOriginPrefetchRequiredProxy"|"PrefetchNotEligibleSchemeIsNotHttps"|"PrefetchNotEligibleUserHasCookies"|"PrefetchNotEligibleUserHasServiceWorker"|"PrefetchNotEligibleUserHasServiceWorkerNoFetchHandler"|"PrefetchNotEligibleRedirectFromServiceWorker"|"PrefetchNotEligibleRedirectToServiceWorker"|"PrefetchNotEligibleBatterySaverEnabled"|"PrefetchNotEligiblePreloadingDisabled"|"PrefetchNotFinishedInTime"|"PrefetchNotStarted"|"PrefetchNotUsedCookiesChanged"|"PrefetchProxyNotAvailable"|"PrefetchResponseUsed"|"PrefetchSuccessfulButNotUsed"|"PrefetchNotUsedProbeFailed"|"PrefetchCancelledOnUserNavigation"; + export type PrefetchStatus = "PrefetchAllowed"|"PrefetchFailedIneligibleRedirect"|"PrefetchFailedInvalidRedirect"|"PrefetchFailedMIMENotSupported"|"PrefetchFailedNetError"|"PrefetchFailedNon2XX"|"PrefetchEvictedAfterBrowsingDataRemoved"|"PrefetchEvictedAfterCandidateRemoved"|"PrefetchEvictedForNewerPrefetch"|"PrefetchHeldback"|"PrefetchIneligibleRetryAfter"|"PrefetchIsPrivacyDecoy"|"PrefetchIsStale"|"PrefetchNotEligibleBlockedByConnectionAllowlist"|"PrefetchNotEligibleBrowserContextOffTheRecord"|"PrefetchNotEligibleDataSaverEnabled"|"PrefetchNotEligibleExistingProxy"|"PrefetchNotEligibleHostIsNonUnique"|"PrefetchNotEligibleNonDefaultStoragePartition"|"PrefetchNotEligibleSameSiteCrossOriginPrefetchRequiredProxy"|"PrefetchNotEligibleSchemeIsNotHttps"|"PrefetchNotEligibleUserHasCookies"|"PrefetchNotEligibleUserHasServiceWorker"|"PrefetchNotEligibleUserHasServiceWorkerNoFetchHandler"|"PrefetchNotEligibleRedirectFromServiceWorker"|"PrefetchNotEligibleRedirectToServiceWorker"|"PrefetchNotEligibleBatterySaverEnabled"|"PrefetchNotEligiblePreloadingDisabled"|"PrefetchNotFinishedInTime"|"PrefetchNotStarted"|"PrefetchNotUsedCookiesChanged"|"PrefetchProxyNotAvailable"|"PrefetchResponseUsed"|"PrefetchSuccessfulButNotUsed"|"PrefetchNotUsedProbeFailed"|"PrefetchCancelledOnUserNavigation"; /** * Information of headers to be displayed when the header mismatch occurred. */ @@ -18986,13 +19113,11 @@ present in the tab UI strip. Cannot be created with `forTab: true`, `newWindow: */ hidden?: boolean; /** - * If specified, the option is used to determine if the new target should -be focused or not. By default, the focus behavior depends on the -value of the background field. For example, background=false and focus=false -will result in the target tab being opened but the browser window remain -unchanged (if it was in the background, it will remain in the background) -and background=false with focus=undefined will result in the window being focused. -Using background: true and focus: true is not supported and will result in an error. + * If specified, determines whether the new target should be focused. +By default, the focus behavior depends on the `background` parameter: +- If `background` is false (default) and `focus` is omitted, the new target is focused and the browser window is brought to the foreground. +- If `background` is false and `focus` is false, the target is opened but the browser window's focus remains unchanged (e.g., if the window was in the background, it stays there). +- If `background` is true, setting `focus` to true is not supported and will result in an error. */ focus?: boolean; } @@ -19455,6 +19580,21 @@ are ignored. * Backend type (defaults to `auto`) */ tracingBackend?: TracingBackend; + /** + * Maximum width and height (in pixels) of each captured screenshot. +Only used when the `disabled-by-default-devtools.screenshot` category is +enabled. Defaults to 500. The combined memory footprint of screenshots +(`screenshotMaxSize` * `screenshotMaxSize` * 4 * `screenshotMaxCount`) +is clamped to the existing per-session budget. + */ + screenshotMaxSize?: number; + /** + * Maximum number of screenshots captured during a single tracing session. +Only used when the `disabled-by-default-devtools.screenshot` category is +enabled. Defaults to 450. Clamped together with `screenshotMaxSize` to +stay within the per-session screenshot memory budget. + */ + screenshotMaxCount?: number; } export type startReturnValue = { } @@ -23755,6 +23895,7 @@ Error was thrown. "Overlay.setShowWebVitals": Overlay.setShowWebVitalsParameters; "Overlay.setShowViewportSizeOnResize": Overlay.setShowViewportSizeOnResizeParameters; "Overlay.setShowHinge": Overlay.setShowHingeParameters; + "Overlay.setShowDisplayCutout": Overlay.setShowDisplayCutoutParameters; "Overlay.setShowIsolatedElements": Overlay.setShowIsolatedElementsParameters; "Overlay.setShowWindowControlsOverlay": Overlay.setShowWindowControlsOverlayParameters; "PWA.getOsAppState": PWA.getOsAppStateParameters; @@ -24425,6 +24566,7 @@ Error was thrown. "Overlay.setShowWebVitals": Overlay.setShowWebVitalsReturnValue; "Overlay.setShowViewportSizeOnResize": Overlay.setShowViewportSizeOnResizeReturnValue; "Overlay.setShowHinge": Overlay.setShowHingeReturnValue; + "Overlay.setShowDisplayCutout": Overlay.setShowDisplayCutoutReturnValue; "Overlay.setShowIsolatedElements": Overlay.setShowIsolatedElementsReturnValue; "Overlay.setShowWindowControlsOverlay": Overlay.setShowWindowControlsOverlayReturnValue; "PWA.getOsAppState": PWA.getOsAppStateReturnValue; diff --git a/tests/library/browsercontext-cookies-third-party.spec.ts b/tests/library/browsercontext-cookies-third-party.spec.ts index 59800a9f91db3..7cc5f267b4c28 100644 --- a/tests/library/browsercontext-cookies-third-party.spec.ts +++ b/tests/library/browsercontext-cookies-third-party.spec.ts @@ -31,7 +31,7 @@ type TestUrls = { set_origin1_origin2_origin1: string; }; -const test = contextTest.extend<{ urls: TestUrls }>({ +const test = contextTest.extend<{ urls: TestUrls, webkitPartitions: boolean }>({ urls: async ({ httpsServer }, run) => { const origin1 = httpsServer.PREFIX; const origin2 = httpsServer.CROSS_PROCESS_PREFIX; @@ -47,6 +47,10 @@ const test = contextTest.extend<{ urls: TestUrls }>({ set_origin1_origin2_origin1: origin1 + '/nested-frame-set-cookie.html', }); }, + + webkitPartitions: async ({ browserName, isMac, macVersion }, run) => { + await run(browserName === 'webkit' && isMac && macVersion >= 26); + }, }); test.use({ @@ -196,7 +200,7 @@ test(`save/load third party non-partitioned cookies`, async ({ page, browserName }); }); -async function runPartitionedTest(page: Page, httpsServer: TestServer, browserName: string, isMac: boolean, isBidi: boolean, urls: TestUrls) { +async function runPartitionedTest(page: Page, httpsServer: TestServer, browserName: string, isMac: boolean, webkitPartitions: boolean, isBidi: boolean, urls: TestUrls) { addCommonCookieHandlers(httpsServer, urls); httpsServer.setRoute('/set-cookie.html', (req, res) => { res.setHeader('Set-Cookie', [ @@ -243,22 +247,22 @@ async function runPartitionedTest(page: Page, httpsServer: TestServer, browserNa await page.goto(urls.set_origin2_origin1); await page.goto(urls.read_origin2_origin1); if (browserName === 'webkit') - await expect(frameBody).toHaveText('Received cookie: undefined'); + await expect(frameBody).toHaveText(webkitPartitions ? 'Received cookie: frame-partitioned=value' : 'Received cookie: undefined'); else if (browserName === 'firefox' && isBidi) await expect(frameBody).toHaveText('Received cookie: frame-non-partitioned=value; frame-partitioned=value'); else await expect(frameBody).toHaveText('Received cookie: frame-non-partitioned=value; frame-partitioned=value; top-level-non-partitioned=value'); } -test(`third party 'Partitioned;' cookies`, async ({ page, browserName, httpsServer, isMac, isBidi, urls }) => { - await runPartitionedTest(page, httpsServer, browserName, isMac, isBidi, urls); +test(`third party 'Partitioned;' cookies`, async ({ page, browserName, httpsServer, isMac, webkitPartitions, isBidi, urls }) => { + await runPartitionedTest(page, httpsServer, browserName, isMac, webkitPartitions, isBidi, urls); }); -test(`save/load third party 'Partitioned;' cookies`, async ({ page, browserName, httpsServer, isMac, isBidi, browser, urls }) => { +test(`save/load third party 'Partitioned;' cookies`, async ({ page, browserName, httpsServer, isMac, webkitPartitions, isBidi, browser, urls }) => { test.fixme(browserName === 'firefox' && !isBidi, 'Firefox cookie partitioning is disabled in Firefox(Juggler).'); test.fixme(browserName === 'webkit' && !isMac, 'Linux and Windows WebKit builds do not partition third-party cookies at all.'); - await runPartitionedTest(page, httpsServer, browserName, isMac, isBidi, urls); + await runPartitionedTest(page, httpsServer, browserName, isMac, webkitPartitions, isBidi, urls); async function checkCookies(page: Page) { { @@ -274,7 +278,7 @@ test(`save/load third party 'Partitioned;' cookies`, async ({ page, browserName, await page.goto(urls.read_origin2_origin1); const frameBody = page.locator('iframe').contentFrame().locator('body'); const expectedThirdParty = browserName === 'webkit' ? - 'Received cookie: undefined' : + (webkitPartitions ? 'Received cookie: frame-partitioned=value' : 'Received cookie: undefined') : 'Received cookie: frame-non-partitioned=value; frame-partitioned=value; top-level-non-partitioned=value'; await expect.soft(frameBody).toHaveText(expectedThirdParty, { timeout: 1000 }); } @@ -291,17 +295,18 @@ test(`save/load third party 'Partitioned;' cookies`, async ({ page, browserName, await checkCookies(page); function checkStorageCookies(cookies: Cookie[]) { - const expectedTopLevelPartitioned = browserName === 'webkit' && isMac ? - undefined : - 'https://localhost'; - expectPartitionKey(cookies, 'top-level-partitioned', expectedTopLevelPartitioned); + const webkitNoPartition = browserName === 'webkit' && !webkitPartitions; + expectPartitionKey(cookies, 'top-level-partitioned', webkitNoPartition ? undefined : 'https://localhost'); expectPartitionKey(cookies, 'top-level-non-partitioned', undefined); - if (browserName === 'webkit' && isMac) { + if (webkitNoPartition) { expect(cookies.find(cookie => cookie.name === 'frame-partitioned')).toBeUndefined(); expect(cookies.find(cookie => cookie.name === 'frame-non-partitioned')).toBeUndefined(); } else { expectPartitionKey(cookies, 'frame-partitioned', 'https://127.0.0.1'); - expectPartitionKey(cookies, 'frame-non-partitioned', undefined); + if (browserName === 'webkit') + expect(cookies.find(cookie => cookie.name === 'frame-non-partitioned')).toBeUndefined(); + else + expectPartitionKey(cookies, 'frame-non-partitioned', undefined); } } checkStorageCookies(await page.context().cookies()); @@ -323,7 +328,7 @@ test(`save/load third party 'Partitioned;' cookies`, async ({ page, browserName, }); }); -test(`add 'Partitioned;' cookie via API`, async ({ page, context, browserName, httpsServer, isMac, isBidi, urls }) => { +test(`add 'Partitioned;' cookie via API`, async ({ page, context, browserName, httpsServer, isMac, webkitPartitions, isBidi, urls }) => { addCommonCookieHandlers(httpsServer, urls); await context.addCookies([ @@ -377,7 +382,7 @@ test(`add 'Partitioned;' cookie via API`, async ({ page, context, browserName, h { // Check top-level cookie first. await page.goto(urls.read_origin1); - const expectedTopLevel = browserName === 'webkit' || (browserName === 'firefox' && !isBidi) ? + const expectedTopLevel = (browserName === 'webkit' && !webkitPartitions) || (browserName === 'firefox' && !isBidi) ? 'Received cookie: frame-non-partitioned=value; frame-partitioned=value; top-level-non-partitioned=value; top-level-partitioned=value' : 'Received cookie: frame-non-partitioned=value; top-level-non-partitioned=value; top-level-partitioned=value'; expect.soft(await page.locator('body').textContent()).toBe(expectedTopLevel); @@ -387,10 +392,10 @@ test(`add 'Partitioned;' cookie via API`, async ({ page, context, browserName, h await page.goto(urls.read_origin2_origin1); const frameBody = page.locator('iframe').contentFrame().locator('body'); let expectedThirdParty = 'Received cookie: '; - if (browserName === 'webkit' && isMac) - expectedThirdParty += 'undefined'; - else if (browserName === 'firefox' && isBidi) + if (webkitPartitions || (browserName === 'firefox' && isBidi)) expectedThirdParty += 'frame-partitioned=value'; + else if (browserName === 'webkit' && isMac) + expectedThirdParty += 'undefined'; else if (browserName === 'chromium') expectedThirdParty += 'frame-non-partitioned=value; frame-partitioned=value; top-level-non-partitioned=value'; else @@ -402,7 +407,9 @@ test(`add 'Partitioned;' cookie via API`, async ({ page, context, browserName, h await page.goto(urls.read_origin1_origin2_origin1); // read-origin1-origin2-origin1.html const frameBody = page.locator('iframe').contentFrame().locator('iframe').contentFrame().locator('body'); let expectedThirdParty = 'Received cookie: '; - if (browserName === 'webkit' || (browserName === 'firefox' && !isBidi)) + if (webkitPartitions) + expectedThirdParty += 'frame-non-partitioned=value; top-level-non-partitioned=value; top-level-partitioned=value'; + else if (browserName === 'webkit' || (browserName === 'firefox' && !isBidi)) expectedThirdParty += 'frame-non-partitioned=value; frame-partitioned=value; top-level-non-partitioned=value; top-level-partitioned=value'; else if (browserName === 'firefox' && isBidi) expectedThirdParty += 'undefined'; diff --git a/tests/mcp/annotate.spec.ts b/tests/mcp/annotate.spec.ts index db69c71b430b6..b63eb702ab090 100644 --- a/tests/mcp/annotate.spec.ts +++ b/tests/mcp/annotate.spec.ts @@ -228,6 +228,7 @@ test('user-initiated annotate downloads zip with feedback.md', async ({ connectT }); test('should capture annotations via show --annotate', async ({ connectToDashboard, cli, server }) => { + test.slow(); await cli('open', server.EMPTY_PAGE); const bindTitle = `--playwright-internal--${crypto.randomUUID()}`; await cli('show', { bindTitle }); diff --git a/tests/mcp/cli-config.spec.ts b/tests/mcp/cli-config.spec.ts index 3adf9e68b01ab..1fb3869d99171 100644 --- a/tests/mcp/cli-config.spec.ts +++ b/tests/mcp/cli-config.spec.ts @@ -43,6 +43,30 @@ test('context options', async ({ cli, server }, testInfo) => { expect(output).toContain('800x600'); }); +test('--mobile emulates a mobile viewport', async ({ cli, server, mcpBrowser }) => { + test.skip(mcpBrowser !== 'chrome', 'Default --mobile device (Pixel 10) is Chromium; family mapping is covered by config-resolve tests.'); + server.setContent('/', ` + + + `, 'text/html'); + await cli('open', '--mobile', server.PREFIX); + const { output } = await cli('eval', 'window.innerWidth'); + // Pixel 10 device width. + expect(output).toContain('360'); +}); + +test('--device emulates the given device', async ({ cli, server, mcpBrowser }) => { + test.skip(mcpBrowser !== 'chrome', 'Device viewport is engine-independent; asserting one browser is enough.'); + server.setContent('/', ` + + + `, 'text/html'); + await cli('open', '--device=iPhone 15', server.PREFIX); + const { output } = await cli('eval', 'window.innerWidth'); + // iPhone 15 device width. + expect(output).toContain('393'); +}); + test('config-print prints merged config', async ({ cli }) => { await cli('open'); const { output } = await cli('config-print'); diff --git a/tests/mcp/cli-killall.spec.ts b/tests/mcp/cli-killall.spec.ts index 2fb812ab0fe3a..43b17bd152344 100644 --- a/tests/mcp/cli-killall.spec.ts +++ b/tests/mcp/cli-killall.spec.ts @@ -44,11 +44,16 @@ test('kill-all kills filtered dashboard pid', async ({ cli }) => { expect(dashboardPid).toBeDefined(); await expect.poll(() => isAlive(dashboardPid)).toBe(true); - const { output } = await cli('kill-all', { - env: { PWTEST_KILL_ALL_PID_FILTER_FOR_TEST: String(dashboardPid) }, - }); - expect(output).toContain(`Killed daemon process ${dashboardPid}`); - expect(output).toContain('Killed 1 daemon process.'); + // kill-all discovers daemons via `execSync('ps auxww')` and treats any failure + // (including a swallowed spawn error under load) as "no processes found", so a + // single call can miss a live daemon. Retry until it observes and kills it. + await expect(async () => { + const { output } = await cli('kill-all', { + env: { PWTEST_KILL_ALL_PID_FILTER_FOR_TEST: String(dashboardPid) }, + }); + expect(output).toContain(`Killed daemon process ${dashboardPid}`); + expect(output).toContain('Killed 1 daemon process.'); + }).toPass(); await expect.poll(() => isAlive(dashboardPid)).toBe(false); }); diff --git a/tests/mcp/config-resolve.spec.ts b/tests/mcp/config-resolve.spec.ts index d961bfec20465..b0723553727c3 100644 --- a/tests/mcp/config-resolve.spec.ts +++ b/tests/mcp/config-resolve.spec.ts @@ -107,6 +107,11 @@ test.describe('browserName and channel', () => { // --------------------------------------------------------------------------- test.describe('sandbox', () => { + test('chromium sandbox enabled by default', async () => { + const config = await resolveCLIConfigForMCP({}, emptyEnv); + expect(config.browser.launchOptions.chromiumSandbox).toBe(true); + }); + test('chromium sandbox enabled for chrome channel', async () => { const config = await resolveCLIConfigForMCP({ browser: 'chrome' }, emptyEnv); expect(config.browser.launchOptions.chromiumSandbox).toBe(true); @@ -189,6 +194,48 @@ test.describe('viewport', () => { }); }); +test.describe('mobile', () => { + test('--mobile defaults to a Chromium mobile device', async () => { + const config = await resolveCLIConfigForMCP({ mobile: true }, emptyEnv); + expect(config.browser.contextOptions.isMobile).toBe(true); + expect(config.browser.contextOptions.userAgent).toContain('Pixel 10'); + }); + + test('--mobile with a Chromium channel picks a Chromium mobile device', async () => { + const config = await resolveCLIConfigForMCP({ mobile: true, browser: 'msedge' }, emptyEnv); + expect(config.browser.contextOptions.isMobile).toBe(true); + expect(config.browser.contextOptions.userAgent).toContain('Pixel 10'); + }); + + test('--mobile with WebKit picks a WebKit mobile device', async () => { + const config = await resolveCLIConfigForMCP({ mobile: true, browser: 'webkit' }, emptyEnv); + expect(config.browser.contextOptions.isMobile).toBe(true); + expect(config.browser.contextOptions.userAgent).toContain('iPhone'); + }); + + test('explicit viewport still wins over --mobile', async () => { + const config = await resolveCLIConfigForMCP({ mobile: true, viewportSize: { width: 800, height: 600 } }, emptyEnv); + expect(config.browser.contextOptions.isMobile).toBe(true); + expect(config.browser.contextOptions.viewport).toEqual({ width: 800, height: 600 }); + }); + + test('--mobile via env var', async () => { + const config = await resolveCLIConfigForMCP({}, { PLAYWRIGHT_MCP_MOBILE: '1' }); + expect(config.browser.contextOptions.isMobile).toBe(true); + expect(config.browser.contextOptions.userAgent).toContain('Pixel 10'); + }); + + test('--mobile is rejected with Firefox', async () => { + await expect(resolveCLIConfigForMCP({ mobile: true, browser: 'firefox' }, emptyEnv)) + .rejects.toThrow('--mobile is not supported with the Firefox browser.'); + }); + + test('--mobile cannot be combined with --device', async () => { + await expect(resolveCLIConfigForMCP({ mobile: true, device: 'iPhone 15' }, emptyEnv)) + .rejects.toThrow('Cannot use --mobile together with --device'); + }); +}); + // --------------------------------------------------------------------------- // Shared behavior — merge order and config file // --------------------------------------------------------------------------- diff --git a/tests/mcp/device.spec.ts b/tests/mcp/device.spec.ts index 696834149269a..476b85041a6fb 100644 --- a/tests/mcp/device.spec.ts +++ b/tests/mcp/device.spec.ts @@ -16,6 +16,16 @@ import { test, expect } from './fixtures'; +const viewportProbe = ` + + + + + +`; + test('--device should work', async ({ startClient, server }) => { const { client } = await startClient({ args: ['--device', 'iPhone 15'], @@ -23,15 +33,7 @@ test('--device should work', async ({ startClient, server }) => { server.setRoute('/', (req, res) => { res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(` - - - - - - `); + res.end(viewportProbe); }); expect(await client.callTool({ @@ -43,3 +45,28 @@ test('--device should work', async ({ startClient, server }) => { snapshot: expect.stringContaining(`393x659`), }); }); + +test('--mobile emulates a mobile viewport', async ({ startClient, server, mcpBrowser }) => { + test.skip(mcpBrowser === 'firefox', '--mobile is not supported with Firefox.'); + + const { client } = await startClient({ + args: ['--mobile'], + }); + + server.setRoute('/', (req, res) => { + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(viewportProbe); + }); + + // Pixel 10 for Chromium, iPhone 17 for WebKit — both are narrow mobile + // viewports, so assert the width rather than an exact model dimension. + const width = mcpBrowser === 'webkit' ? '402x' : '360x'; + expect(await client.callTool({ + name: 'browser_navigate', + arguments: { + url: server.PREFIX, + }, + })).toHaveResponse({ + snapshot: expect.stringContaining(width), + }); +}); diff --git a/tests/mcp/find.spec.ts b/tests/mcp/find.spec.ts index 4455cdbcd20f0..1b4ee9f93bc50 100644 --- a/tests/mcp/find.spec.ts +++ b/tests/mcp/find.spec.ts @@ -46,6 +46,92 @@ test('browser_find by text', async ({ client, server }) => { }); }); +const nestedPage = ` +
+
+ +
+
+`; + +test('browser_find shows the path from the root to the match', async ({ client, server }) => { + server.setContent('/', nestedPage, 'text/html'); + await client.callTool({ name: 'browser_navigate', arguments: { url: server.PREFIX } }); + + const response = await client.callTool({ + name: 'browser_find', + arguments: { text: 'Deep Target Link' }, + }); + + // The ancestor path is prepended even though it is far above the 3-line + // context window, so the matched node is shown in its place in the tree. The + // jump from the path down to the context window is implied, so it is not + // marked with an ellipsis. + expect(response).toHaveResponse({ + result: expect.stringContaining(`Found 1 match for "Deep Target Link": + +- main [ref=e2]: + - region "Sidebar" [ref=e3]: + - navigation "Primary" [ref=e4]: + - list [ref=e5]: + - listitem [ref=e14]:`), + }); + expect(response).toHaveResponse({ + result: expect.stringContaining(` - listitem [ref=e16]: + - link "Deep Target Link" [ref=e17]`), + }); + // The preceding sibling still appears as surrounding context. + expect(response).toHaveResponse({ + result: expect.stringContaining('Careers'), + }); +}); + +const toolbarPage = ` +
+
+ + + + +
+
+ +
+
+`; + +test('browser_find marks gaps within off-path context with an ellipsis', async ({ client, server }) => { + server.setContent('/', toolbarPage, 'text/html'); + await client.callTool({ name: 'browser_navigate', arguments: { url: server.PREFIX } }); + + // The match sits under "Content"; the "Toolbar" group is off-path context + // whose truncated buttons are marked with an ellipsis, while the path down to + // the match stays unmarked. + expect(await client.callTool({ + name: 'browser_find', + arguments: { text: 'Target Button' }, + })).toHaveResponse({ + result: expect.stringContaining(`Found 1 match for "Target Button": + +- main [ref=e2]: + - group "Toolbar" [ref=e3]: + ... + - button "Three" [ref=e6] + - button "Four" [ref=e7] + - group "Content" [ref=e8]: + - button "Target Button" [ref=e9]`), + }); +}); + test('browser_find is case-insensitive for text', async ({ client, server }) => { server.setContent('/', listPage, 'text/html'); await client.callTool({ name: 'browser_navigate', arguments: { url: server.PREFIX } });