From 2975b7988ff943bb20753f1726df49c166845923 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 3 Jul 2026 13:24:52 +0100 Subject: [PATCH 1/5] devops: update actions/checkout and actions/setup-node (#41610) --- .github/actions/run-test/action.yml | 2 +- .github/workflows/tests_secondary.yml | 2 +- .github/workflows/tests_webview_simulator.yml | 2 +- docs/src/ci-intro.md | 10 +++---- docs/src/ci.md | 28 +++++++++---------- docs/src/test-sharding-js.md | 8 +++--- .../.github/workflows/copilot-setup-steps.yml | 4 +-- package-lock.json | 10 ------- packages/playwright-cli-stub/package.json | 8 ------ .../playwright-cli-stub.js | 23 --------------- .../src/agents/copilot-setup-steps.yml | 4 +-- 11 files changed, 30 insertions(+), 71 deletions(-) delete mode 100644 packages/playwright-cli-stub/package.json delete mode 100755 packages/playwright-cli-stub/playwright-cli-stub.js diff --git a/.github/actions/run-test/action.yml b/.github/actions/run-test/action.yml index 247a49ba3919e..624a3c2a29c93 100644 --- a/.github/actions/run-test/action.yml +++ b/.github/actions/run-test/action.yml @@ -39,7 +39,7 @@ inputs: runs: using: composite steps: - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: ${{ inputs.node-version }} - run: | diff --git a/.github/workflows/tests_secondary.yml b/.github/workflows/tests_secondary.yml index 5578693a677e3..7e04617c61066 100644 --- a/.github/workflows/tests_secondary.yml +++ b/.github/workflows/tests_secondary.yml @@ -197,7 +197,7 @@ jobs: 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 + - uses: actions/setup-node@v6 with: node-version: 22 - name: Create Android Emulator diff --git a/.github/workflows/tests_webview_simulator.yml b/.github/workflows/tests_webview_simulator.yml index 610ef3f755289..3f484c8ce67bb 100644 --- a/.github/workflows/tests_webview_simulator.yml +++ b/.github/workflows/tests_webview_simulator.yml @@ -26,7 +26,7 @@ jobs: shard: [1, 2, 3, 4] steps: - uses: actions/checkout@v6 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: 20 diff --git a/docs/src/ci-intro.md b/docs/src/ci-intro.md index b17c5948622b6..f39f11bef5ab4 100644 --- a/docs/src/ci-intro.md +++ b/docs/src/ci-intro.md @@ -48,8 +48,8 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 with: node-version: lts/* - name: Install dependencies @@ -94,7 +94,7 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 with: @@ -126,7 +126,7 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-java@v5 with: distribution: 'temurin' @@ -151,7 +151,7 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Setup dotnet uses: actions/setup-dotnet@v5 with: diff --git a/docs/src/ci.md b/docs/src/ci.md index 5b319041a6d1e..9c2c2cac03cd3 100644 --- a/docs/src/ci.md +++ b/docs/src/ci.md @@ -82,7 +82,7 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: lts/* @@ -117,7 +117,7 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 with: @@ -149,7 +149,7 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-java@v5 with: distribution: 'temurin' @@ -174,7 +174,7 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Setup dotnet uses: actions/setup-dotnet@v5 with: @@ -211,7 +211,7 @@ jobs: image: mcr.microsoft.com/playwright:v%%VERSION%%-noble options: --user 1001 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: lts/* @@ -236,7 +236,7 @@ jobs: image: mcr.microsoft.com/playwright/python:v%%VERSION%%-noble options: --user 1001 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 with: @@ -265,7 +265,7 @@ jobs: image: mcr.microsoft.com/playwright/java:v%%VERSION%%-noble options: --user 1001 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-java@v5 with: distribution: 'temurin' @@ -291,7 +291,7 @@ jobs: image: mcr.microsoft.com/playwright/dotnet:v%%VERSION%%-noble options: --user 1001 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Setup dotnet uses: actions/setup-dotnet@v5 with: @@ -316,7 +316,7 @@ jobs: runs-on: ubuntu-latest if: github.event.deployment_status.state == 'success' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: lts/* @@ -340,7 +340,7 @@ jobs: runs-on: ubuntu-latest if: github.event.deployment_status.state == 'success' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 uses: actions/setup-python@v6 with: python-version: '3.13' @@ -367,7 +367,7 @@ jobs: runs-on: ubuntu-latest if: github.event.deployment_status.state == 'success' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-java@v5 with: distribution: 'temurin' @@ -393,7 +393,7 @@ jobs: runs-on: ubuntu-latest if: github.event.deployment_status.state == 'success' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Setup dotnet uses: actions/setup-dotnet@v5 with: @@ -427,7 +427,7 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: # Force a non-shallow checkout, so that we can reference $GITHUB_BASE_REF. # See https://github.com/actions/checkout for more details. @@ -971,7 +971,7 @@ To run Playwright tests on Google Cloud Build, use our public Docker image ([see ```yml steps: - name: mcr.microsoft.com/playwright:v%%VERSION%%-noble - script: + script: ... env: - 'CI=true' diff --git a/docs/src/test-sharding-js.md b/docs/src/test-sharding-js.md index 2db0e4cb5520b..a83df1b1abda1 100644 --- a/docs/src/test-sharding-js.md +++ b/docs/src/test-sharding-js.md @@ -101,8 +101,8 @@ jobs: shardIndex: [1, 2, 3, 4] shardTotal: [4] steps: - - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 with: node-version: lts/* - name: Install dependencies @@ -134,8 +134,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 with: node-version: lts/* - name: Install dependencies diff --git a/examples/todomvc/.github/workflows/copilot-setup-steps.yml b/examples/todomvc/.github/workflows/copilot-setup-steps.yml index d9b5b711e6173..7e76a9d2a4dd4 100644 --- a/examples/todomvc/.github/workflows/copilot-setup-steps.yml +++ b/examples/todomvc/.github/workflows/copilot-setup-steps.yml @@ -17,9 +17,9 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: lts/* diff --git a/package-lock.json b/package-lock.json index 83a3bc510ab85..fe35404b9f42c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8160,10 +8160,6 @@ "resolved": "packages/playwright-chromium", "link": true }, - "node_modules/playwright-cli-stub": { - "resolved": "packages/playwright-cli-stub", - "link": true - }, "node_modules/playwright-core": { "resolved": "packages/playwright-core", "link": true @@ -10232,12 +10228,6 @@ "node": ">=20" } }, - "packages/playwright-cli-stub": { - "version": "0.0.0", - "bin": { - "playwright-cli": "playwright-cli-stub.js" - } - }, "packages/playwright-client": { "name": "@playwright/client", "version": "0.0.0", diff --git a/packages/playwright-cli-stub/package.json b/packages/playwright-cli-stub/package.json deleted file mode 100644 index a56ea13e2a5b6..0000000000000 --- a/packages/playwright-cli-stub/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "playwright-cli-stub", - "version": "0.0.0", - "private": true, - "bin": { - "playwright-cli": "playwright-cli-stub.js" - } -} diff --git a/packages/playwright-cli-stub/playwright-cli-stub.js b/packages/playwright-cli-stub/playwright-cli-stub.js deleted file mode 100755 index 59772a7ce80f8..0000000000000 --- a/packages/playwright-cli-stub/playwright-cli-stub.js +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env node -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const { program } = require('playwright-core/lib/tools/cli-client/program'); - -program().catch(e => { - console.error(e.message); - process.exit(1); -}); diff --git a/packages/playwright/src/agents/copilot-setup-steps.yml b/packages/playwright/src/agents/copilot-setup-steps.yml index d9b5b711e6173..7e76a9d2a4dd4 100644 --- a/packages/playwright/src/agents/copilot-setup-steps.yml +++ b/packages/playwright/src/agents/copilot-setup-steps.yml @@ -17,9 +17,9 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: lts/* From 9c899cd73e6cc3370e28ae24c894d060033b9a28 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 3 Jul 2026 14:12:25 +0100 Subject: [PATCH 2/5] chore(locator): reimplement waitForFunction via _callOnElementOnceMatches (#41611) --- .../playwright-core/src/client/locator.ts | 1 - .../src/server/dispatchers/frameDispatcher.ts | 4 +- packages/playwright-core/src/server/frames.ts | 94 ++++++++++--------- 3 files changed, 53 insertions(+), 46 deletions(-) diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts index 52c20c4269063..99244f2295842 100644 --- a/packages/playwright-core/src/client/locator.ts +++ b/packages/playwright-core/src/client/locator.ts @@ -400,7 +400,6 @@ export class Locator implements api.Locator { isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), timeout: this._frame._timeout(options), - pollingInterval: 100, }, options?.signal); } diff --git a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts index d0225e66d5c51..12aea64c8e2fd 100644 --- a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts @@ -255,11 +255,11 @@ export class FrameDispatcher extends Dispatcher { - const handle = await this._frame.waitForFunctionExpression(progress, params.expression, params.isFunction, parseArgument(params.arg), params); if (params.selector !== undefined) { - handle.dispose(); + await this._frame.waitForFunctionExpressionOnElement(progress, params.selector, params.expression, params.isFunction, parseArgument(params.arg), { strict: params.strict }); return {}; } + const handle = await this._frame.waitForFunctionExpression(progress, params.expression, params.isFunction, parseArgument(params.arg), params); return { handle: ElementHandleDispatcher.fromJSOrElementHandle(this, handle) }; } diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 1cb22c816183c..4aa8178c5816a 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -899,8 +899,9 @@ export class Frame extends SdkObject { } async dispatchEvent(progress: Progress, selector: string, type: string, eventInit: Object = {}, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { - await this._callOnElementOnceMatches(progress, selector, (injectedScript, element, data) => { + await this._waitForFunctionOnSelector(progress, selector, (injectedScript, element, data) => { injectedScript.dispatchEvent(element, data.type, data.eventInit); + return { result: undefined }; }, { type, eventInit }, { mainWorld: true, ...options }, scope); } @@ -1354,32 +1355,37 @@ export class Frame extends SdkObject { } async textContent(progress: Progress, selector: string, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { - return this._callOnElementOnceMatches(progress, selector, (injected, element) => element.textContent, undefined, options, scope); + const { result } = await this._waitForFunctionOnSelector(progress, selector, (injected, element) => ({ result: element.textContent }), undefined, options, scope); + return result; } async innerText(progress: Progress, selector: string, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { - return this._callOnElementOnceMatches(progress, selector, (injectedScript, element) => { + const { result } = await this._waitForFunctionOnSelector(progress, selector, (injectedScript, element) => { if (element.namespaceURI !== 'http://www.w3.org/1999/xhtml') throw injectedScript.createStacklessError('Node is not an HTMLElement'); - return (element as HTMLElement).innerText; + return { result: (element as HTMLElement).innerText }; }, undefined, options, scope); + return result; } async innerHTML(progress: Progress, selector: string, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { - return this._callOnElementOnceMatches(progress, selector, (injected, element) => element.innerHTML, undefined, options, scope); + const { result } = await this._waitForFunctionOnSelector(progress, selector, (injected, element) => ({ result: element.innerHTML }), undefined, options, scope); + return result; } async getAttribute(progress: Progress, selector: string, name: string, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { - return this._callOnElementOnceMatches(progress, selector, (injected, element, data) => element.getAttribute(data.name), { name }, options, scope); + const { result } = await this._waitForFunctionOnSelector(progress, selector, (injected, element, data) => ({ result: element.getAttribute(data.name) }), { name }, options, scope); + return result; } async inputValue(progress: Progress, selector: string, options: types.StrictOptions, scope?: dom.ElementHandle): Promise { - return this._callOnElementOnceMatches(progress, selector, (injectedScript, node) => { + const { result } = await this._waitForFunctionOnSelector(progress, selector, (injectedScript, node) => { const element = injectedScript.retarget(node, 'follow-label'); if (!element || (element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA' && element.nodeName !== 'SELECT')) throw injectedScript.createStacklessError('Node is not an ,