From d14fa5fb33977fc1efba78231888ca5cf942b2fe Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 11:44:41 -0700 Subject: [PATCH 1/4] feat(webkit): roll to r2291 (#40813) --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 3c758f2d1e125..ed8a2dc6bc147 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -45,7 +45,7 @@ }, { "name": "webkit", - "revision": "2290", + "revision": "2291", "installByDefault": true, "revisionOverrides": { "mac14": "2251", From 2094c0006eb7837cb1d53507430cfd92f2b28b73 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 13 May 2026 21:49:41 +0100 Subject: [PATCH 2/4] chore(docs): use * for object type alias markers (#40821) --- docs/src/api/class-apiresponse.md | 4 ++-- docs/src/api/class-browser.md | 3 ++- docs/src/api/class-browsercontext.md | 12 +++++----- docs/src/api/class-debugger.md | 6 ++--- docs/src/api/class-elementhandle.md | 4 ++-- docs/src/api/class-formdata.md | 8 +++---- docs/src/api/class-locator.md | 4 ++-- docs/src/api/class-page.md | 10 ++++---- docs/src/api/class-request.md | 12 +++++----- docs/src/api/class-response.md | 12 +++++----- docs/src/api/class-screencast.md | 4 ++-- docs/src/api/class-tracing.md | 2 +- docs/src/api/class-weberror.md | 2 +- docs/src/api/params.md | 34 +++++++++++++--------------- 14 files changed, 58 insertions(+), 59 deletions(-) diff --git a/docs/src/api/class-apiresponse.md b/docs/src/api/class-apiresponse.md index 8a8b4639261f5..bbc70f5df451d 100644 --- a/docs/src/api/class-apiresponse.md +++ b/docs/src/api/class-apiresponse.md @@ -58,8 +58,8 @@ An object with all the response HTTP headers associated with this response. ## method: APIResponse.headersArray * since: v1.16 - returns: <[Array]<[Object]>> - - alias-csharp: Header - - alias-java: HttpHeader + * alias: HttpHeader + * alias-csharp: Header - `name` <[string]> Name of the header. - `value` <[string]> Value of the header. diff --git a/docs/src/api/class-browser.md b/docs/src/api/class-browser.md index b676c64dc04ac..2c07e98c57b2b 100644 --- a/docs/src/api/class-browser.md +++ b/docs/src/api/class-browser.md @@ -304,7 +304,8 @@ testing frameworks should explicitly create [`method: Browser.newContext`] follo ## async method: Browser.bind * since: v1.59 - returns: <[Object]> - - alias: BindResult + * alias: BindResult + * alias-csharp: BrowserBindResult - `endpoint` <[string]> Binds the browser to a named pipe or web socket, making it available for other clients to connect to. diff --git a/docs/src/api/class-browsercontext.md b/docs/src/api/class-browsercontext.md index e839f13a017d8..a00901d0c5e9f 100644 --- a/docs/src/api/class-browsercontext.md +++ b/docs/src/api/class-browsercontext.md @@ -367,7 +367,7 @@ await context.AddCookiesAsync(new[] { cookie1, cookie2 }); ### param: BrowserContext.addCookies.cookies * since: v1.8 - `cookies` <[Array]<[Object]>> - - alias-java: Cookie + * alias-java: Cookie - `name` <[string]> - `value` <[string]> - `url` ?<[string]> Either `url` or both `domain` and `path` are required. Optional. @@ -607,8 +607,8 @@ The default browser context cannot be closed. ## async method: BrowserContext.cookies * since: v1.8 - returns: <[Array]<[Object]>> - - alias-csharp: BrowserContextCookiesResult - - alias-java: Cookie + * alias: Cookie + * alias-csharp: BrowserContextCookiesResult - `name` <[string]> - `value` <[string]> - `domain` <[string]> @@ -769,7 +769,7 @@ Name of the function on the window object. ### param: BrowserContext.exposeBinding.callback * since: v1.8 - `callback` <[function]> - - alias-java: BindingCallback + * alias: BindingCallback Callback function that will be called in the Playwright's context. @@ -961,7 +961,7 @@ Name of the function on the window object. ### param: BrowserContext.exposeFunction.callback * since: v1.8 - `callback` <[function]> - - alias-java: FunctionCallback + * alias: FunctionCallback Callback function that will be called in the Playwright's context. @@ -1498,7 +1498,7 @@ its geolocation. ### param: BrowserContext.setGeolocation.geolocation * since: v1.8 - `geolocation` <[null]|[Object]> - - alias-java: Geolocation + * alias: Geolocation - `latitude` <[float]> Latitude between -90 and 90. - `longitude` <[float]> Longitude between -180 and 180. - `accuracy` ?<[float]> Non-negative accuracy value. Defaults to `0`. diff --git a/docs/src/api/class-debugger.md b/docs/src/api/class-debugger.md index 3382cd618977c..8f59f7cc688e7 100644 --- a/docs/src/api/class-debugger.md +++ b/docs/src/api/class-debugger.md @@ -12,9 +12,9 @@ Emitted when the debugger pauses or resumes. ## method: Debugger.pausedDetails * since: v1.59 - returns: <[null]|[Object]> - - alias: DebuggerPausedDetails + * alias: DebuggerPausedDetails - `location` <[Object]> - - alias-java: Location + * alias: Location - `file` <[string]> - `line` ?<[int]> - `column` ?<[int]> @@ -49,7 +49,7 @@ Resumes script execution and pauses when an action originates from the given sou ### param: Debugger.runTo.location * since: v1.59 - `location` <[Object]> - - alias-java: Location + * alias: Location - `file` <[string]> - `line` ?<[int]> - `column` ?<[int]> diff --git a/docs/src/api/class-elementhandle.md b/docs/src/api/class-elementhandle.md index 7032f0ca15d62..47f622a536655 100644 --- a/docs/src/api/class-elementhandle.md +++ b/docs/src/api/class-elementhandle.md @@ -109,8 +109,8 @@ await locator.ClickAsync(); ## async method: ElementHandle.boundingBox * since: v1.8 - returns: <[null]|[Object]> - - alias-csharp: ElementHandleBoundingBoxResult - - alias-java: BoundingBox + * alias: BoundingBox + * alias-csharp: ElementHandleBoundingBoxResult - `x` <[float]> the x coordinate of the element in pixels. - `y` <[float]> the y coordinate of the element in pixels. - `width` <[float]> the width of the element in pixels. diff --git a/docs/src/api/class-formdata.md b/docs/src/api/class-formdata.md index 5781fb6707fee..ea714ef79ef2b 100644 --- a/docs/src/api/class-formdata.md +++ b/docs/src/api/class-formdata.md @@ -115,7 +115,7 @@ Field name. ### param: FormData.append.value * since: v1.44 - `value` <[string]|[boolean]|[int]|[path]|[Object]> - - alias: FilePayload + * alias: FilePayload - `name` <[string]> File name - `mimeType` <[string]> File type - `buffer` <[Buffer]> File content @@ -126,7 +126,7 @@ Field value. * since: v1.44 * langs: csharp - `value` <[string]|[boolean]|[int]|[Object]> - - alias-csharp: FilePayload + * alias: FilePayload - `name` <[string]> File name - `mimeType` <[string]> File type - `buffer` <[Buffer]> File content @@ -216,7 +216,7 @@ Field name. ### param: FormData.set.value * since: v1.18 - `value` <[string]|[boolean]|[int]|[path]|[Object]> - - alias: FilePayload + * alias: FilePayload - `name` <[string]> File name - `mimeType` <[string]> File type - `buffer` <[Buffer]> File content @@ -227,7 +227,7 @@ Field value. * since: v1.18 * langs: csharp - `value` <[string]|[boolean]|[int]|[Object]> - - alias-csharp: FilePayload + * alias: FilePayload - `name` <[string]> File name - `mimeType` <[string]> File type - `buffer` <[Buffer]> File content diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index d3a90c0844214..59a98671aec5e 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -251,8 +251,8 @@ Calls [blur](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur) ## async method: Locator.boundingBox * since: v1.14 - returns: <[null]|[Object]> - - alias-csharp: LocatorBoundingBoxResult - - alias-java: BoundingBox + * alias: BoundingBox + * alias-csharp: LocatorBoundingBoxResult - `x` <[float]> the x coordinate of the element in pixels. - `y` <[float]> the y coordinate of the element in pixels. - `width` <[float]> the width of the element in pixels. diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index 6f9a43f19a2e2..cb945d673898c 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -1829,7 +1829,7 @@ Name of the function on the window object. ### param: Page.exposeBinding.callback * since: v1.8 - `callback` <[function]> - - alias-java: BindingCallback + * alias: BindingCallback Callback function that will be called in the Playwright's context. @@ -2023,7 +2023,7 @@ Name of the function on the window object ### param: Page.exposeFunction.callback * since: v1.8 - `callback` <[function]> - - alias-java: FunctionCallback + * alias: FunctionCallback Callback function which will be called in Playwright's context. @@ -3006,7 +3006,7 @@ Paper margins, defaults to none. * since: v1.8 * langs: csharp, java - `margin` <[Object]> - - alias-java: Margin + * alias-java: Margin - `top` ?<[string]> Top margin, accepts values labeled with units. Defaults to `0`. - `right` ?<[string]> Right margin, accepts values labeled with units. Defaults to `0`. - `bottom` ?<[string]> Bottom margin, accepts values labeled with units. Defaults to `0`. @@ -4471,8 +4471,8 @@ Video object associated with this page. Can be used to access the video file whe ## method: Page.viewportSize * since: v1.8 - returns: <[null]|[Object]> - - alias-csharp: PageViewportSizeResult - - alias-java: ViewportSize + * alias: ViewportSize + * alias-csharp: PageViewportSizeResult - `width` <[int]> page width in pixels. - `height` <[int]> page height in pixels. diff --git a/docs/src/api/class-request.md b/docs/src/api/class-request.md index 5f3bd280d68ea..a37a5d5953a6c 100644 --- a/docs/src/api/class-request.md +++ b/docs/src/api/class-request.md @@ -114,8 +114,8 @@ You can use [`method: Request.allHeaders`] for complete list of headers that inc ## async method: Request.headersArray * since: v1.15 - returns: <[Array]<[Object]>> - - alias-csharp: Header - - alias-java: HttpHeader + * alias: HttpHeader + * alias-csharp: Header - `name` <[string]> Name of the header. - `value` <[string]> Value of the header. @@ -313,8 +313,8 @@ Requests originated in a Service Worker do not have a [`method: Request.frame`] ## async method: Request.sizes * since: v1.15 - returns: <[Object]> - - alias-csharp: RequestSizesResult - - alias-java: Sizes + * alias-csharp: RequestSizesResult + * alias-java: Sizes - `requestBodySize` <[int]> Size of the request body (POST data payload) in bytes. Set to 0 if there was no body. - `requestHeadersSize` <[int]> Total number of bytes from the start of the HTTP request message until (and including) the double CRLF before the body. - `responseBodySize` <[int]> Size of the received response body (encoded) in bytes. @@ -325,8 +325,8 @@ Returns resource size information for given request. ## method: Request.timing * since: v1.8 - returns: <[Object]> - - alias-csharp: RequestTimingResult - - alias-java: Timing + * alias-csharp: RequestTimingResult + * alias-java: Timing - `startTime` <[float]> Request start time in milliseconds elapsed since January 1, 1970 00:00:00 UTC - `domainLookupStart` <[float]> Time immediately before the browser starts the domain name lookup for the resource. The value is given in milliseconds relative to `startTime`, -1 if not available. diff --git a/docs/src/api/class-response.md b/docs/src/api/class-response.md index a89d0bc9471ec..6584ee3417b6f 100644 --- a/docs/src/api/class-response.md +++ b/docs/src/api/class-response.md @@ -49,8 +49,8 @@ You can use [`method: Response.allHeaders`] for complete list of headers that in ## async method: Response.headersArray * since: v1.15 - returns: <[Array]<[Object]>> - - alias-csharp: Header - - alias-java: HttpHeader + * alias: HttpHeader + * alias-csharp: Header - `name` <[string]> Name of the header. - `value` <[string]> Value of the header. @@ -121,8 +121,8 @@ Returns the matching [Request] object. ## async method: Response.securityDetails * since: v1.13 - returns: <[null]|[Object]> - - alias-csharp: ResponseSecurityDetailsResult - - alias-java: SecurityDetails + * alias: SecurityDetails + * alias-csharp: ResponseSecurityDetailsResult - `issuer` ?<[string]> Common Name component of the Issuer field. from the certificate. This should only be used for informational purposes. Optional. - `protocol` ?<[string]> The specific TLS protocol used. (e.g. `TLS 1.3`). Optional. @@ -138,8 +138,8 @@ Returns SSL and other security information. ## async method: Response.serverAddr * since: v1.13 - returns: <[null]|[Object]> - - alias-csharp: ResponseServerAddrResult - - alias-java: ServerAddr + * alias-csharp: ResponseServerAddrResult + * alias-java: ServerAddr - `ipAddress` <[string]> IPv4 or IPV6 address of the server. - `port` <[int]> diff --git a/docs/src/api/class-screencast.md b/docs/src/api/class-screencast.md index 4d1ba1843ea8e..b5d2c2757dddf 100644 --- a/docs/src/api/class-screencast.md +++ b/docs/src/api/class-screencast.md @@ -34,7 +34,7 @@ await page.screencast.stop(); ### option: Screencast.start.onFrame * since: v1.59 - `onFrame` <[function]\([Object]\): [Promise]> - - alias: ScreencastFrame + * alias: ScreencastFrame - `data` <[Buffer]> JPEG-encoded frame data. - `viewportWidth` <[int]> Width of the page viewport at the time the frame was captured. - `viewportHeight` <[int]> Height of the page viewport at the time the frame was captured. @@ -57,7 +57,7 @@ The quality of the image, between 0-100. * since: v1.59 * langs: js - `size` ?<[Object]> - - alias-csharp: ScreencastSize + * alias-csharp: ScreencastSize - `width` <[int]> Max frame width in pixels. - `height` <[int]> Max frame height in pixels. diff --git a/docs/src/api/class-tracing.md b/docs/src/api/class-tracing.md index 1af0427bfd888..256ae513a5327 100644 --- a/docs/src/api/class-tracing.md +++ b/docs/src/api/class-tracing.md @@ -444,7 +444,7 @@ Group name shown in the trace viewer. ### option: Tracing.group.location * since: v1.49 - `location` ?<[Object]> - - alias-java: Location + * alias: Location - `file` <[string]> - `line` ?<[int]> - `column` ?<[int]> diff --git a/docs/src/api/class-weberror.md b/docs/src/api/class-weberror.md index 455450861996f..8f3c91a960765 100644 --- a/docs/src/api/class-weberror.md +++ b/docs/src/api/class-weberror.md @@ -69,7 +69,7 @@ Unhandled error that was thrown. ## method: WebError.location * since: v1.60 - returns: <[Object]> - - alias: WebErrorLocation + * alias: WebErrorLocation - `url` <[string]> URL of the resource. - `line` <[int]> 0-based line number in the resource. - `column` <[int]> 0-based column number in the resource. diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 350afc877b268..eae785f34737e 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -97,7 +97,7 @@ A selector to search for an element to drop onto. If there are multiple elements ## input-position - `position` <[Object]> - - alias-java: Position + * alias: Position - `x` <[float]> - `y` <[float]> @@ -128,16 +128,16 @@ Defaults to `left`. ## input-files - `files` <[path]|[Array]<[path]>|[Object]|[Array]<[Object]>> - - alias: FilePayload + * alias: FilePayload - `name` <[string]> File name - `mimeType` <[string]> File type - `buffer` <[Buffer]> File content ## drop-payload - `payload` <[Object]> - - alias: DropPayload + * alias: DropPayload - `files` ?<[path]|[Array]<[path]>|[Object]|[Array]<[Object]>> - - alias: FilePayload + * alias: FilePayload - `name` <[string]> File name - `mimeType` <[string]> File type - `buffer` <[Buffer]> File content @@ -169,7 +169,7 @@ When set, this method only performs the [actionability](../actionability.md) che ## input-source-position - `sourcePosition` <[Object]> - - alias-java: Position + * alias-java: Position - `x` <[float]> - `y` <[float]> @@ -177,7 +177,7 @@ Clicks on the source element at this point relative to the top-left corner of th ## input-target-position - `targetPosition` <[Object]> - - alias-java: Position + * alias-java: Position - `x` <[float]> - `y` <[float]> @@ -253,7 +253,7 @@ Dangerous option; use with care. Defaults to `false`. ## browser-option-proxy - `proxy` <[Object]> - - alias-java: Proxy + * alias: Proxy - `server` <[string]> Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example `http://myproxy.com:3128` or `socks5://myproxy.com:3128`. Short form `myproxy.com:3128` is considered an HTTP proxy. @@ -350,7 +350,7 @@ When using [`method: Page.goto`], [`method: Page.route`], [`method: Page.waitFor * langs: js, java - alias-java: viewportSize - `viewport` <[null]|[Object]> - - alias-java: ViewportSize + * alias: ViewportSize - `width` <[int]> page width in pixels. - `height` <[int]> page height in pixels. @@ -384,7 +384,7 @@ It makes the execution of the tests non-deterministic. - alias-java: screenSize - alias-csharp: screenSize - `screen` <[Object]> - - alias-java: ScreenSize + * alias: ScreenSize - `width` <[int]> page width in pixels. - `height` <[int]> page height in pixels. @@ -616,7 +616,7 @@ Does not enforce fixed viewport, allows resizing window in the headed mode. ## context-option-clientCertificates - `clientCertificates` <[Array]<[Object]>> - - alias-java: ClientCertificate + * alias: ClientCertificate - `origin` <[string]> Exact origin that the certificate is valid for. Origin includes `https` protocol, a hostname and optionally a port. - `certPath` ?<[path]> Path to the file with the certificate in PEM format. - `cert` ?<[Buffer]> Direct value of the certificate in PEM format. @@ -671,7 +671,7 @@ for a list of supported timezone IDs. Defaults to the system timezone. ## context-option-geolocation - `geolocation` <[Object]> - - alias-java: Geolocation + * alias: Geolocation - `latitude` <[float]> Latitude between -90 and 90. - `longitude` <[float]> Longitude between -180 and 180. - `accuracy` ?<[float]> Non-negative accuracy value. Defaults to `0`. @@ -699,7 +699,7 @@ Whether to emulate network being offline. Defaults to `false`. Learn more about ## context-option-httpcredentials - `httpCredentials` <[Object]> - - alias-java: HttpCredentials + * alias: HttpCredentials - `username` <[string]> - `password` <[string]> - `origin` ?<[string]> Restrain sending http credentials on specific origin (scheme://host:port). @@ -821,11 +821,9 @@ When set to `minimal`, only record information necessary for routing from HAR. T - `size` ?<[Object]> Optional dimensions of the recorded videos. If not specified the size will be equal to `viewport` scaled down to fit into 800x800. If `viewport` is not configured explicitly the video size defaults to 800x450. Actual picture of each page will be scaled down if necessary to fit the specified size. - - alias-csharp: RecordVideoSize - `width` <[int]> Video frame width. - `height` <[int]> Video frame height. - `showActions` ?<[Object]> If specified, enables visual annotations on interacted elements during video recording. - - alias-csharp: ShowActionsOptions - `duration` ?<[float]> How long each annotation is displayed in milliseconds. Defaults to `500`. - `position` ?<[AnnotatePosition]<"top-left"|"top"|"top-right"|"bottom-left"|"bottom"|"bottom-right">> Position of the action title overlay. Defaults to `"top-right"`. - `fontSize` ?<[int]> Font size of the action title in pixels. Defaults to `24`. @@ -845,7 +843,7 @@ not recorded. Make sure to call [`method: BrowserContext.close`] for videos to b * langs: csharp, java, python - alias-python: record_video_size - `recordVideoSize` <[Object]> - - alias-java: RecordVideoSize + * alias-java: RecordVideoSize - `width` <[int]> Video frame width. - `height` <[int]> Video frame height. @@ -855,7 +853,7 @@ Actual picture of each page will be scaled down if necessary to fit the specifie ## context-option-proxy - `proxy` <[Object]> - - alias-java: Proxy + * alias: Proxy - `server` <[string]> Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example `http://myproxy.com:3128` or `socks5://myproxy.com:3128`. Short form `myproxy.com:3128` is considered an HTTP proxy. - `bypass` ?<[string]> Optional comma-separated domains to bypass proxy, for example `".com, chromium.org, .domain.com"`. @@ -903,7 +901,7 @@ Specifies whether to wait for already running handlers and what to do if they th ## select-options-values * langs: java, js, csharp - `values` <[null]|[string]|[ElementHandle]|[Array]<[string]>|[Object]|[Array]<[ElementHandle]>|[Array]<[Object]>> - - alias-java: SelectOption + * alias-java: SelectOption - `value` ?<[string]> Matches by `option.value`. Optional. - `label` ?<[string]> Matches by `option.label`. Optional. - `index` ?<[int]> Matches by the index. Optional. @@ -1306,7 +1304,7 @@ When true, takes a screenshot of the full scrollable page, instead of the curren ## screenshot-option-clip - `clip` <[Object]> - - alias-java: Clip + * alias-java: Clip - `x` <[float]> x-coordinate of top-left corner of clip area - `y` <[float]> y-coordinate of top-left corner of clip area - `width` <[float]> width of clipping area From d2edf45eca26ea55cd447ec14047728aa4fb1c52 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 13 May 2026 21:49:56 +0100 Subject: [PATCH 3/4] devops: add android bot to tests_secondary (#40799) --- .github/workflows/tests_secondary.yml | 31 +++++++++++++++++++++++++++ tests/android/playwright.config.ts | 1 + tests/page/page-evaluate.spec.ts | 7 ++++-- tests/page/page-route.spec.ts | 4 +++- utils/avd_install.sh | 2 +- utils/avd_recreate.sh | 7 ++++++ utils/avd_start.sh | 4 ++++ 7 files changed, 52 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests_secondary.yml b/.github/workflows/tests_secondary.yml index e793291e12c4b..1953ee2166206 100644 --- a/.github/workflows/tests_secondary.yml +++ b/.github/workflows/tests_secondary.yml @@ -320,3 +320,34 @@ jobs: flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} env: PWTEST_CHANNEL: chromium + + test_android: + name: Android + environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} + runs-on: playwright-x64-ubuntu24-64-core + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '21' + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + - uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Create Android Emulator + run: utils/avd_recreate.sh + - name: Start Android Emulator + run: utils/avd_start.sh + - uses: ./.github/actions/run-test + with: + browsers-to-install: android + command: npm run atest + bot-name: "android" + flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }} + flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }} + flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} diff --git a/tests/android/playwright.config.ts b/tests/android/playwright.config.ts index db8e968b17e7b..06e8085c5dc9d 100644 --- a/tests/android/playwright.config.ts +++ b/tests/android/playwright.config.ts @@ -40,6 +40,7 @@ const config: Config { expect(result).toBe(42); }); -it('should work with large strings', async ({ page }) => { +it('should work with large strings', async ({ page, isAndroid }) => { + it.skip(isAndroid, 'string is too long :('); + const expected = 'x'.repeat(40000); expect(await page.evaluate(data => data, expected)).toBe(expected); }); -it('should work with large unicode strings', async ({ page, browserName, platform }) => { +it('should work with large unicode strings', async ({ page, browserName, platform, isAndroid }) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/16367' }); + it.skip(isAndroid, 'string is too long :('); const expected = '🎭'.repeat(10000); expect(await page.evaluate(data => data, expected)).toBe(expected); diff --git a/tests/page/page-route.spec.ts b/tests/page/page-route.spec.ts index c2f1f41f0f221..9f426e588c976 100644 --- a/tests/page/page-route.spec.ts +++ b/tests/page/page-route.spec.ts @@ -1036,8 +1036,10 @@ for (const method of ['fulfill', 'continue', 'fallback', 'abort'] as const) { }); } -it('should intercept when postData is more than 1MB', async ({ page, server }) => { +it('should intercept when postData is more than 1MB', async ({ page, server, isAndroid }) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/22753' }); + it.fixme(isAndroid); + await page.goto(server.EMPTY_PAGE); let interceptionCallback; const interceptionPromise = new Promise(x => interceptionCallback = x); diff --git a/utils/avd_install.sh b/utils/avd_install.sh index a0586d6936e4a..a7264ebaf7bc3 100755 --- a/utils/avd_install.sh +++ b/utils/avd_install.sh @@ -16,7 +16,7 @@ cd ${ANDROID_HOME}/cmdline-tools COMMAND_LINE_TOOLS_ZIP=${ANDROID_HOME}/commandlinetools.zip # https://developer.android.com/studio curl https://dl.google.com/android/repository/commandlinetools-mac-11076708_latest.zip -o ${COMMAND_LINE_TOOLS_ZIP} -unzip ${COMMAND_LINE_TOOLS_ZIP} +unzip ${COMMAND_LINE_TOOLS_ZIP} rm ${COMMAND_LINE_TOOLS_ZIP} mv cmdline-tools latest diff --git a/utils/avd_recreate.sh b/utils/avd_recreate.sh index 81c348490725b..1b1c0509427e1 100755 --- a/utils/avd_recreate.sh +++ b/utils/avd_recreate.sh @@ -6,6 +6,13 @@ if [[ -z "${ANDROID_HOME}" ]]; then export ANDROID_HOME="$PWD/.android-sdk" fi +# Pin AVD storage to ~/.android so cmdline-tools (avdmanager) and the +# emulator binary agree on the location. On Linux runners that set +# XDG_CONFIG_HOME (e.g. GitHub Actions), avdmanager otherwise writes to +# $XDG_CONFIG_HOME/.android/avd while `emulator` only searches $HOME/.android/avd. +export ANDROID_USER_HOME="$HOME/.android" +export ANDROID_AVD_HOME="$HOME/.android/avd" + ANDROID_ARCH="x86_64" # on MacOS M1 we need to use arm64 (can't emulate x86_64) diff --git a/utils/avd_start.sh b/utils/avd_start.sh index 77bf7e45ad4e2..7fa111fcbba2c 100755 --- a/utils/avd_start.sh +++ b/utils/avd_start.sh @@ -6,6 +6,10 @@ if [[ -z "${ANDROID_HOME}" ]]; then export ANDROID_HOME="$PWD/.android-sdk" fi +# Keep in sync with avd_recreate.sh — both tools must agree on the AVD path. +export ANDROID_USER_HOME="$HOME/.android" +export ANDROID_AVD_HOME="$HOME/.android/avd" + bash $PWD/utils/avd_stop.sh EMULATOR_EXTRA_ARGS=() From c0bcc849ff51daf1fe90bc1273c202077bdc5981 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 13 May 2026 15:24:12 -0700 Subject: [PATCH 4/4] fix(mcp): support remoteHeaders for remote browser endpoint (#40828) --- .../src/tools/mcp/browserFactory.ts | 5 +- .../playwright-core/src/tools/mcp/config.d.ts | 5 ++ .../playwright-core/src/tools/mcp/config.ts | 3 + .../playwright-core/src/tools/mcp/program.ts | 1 + tests/mcp/cli-remote.spec.ts | 38 ++++++++++++ tests/mcp/fixtures.ts | 9 +++ tests/mcp/remote-endpoint.spec.ts | 59 +++++++++++++++++++ 7 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 tests/mcp/cli-remote.spec.ts create mode 100644 tests/mcp/remote-endpoint.spec.ts diff --git a/packages/playwright-core/src/tools/mcp/browserFactory.ts b/packages/playwright-core/src/tools/mcp/browserFactory.ts index 62d1cf7795b17..ed4dc7ffe5fba 100644 --- a/packages/playwright-core/src/tools/mcp/browserFactory.ts +++ b/packages/playwright-core/src/tools/mcp/browserFactory.ts @@ -135,7 +135,10 @@ async function createRemoteBrowser(config: FullConfig): Promise const endpoint = config.browser.remoteEndpoint!; const playwrightObject = playwright as Playwright; // Use connectToBrowser instead of playwright[browserName].connect because we don't have browserName. - const browser = await connectToBrowser(playwrightObject, { endpoint }); + const browser = await connectToBrowser(playwrightObject, { + endpoint, + headers: config.browser.remoteHeaders, + }); browser._connectToBrowserType(playwrightObject[browser._browserName], {}, undefined); return { browser, browserInfo: browserInfo(browser, config), canBind: false, ownership: 'attached' }; } diff --git a/packages/playwright-core/src/tools/mcp/config.d.ts b/packages/playwright-core/src/tools/mcp/config.d.ts index 6756d9a731650..640084178c026 100644 --- a/packages/playwright-core/src/tools/mcp/config.d.ts +++ b/packages/playwright-core/src/tools/mcp/config.d.ts @@ -86,6 +86,11 @@ export type Config = { */ remoteEndpoint?: string; + /** + * Headers to send with the remote endpoint connect request. + */ + remoteHeaders?: Record; + /** * Paths to TypeScript files to add as initialization scripts for Playwright page. */ diff --git a/packages/playwright-core/src/tools/mcp/config.ts b/packages/playwright-core/src/tools/mcp/config.ts index cc85e23f6bfb4..0c33c134a1575 100644 --- a/packages/playwright-core/src/tools/mcp/config.ts +++ b/packages/playwright-core/src/tools/mcp/config.ts @@ -63,6 +63,7 @@ export type CLIOptions = { port?: number; proxyBypass?: string; proxyServer?: string; + remoteHeader?: Record; saveSession?: boolean; secrets?: Record; sharedBrowserContext?: boolean; @@ -332,6 +333,7 @@ function configFromCLIOptions(cliOptions: CLIOptions): Config & { configFile?: s initPage: cliOptions.initPage, initScript: cliOptions.initScript, remoteEndpoint: cliOptions.endpoint, + remoteHeaders: cliOptions.remoteHeader, }, extension: cliOptions.extension, server: { @@ -402,6 +404,7 @@ export function configFromEnv(env?: NodeJS.ProcessEnv): Config & { configFile?: options.port = numberParser(e.PLAYWRIGHT_MCP_PORT); options.proxyBypass = envToString(e.PLAYWRIGHT_MCP_PROXY_BYPASS); options.proxyServer = envToString(e.PLAYWRIGHT_MCP_PROXY_SERVER); + options.remoteHeader = headerParser(envToString(e.PLAYWRIGHT_MCP_REMOTE_HEADERS)); options.secrets = dotenvFileLoader(e.PLAYWRIGHT_MCP_SECRETS_FILE); options.storageState = envToString(e.PLAYWRIGHT_MCP_STORAGE_STATE); options.testIdAttribute = envToString(e.PLAYWRIGHT_MCP_TEST_ID_ATTRIBUTE); diff --git a/packages/playwright-core/src/tools/mcp/program.ts b/packages/playwright-core/src/tools/mcp/program.ts index 75c76cdf9275d..ceca2400ae984 100644 --- a/packages/playwright-core/src/tools/mcp/program.ts +++ b/packages/playwright-core/src/tools/mcp/program.ts @@ -63,6 +63,7 @@ export function decorateMCPCommand(command: Command) { .option('--port ', 'port to listen on for SSE transport.') .option('--proxy-bypass ', 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"') .option('--proxy-server ', 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"') + .option('--remote-header ', 'headers to send with the remote endpoint connect request, multiple can be specified.', headerParser) .option('--sandbox', 'enable the sandbox for all process types that are normally not sandboxed.') .option('--save-session', 'Whether to save the Playwright MCP session into the output directory.') .option('--secrets ', 'path to a file containing secrets in the dotenv format', dotenvFileLoader) diff --git a/tests/mcp/cli-remote.spec.ts b/tests/mcp/cli-remote.spec.ts new file mode 100644 index 0000000000000..40fb22b236b1c --- /dev/null +++ b/tests/mcp/cli-remote.spec.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from 'fs'; +import { test, expect } from './cli-fixtures'; + +test.skip(({ mcpBrowser }) => mcpBrowser !== 'chromium', 'Run only on the chromium project; the remote server connection is browser-agnostic.'); + +test('attach to run-server endpoint with remoteHeaders from config', async ({ cli, runServerEndpoint, server }, testInfo) => { + const configPath = testInfo.outputPath('config.json'); + await fs.promises.writeFile(configPath, JSON.stringify({ + browser: { + remoteEndpoint: runServerEndpoint, + remoteHeaders: { 'x-playwright-browser': 'chromium' }, + isolated: true, + }, + }, null, 2)); + + const { exitCode } = await cli('attach', runServerEndpoint, '-s=remote', `--config=${configPath}`); + expect(exitCode).toBe(0); + + await cli('-s=remote', 'goto', server.HELLO_WORLD); + const { inlineSnapshot } = await cli('-s=remote', 'snapshot'); + expect(inlineSnapshot).toContain('Hello, world!'); +}); diff --git a/tests/mcp/fixtures.ts b/tests/mcp/fixtures.ts index 9793b65205ade..98ffc26e144ce 100644 --- a/tests/mcp/fixtures.ts +++ b/tests/mcp/fixtures.ts @@ -26,6 +26,7 @@ import { TestServer } from '../config/testserver'; import { serverFixtures } from '../config/serverFixtures'; import { tools } from '../../packages/playwright-core/lib/coreBundle'; import { commonFixtures } from '../config/commonFixtures'; +import { RunServer } from '../config/remoteServer'; import { inheritAndCleanEnv } from '../config/utils'; import type { CommonFixtures, CommonWorkerFixtures } from '../config/commonFixtures'; @@ -67,6 +68,7 @@ type TestFixtures = { client: Client; startClient: StartClient; wsEndpoint: string; + runServerEndpoint: string; cdpServer: CDPServer; server: TestServer; httpsServer: TestServer; @@ -161,6 +163,13 @@ export const test = serverTest.extend { + const runServer = new RunServer(); + await runServer.start(childProcess); + await use(runServer.wsEndpoint()); + await runServer.close(); + }, + cdpServer: async ({ mcpBrowser }, use, testInfo) => { test.skip(!['chrome', 'msedge', 'chromium'].includes(mcpBrowser!), 'CDP is not supported for non-Chromium browsers'); diff --git a/tests/mcp/remote-endpoint.spec.ts b/tests/mcp/remote-endpoint.spec.ts new file mode 100644 index 0000000000000..6aba3e9df20a2 --- /dev/null +++ b/tests/mcp/remote-endpoint.spec.ts @@ -0,0 +1,59 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { test, expect } from './fixtures'; + +test.skip(({ mcpBrowser }) => mcpBrowser !== 'chromium', 'Run only on the chromium project; the remote server connection is browser-agnostic.'); + +test('remoteHeaders selects the browser on run-server endpoint', async ({ startClient, server, runServerEndpoint }) => { + const { client } = await startClient({ + config: { + browser: { + remoteEndpoint: runServerEndpoint, + remoteHeaders: { 'x-playwright-browser': 'chromium' }, + isolated: true, + }, + }, + }); + + const response = await client.callTool({ + name: 'browser_navigate', + arguments: { url: server.HELLO_WORLD }, + }); + expect(response).toHaveResponse({ + page: expect.stringContaining('Page Title: Title'), + }); +}); + +test('connect without remoteHeaders fails on run-server endpoint', async ({ startClient, server, runServerEndpoint }) => { + const { client } = await startClient({ + config: { + browser: { + remoteEndpoint: runServerEndpoint, + isolated: true, + }, + }, + }); + + const response = await client.callTool({ + name: 'browser_navigate', + arguments: { url: server.EMPTY_PAGE }, + }); + expect(response).toHaveResponse({ + isError: true, + error: expect.stringContaining(`reading 'launch'`), + }); +});