diff --git a/.bazelignore b/.bazelignore index 5e7deeab6caa5..a3400f2832385 100644 --- a/.bazelignore +++ b/.bazelignore @@ -9,7 +9,8 @@ dotnet/src/support/bin dotnet/src/support/obj dotnet/src/webdriver/bin dotnet/src/webdriver/obj -java/build/production +java-libs +java/build java/client/build java/server/build javascript/atoms/node_modules diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index 9796ce323cfb0..0f32ee6e7048c 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -32,7 +32,7 @@ on: type: boolean default: false node-version: - description: Custom Node version to install + description: Custom Node version to use required: false type: string default: '' @@ -91,11 +91,17 @@ on: required: false type: boolean default: false + outputs: + output: + description: Value the run script writes to $GITHUB_OUTPUT as "output=..."; empty when unused + value: ${{ jobs.bazel.outputs.output }} jobs: bazel: name: ${{ inputs.name }} runs-on: ${{ contains(inputs.os, '-') && inputs.os || format('{0}-latest', inputs.os) }} + outputs: + output: ${{ steps.run-bazel.outputs.output }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SEL_M2_USER: ${{ secrets.SEL_M2_USER }} @@ -145,7 +151,7 @@ jobs: env: GIT_REF: ${{ github.ref }} - name: Delete browsers and drivers - if: inputs.os == 'ubuntu' || inputs.os == 'macos' + if: startsWith(inputs.os, 'ubuntu') || inputs.os == 'macos' run: ./scripts/github-actions/delete-browsers-drivers.sh - name: Delete drivers (Windows) if: inputs.os == 'windows' @@ -182,11 +188,9 @@ jobs: - name: Set Ruby version if: inputs.ruby-version != '' run: echo '${{ inputs.ruby-version }}' > rb/.ruby-version - - name: Setup Node + - name: Set Node version if: inputs.node-version != '' - uses: actions/setup-node@v6 - with: - node-version: ${{ inputs.node-version }} + run: echo '${{ inputs.node-version }}' > .nvmrc - name: Setup Bazel with caching continue-on-error: true timeout-minutes: 10 diff --git a/.github/workflows/ci-dotnet.yml b/.github/workflows/ci-dotnet.yml index c59c8df8927ac..6b059e79271b2 100644 --- a/.github/workflows/ci-dotnet.yml +++ b/.github/workflows/ci-dotnet.yml @@ -31,6 +31,7 @@ jobs: name: Browser Tests os: windows needs-display: true + rerun-with-debug: true run: | bazel test //dotnet/test/webdriver:ElementFindingTests-firefox //dotnet/test/webdriver:ElementFindingTests-chrome @@ -41,5 +42,6 @@ jobs: name: Remote Tests os: windows needs-display: true + rerun-with-debug: true run: | bazel test //dotnet/test/remote --flaky_test_attempts=3 diff --git a/.github/workflows/ci-javascript.yml b/.github/workflows/ci-javascript.yml new file mode 100644 index 0000000000000..883e8ee5f379b --- /dev/null +++ b/.github/workflows/ci-javascript.yml @@ -0,0 +1,30 @@ +name: CI - JavaScript + +on: + workflow_call: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Build + uses: ./.github/workflows/bazel.yml + with: + name: Build + run: bazel build //javascript/selenium-webdriver + + unit-tests: + name: Unit Tests + uses: ./.github/workflows/bazel.yml + strategy: + fail-fast: false + matrix: + node-version: ['22.22.0', '24.14.1'] + os: [ubuntu] + with: + name: Unit Tests (${{ matrix.node-version }}, ${{ matrix.os }}) + os: ${{ matrix.os }} + node-version: ${{ matrix.node-version }} + run: bazel test --local_test_jobs 1 //javascript/selenium-webdriver:small-tests diff --git a/.github/workflows/ci-ruby.yml b/.github/workflows/ci-ruby.yml index 192de97107386..30dcc3652cd4b 100644 --- a/.github/workflows/ci-ruby.yml +++ b/.github/workflows/ci-ruby.yml @@ -2,6 +2,12 @@ name: CI - Ruby on: workflow_call: + inputs: + smoke: + description: Run smoke tests only (callers pass false to run the full matrix) + required: false + type: boolean + default: true workflow_dispatch: inputs: smoke: @@ -25,7 +31,7 @@ jobs: # covers truffleruby and the most recent MRI release. unit-tests: name: Unit Tests - if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && !inputs.smoke) + if: github.event_name == 'schedule' || !inputs.smoke uses: ./.github/workflows/bazel.yml strategy: fail-fast: false @@ -48,7 +54,7 @@ jobs: smoke: name: ${{ matrix.os }}-smoke - if: github.event_name != 'schedule' && (github.event_name != 'workflow_dispatch' || inputs.smoke) + if: github.event_name != 'schedule' && inputs.smoke uses: ./.github/workflows/bazel.yml strategy: fail-fast: false @@ -79,7 +85,7 @@ jobs: os-tests-full: name: ${{ matrix.os }}-full - if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && !inputs.smoke) + if: github.event_name == 'schedule' || !inputs.smoke uses: ./.github/workflows/bazel.yml strategy: fail-fast: false diff --git a/.github/workflows/ci-rust.yml b/.github/workflows/ci-rust.yml index f59a2870b4692..1b60927fd8512 100644 --- a/.github/workflows/ci-rust.yml +++ b/.github/workflows/ci-rust.yml @@ -29,10 +29,12 @@ jobs: include: - os: macos - os: ubuntu + - os: ubuntu-24.04-arm - os: windows with: name: Tests (${{ matrix.os }}) os: ${{ matrix.os }} + rerun-with-debug: true run: bazel test --test_env=RUST_BACKTRACE=full --test_env=RUST_TEST_NOCAPTURE=1 --flaky_test_attempts=3 //rust/... windows-stable: @@ -112,17 +114,28 @@ jobs: - name: "Install cross" run: | cargo install cross --git https://github.com/cross-rs/cross - - name: "Build release binary" + - name: "Build release binary (x64)" run: cross build --target x86_64-unknown-linux-musl --release working-directory: rust + - name: "Build release binary (arm64)" + run: cross build --target aarch64-unknown-linux-musl --release + working-directory: rust - name: "Rename binary" - run: mv rust/target/x86_64-unknown-linux-musl/release/selenium-manager selenium-manager-linux + run: | + mv rust/target/x86_64-unknown-linux-musl/release/selenium-manager selenium-manager-linux + mv rust/target/aarch64-unknown-linux-musl/release/selenium-manager selenium-manager-linux-arm64 - name: "Upload release binary" uses: actions/upload-artifact@v7 with: name: selenium-manager-linux path: selenium-manager-linux retention-days: 6 + - name: "Upload release binary" + uses: actions/upload-artifact@v7 + with: + name: selenium-manager-linux-arm64 + path: selenium-manager-linux-arm64 + retention-days: 6 linux-debug: name: "Linux Debug" @@ -141,18 +154,30 @@ jobs: - name: "Install cross" run: | cargo install cross --git https://github.com/cross-rs/cross - - name: "Build release binary" + - name: "Build release binary (x64)" run: | cross build --target x86_64-unknown-linux-musl --profile dev cd target/x86_64-unknown-linux-musl/debug tar -cvf ../../../../selenium-manager-linux-debug.tar selenium-manager working-directory: rust + - name: "Build release binary (arm64)" + run: | + cross build --target aarch64-unknown-linux-musl --profile dev + cd target/aarch64-unknown-linux-musl/debug + tar -cvf ../../../../selenium-manager-linux-arm64-debug.tar selenium-manager + working-directory: rust - name: "Upload release binary" uses: actions/upload-artifact@v7 with: name: selenium-manager-linux-debug path: selenium-manager-linux-debug.tar retention-days: 6 + - name: "Upload release binary" + uses: actions/upload-artifact@v7 + with: + name: selenium-manager-linux-arm64-debug + path: selenium-manager-linux-arm64-debug.tar + retention-days: 6 macos-stable: name: "MacOS Stable" @@ -271,11 +296,12 @@ jobs: - name: "Prepare and Commit" run: | linux_sha=$(shasum -a 256 artifacts/selenium-manager-linux/selenium-manager-linux | awk '{print $1}') + linux_arm64_sha=$(shasum -a 256 artifacts/selenium-manager-linux-arm64/selenium-manager-linux-arm64 | awk '{print $1}') macos_sha=$(shasum -a 256 artifacts/selenium-manager-macos/selenium-manager-macos | awk '{print $1}') windows_sha=$(shasum -a 256 artifacts/selenium-manager-windows/selenium-manager-windows.exe | awk '{print $1}') sbom_sha=$(shasum -a 256 artifacts/selenium-manager-sbom/selenium-manager.cdx.json | awk '{print $1}') notice_sha=$(shasum -a 256 artifacts/selenium-manager-sbom/selenium-manager-THIRD-PARTY-NOTICES.txt | awk '{print $1}') - echo "{\"macos\": \"$macos_sha\", \"windows\": \"$windows_sha\", \"linux\": \"$linux_sha\", \"sbom\": \"$sbom_sha\", \"notice\": \"$notice_sha\"}" > latest.json + echo "{\"macos\": \"$macos_sha\", \"windows\": \"$windows_sha\", \"linux\": \"$linux_sha\", \"linux-arm64\": \"$linux_arm64_sha\", \"sbom\": \"$sbom_sha\", \"notice\": \"$notice_sha\"}" > latest.json git config --local user.email "selenium-ci@users.noreply.github.com" git config --local user.name "Selenium CI Bot" git add latest.json @@ -294,9 +320,11 @@ jobs: prerelease: false files: | artifacts/selenium-manager-linux/selenium-manager-linux + artifacts/selenium-manager-linux-arm64/selenium-manager-linux-arm64 artifacts/selenium-manager-macos/selenium-manager-macos artifacts/selenium-manager-windows/selenium-manager-windows.exe artifacts/selenium-manager-linux-debug/selenium-manager-linux-debug.tar + artifacts/selenium-manager-linux-arm64-debug/selenium-manager-linux-arm64-debug.tar artifacts/selenium-manager-macos-debug/selenium-manager-macos-debug.tar artifacts/selenium-manager-windows-debug/selenium-manager-windows-debug.exe artifacts/selenium-manager-sbom/selenium-manager.cdx.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11f1d413e2a34..eb8baea7fdd9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: [ "${{ github.event_name }}" == "workflow_call" ] || \ [ "${{ github.event_name }}" == "workflow_dispatch" ]; then echo "Running all targets for ${{ github.event_name }} event" - echo "//java/... //py/... //rb/... //dotnet/... //rust/..." > bazel-targets.txt + echo "//java/... //py/... //rb/... //dotnet/... //rust/... //javascript/..." > bazel-targets.txt else if [ -n "${{ github.event.pull_request.base.sha }}" ]; then BASE_SHA="HEAD^1" @@ -70,6 +70,7 @@ jobs: rb: ${{ steps.read.outputs.rb }} dotnet: ${{ steps.read.outputs.dotnet }} rust: ${{ steps.read.outputs.rust }} + js: ${{ steps.read.outputs.js }} steps: - name: Download targets uses: actions/download-artifact@v8 @@ -101,6 +102,7 @@ jobs: check_binding "openqa/selenium/grid" "grid" check_binding "//dotnet" "dotnet" check_binding "//rust" "rust" + check_binding "//javascript" "js" process_binding "//rb" "rb" process_binding "//py" "py" - name: Upload target files @@ -138,6 +140,8 @@ jobs: needs: read-targets uses: ./.github/workflows/ci-ruby.yml if: needs.read-targets.outputs.rb != '' + with: + smoke: ${{ !(github.head_ref == 'pinned-browser-updates' && contains(github.event.pull_request.title, '(major)')) }} rust: name: Rust @@ -147,10 +151,16 @@ jobs: SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }} if: needs.read-targets.outputs.rust != '' + javascript: + name: JavaScript + needs: read-targets + uses: ./.github/workflows/ci-javascript.yml + if: needs.read-targets.outputs.js != '' + ci-success: name: CI Success if: always() - needs: [check, read-targets, dotnet, java, grid, python, ruby, rust] + needs: [check, read-targets, dotnet, java, grid, python, ruby, rust, javascript] runs-on: ubuntu-latest steps: - name: Verify required jobs succeeded diff --git a/.github/workflows/pin-browsers.yml b/.github/workflows/pin-browsers.yml index 896c3019d082f..3870a3b451386 100644 --- a/.github/workflows/pin-browsers.yml +++ b/.github/workflows/pin-browsers.yml @@ -13,7 +13,7 @@ jobs: uses: ./.github/workflows/bazel.yml with: name: Pin Browsers - run: bazel run //scripts:pinned_browsers + run: ./scripts/github-actions/update_browsers.sh artifact-name: pinned-browsers create-pr: @@ -49,9 +49,9 @@ jobs: commit-message: "Update pinned browser versions" author: Selenium CI Bot base: trunk - title: "[build] Automated Browser Version Update" + title: "[build] Automated Browser Version Update${{ contains(needs.update.outputs.output, 'major') && ' (major)' || '' }}${{ contains(needs.update.outputs.output, 'cdp') && ' with CDP' || '' }}" body: | - This is an automated pull request to update pinned browsers and drivers + This is an automated pull request to update pinned browsers and drivers.${{ contains(needs.update.outputs.output, 'major') && ' Major Chrome/Firefox bump: CI runs the full Ruby matrix.' || '' }}${{ contains(needs.update.outputs.output, 'cdp') && ' Chrome DevTools (CDP) was regenerated to match.' || '' }} Merge after verifying the new browser versions are properly passing the tests branch: "pinned-browser-updates" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0517988819704..b5ddc3ddd9eb5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -141,7 +141,7 @@ jobs: name: Publish ${{ matrix.language }} gpg-sign: ${{ matrix.language == 'java' }} gem-trusted-publishing: ${{ matrix.language == 'ruby' }} - node-version: ${{ matrix.language == 'javascript' && '24' || '' }} + node-version: ${{ matrix.language == 'javascript' && '24.14.1' || '' }} run: | if [ "${{ matrix.language == 'java' && (needs.parse-tag.outputs.language == 'all' || needs.parse-tag.outputs.language == 'java') && github.run_attempt > 1 }}" = "true" ]; then echo "::error::Java release is not yet rerun-safe — check/drop the staging repo at https://central.sonatype.com/publishing/deployments and publish manually" diff --git a/.gitignore b/.gitignore index 36ec7206b1833..33e688dbd08d8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ projectFilesBackup/ .svn .credentials.dat .ijwb +/java-libs mockpiframe.log mockpiframe.log.lck junitvmwatcher*.properties diff --git a/.idea/libraries/java-libs.xml b/.idea/libraries/java-libs.xml new file mode 100644 index 0000000000000..d9bca89dcd39a --- /dev/null +++ b/.idea/libraries/java-libs.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/.idea/libraries/libcdp.xml b/.idea/libraries/libcdp.xml deleted file mode 100644 index a4f8561db4e2a..0000000000000 --- a/.idea/libraries/libcdp.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/.idea/misc.xml b/.idea/misc.xml index fa12a2be3de04..63ea786e8bbf1 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 9811c78029378..558ff1ebc517a 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -4,10 +4,10 @@ + - diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000000..85e502778f623 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.22.0 diff --git a/AGENTS.md b/AGENTS.md index bb156032cbf5f..89912a5afadec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,7 @@ The repository README is aimed at contributors; end-user docs live elsewhere. - If `.local/agent/skills/` exists, inspect its `*/SKILL.md` files and treat them as additional user-defined skills. ## Invariants (don't violate unless explicitly asked) -- Maintain API/ABI compatibility - users upgrade by changing only version number +- Maintain API/ABI compatibility by default (users upgrade by changing only the version number); public functionality may be removed only after it has gone through the [Deprecation policy](#deprecation-policy) below - Avoid repo-wide refactors/formatting; prefer small, reversible diffs ## Toolchain diff --git a/MODULE.bazel b/MODULE.bazel index 6ed26168a30d2..827d1a144d406 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -60,6 +60,15 @@ single_version_override( ], ) +# Patch support for bazel-contrib/rules_ruby#393 remove in next rules_ruby release +single_version_override( + module_name = "rules_ruby", + patch_strip = 1, + patches = [ + "//third_party/bazel:rules_ruby_windows_batch_crlf.patch", + ], +) + multitool = use_extension("@rules_multitool//multitool:extension.bzl", "multitool") multitool.hub(lockfile = "//:multitool.lock.json") use_repo(multitool, "multitool") @@ -76,7 +85,7 @@ linter.configure( linter.register(name = "rust-rustfmt") node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node") -node.toolchain(node_version = "22.22.0") +node.toolchain(node_version_from_nvmrc = "//:.nvmrc") pnpm = use_extension( "@aspect_rules_js//npm:extensions.bzl", diff --git a/common/bidi/BUILD.bazel b/common/bidi/BUILD.bazel new file mode 100644 index 0000000000000..9e5f23866a048 --- /dev/null +++ b/common/bidi/BUILD.bazel @@ -0,0 +1,4 @@ +exports_files( + glob(["*.cddl"]), + visibility = ["//javascript/selenium-webdriver:__pkg__"], +) diff --git a/common/bidi/webdriver-bidi-1140.cddl b/common/bidi/webdriver-bidi-1140.cddl new file mode 100644 index 0000000000000..566c971f34c33 --- /dev/null +++ b/common/bidi/webdriver-bidi-1140.cddl @@ -0,0 +1,8 @@ +; Local copy of w3c/webdriver-bidi#1140: adds the webExtension.install extension point. +; Overrides the upstream closed InstallParameters. Delete once #1140 merges and the +; pinned webref carries it. https://github.com/w3c/webdriver-bidi/pull/1140 +webExtension.InstallParameters = { + extensionData: webExtension.ExtensionData, + webExtension.InstallParametersExtension, +} +webExtension.InstallParametersExtension = ( Extensible ) diff --git a/common/bidi/webextension-install-extensions.cddl b/common/bidi/webextension-install-extensions.cddl new file mode 100644 index 0000000000000..da001787d257d --- /dev/null +++ b/common/bidi/webextension-install-extensions.cddl @@ -0,0 +1,7 @@ +; Firefox vendor fields for webExtension.install, matching Mozilla's agreed CDDL (bug 2057588, +; pending merge). Once it lands, repoint vendor_cddl_files at Mozilla's file and delete this. +; https://bugzilla.mozilla.org/show_bug.cgi?id=2057588 +webExtension.InstallParametersExtension //= ( + ? "moz:allowPrivateBrowsing": bool .default false, + ? "moz:permanent": bool .default false, +) diff --git a/common/devtools/chromium/v148/BUILD.bazel b/common/devtools/chromium/v151/BUILD.bazel similarity index 100% rename from common/devtools/chromium/v148/BUILD.bazel rename to common/devtools/chromium/v151/BUILD.bazel diff --git a/common/devtools/chromium/v148/browser_protocol.pdl b/common/devtools/chromium/v151/browser_protocol.pdl similarity index 97% rename from common/devtools/chromium/v148/browser_protocol.pdl rename to common/devtools/chromium/v151/browser_protocol.pdl index 97a293ccc55c8..a3c85dd998a1c 100644 --- a/common/devtools/chromium/v148/browser_protocol.pdl +++ b/common/devtools/chromium/v151/browser_protocol.pdl @@ -313,6 +313,57 @@ experimental domain Accessibility # Updated node data. array of AXNode nodes +# Copyright 2026 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# A domain for ad-related metrics and data. +experimental domain Ads + depends on Network + depends on Page + depends on Runtime + + # Ad frame data. + type AdFrameData extends object + properties + # The DevTools frame token. + Page.FrameId frameId + # The initial origin of the frame. To minimize the payload size, this is + # only sent once per frame. + optional string initialOrigin + # The network bytes of the frame. + number networkBytes + # The CPU time of the frame, in milliseconds. + number cpuTime + + # Ad metrics for a page. + type AdMetrics extends object + properties + # The viewport ad density by area, represented as a percentage (an integer + # between 0 and 100). + integer viewportAdDensityByArea + # The time-weighted average of the viewport ad density by area, measured + # across the duration of the page. + number averageViewportAdDensityByArea + # The number of ads currently visible within the viewport. + integer viewportAdCount + # The time-weighted average of the viewport ad count, measured across the + # duration of the page. + number averageViewportAdCount + # The total ad CPU usage, in milliseconds. + number totalAdCpuTime + # The total ad network bytes. + number totalAdNetworkBytes + # The list of ad frames that have been updated since the last event. + array of AdFrameData updateAdFrames + # The list of ad frame IDs that have been removed since the last event. + array of Page.FrameId removeAdFrames + + # Retrieves ad metrics for the current page. + command getAdMetrics + returns + AdMetrics metrics + # Copyright 2017 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -827,6 +878,7 @@ experimental domain Audits WriteErrorNonSecureContext WriteErrorNonStringIdField WriteErrorNonStringInMatchDestList + WriteErrorInvalidMatchDestList WriteErrorNonStringMatchField WriteErrorNonTokenTypeField WriteErrorRequestAborted @@ -857,6 +909,10 @@ experimental domain Audits ValidationFailedInvalidLength ValidationFailedSignatureMismatch ValidationFailedIntegrityMismatch + SignatureBaseUnknownDerivedComponent + SignatureBaseMissingHeader + SignatureBaseInvalidUnencodedDigest + SignatureBaseUnsupportedComponent type UnencodedDigestError extends string enum @@ -936,10 +992,15 @@ experimental domain Audits FormInputHasWrongButWellIntendedAutocompleteValueError ResponseWasBlockedByORB NavigationEntryMarkedSkippable + BackUINavigationWouldSkipAd AutofillAndManualTextPolicyControlledFeaturesInfo AutofillPolicyControlledFeatureInfo ManualTextPolicyControlledFeatureInfo FormModelContextParameterMissingTitleAndDescription + FormModelContextMissingToolName + FormModelContextMissingToolDescription + FormModelContextRequiredParameterMissingName + FormModelContextParameterMissingName # Depending on the concrete errorType, different properties are set. type GenericIssueDetails extends object @@ -1062,6 +1123,73 @@ experimental domain Audits InvalidAccountsResponse NoReturningUserFromFetchedAccounts + type EmailVerificationRequestIssueDetails extends object + properties + EmailVerificationRequestIssueReason emailVerificationRequestIssueReason + + # Represents the failure reason when an email verification request fails. + # Should be updated alongside EmailVerificationRequestResult in + # third_party/blink/public/mojom/devtools/inspector_issue.mojom. + type EmailVerificationRequestIssueReason extends string + enum + 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. type ClientHintIssueDetails extends object @@ -1177,6 +1305,8 @@ experimental domain Audits FontSizeTooSmall FontSizeTooLarge InvalidSizeValue + NonSecureContext + MissingTransientUserActivation # This issue warns about improper usage of the element. type PermissionElementIssueDetails extends object @@ -1245,6 +1375,7 @@ experimental domain Audits PermissionElementIssue PerformanceIssue SelectivePermissionsInterventionIssue + EmailVerificationRequestIssue # This struct holds a list of optional fields with additional information # specific to the kind of issue. When adding a new issue code, please also @@ -1280,6 +1411,7 @@ experimental domain Audits optional PermissionElementIssueDetails permissionElementIssueDetails optional PerformanceIssueDetails performanceIssueDetails optional SelectivePermissionsInterventionIssueDetails selectivePermissionsInterventionIssueDetails + optional EmailVerificationRequestIssueDetails emailVerificationRequestIssueDetails # A unique id for a DevTools inspector issue. Allows other entities (e.g. # exceptions, CDP message, console messages, etc.) to reference an issue. @@ -2188,6 +2320,18 @@ experimental domain CSS # Specificity of the selector. experimental optional Specificity specificity + # Contribution of an individual simple selector to specificity. + experimental type SpecificityComponent extends object + properties + # The simple selector text that contributes to specificity. + string text + # The a component contribution. + integer a + # The b component contribution. + integer b + # The c component contribution. + integer c + # Specificity: # https://drafts.csswg.org/selectors/#specificity-rules experimental type Specificity extends object @@ -2199,6 +2343,8 @@ experimental domain CSS integer b # The c component, which represents the number of type selectors and pseudo-elements. integer c + # Per-simple-selector contributions used to explain this specificity. + experimental optional array of SpecificityComponent components # Selector list data. type SelectorList extends object @@ -2444,7 +2590,10 @@ experimental domain CSS experimental type CSSContainerQuery extends object properties # Container query text. - string text + # Contains the query part without the container name for a single query. + # Deprecated in favor of conditionText which contains the full prelude + # after @container. + deprecated string text # The associated rule header range in the enclosing stylesheet (if # available). optional SourceRange range @@ -2460,6 +2609,8 @@ experimental domain CSS optional boolean queriesScrollState # true if the query contains anchored() queries. optional boolean queriesAnchored + # CSSContainerRule.conditionText + string conditionText # CSS Supports at-rule descriptor. experimental type CSSSupports extends object @@ -2629,6 +2780,7 @@ experimental domain CSS font-face font-feature-values font-palette-values + counter-style # Subsection of font-feature-values, if this is a subsection. optional enum subsection # LINT.IfChange(FontVariantAlternatesFeatureType) @@ -3032,7 +3184,17 @@ experimental domain CSS CSSMedia media # Modifies the expression of a container query. - experimental command setContainerQueryText + # Deprecated. Use setContainerQueryConditionText instead. + experimental deprecated command setContainerQueryText + parameters + DOM.StyleSheetId styleSheetId + SourceRange range + string text + returns + # The resulting CSS container query rule after modification. + CSSContainerQuery containerQuery + + experimental command setContainerQueryConditionText parameters DOM.StyleSheetId styleSheetId SourceRange range @@ -3414,7 +3576,7 @@ domain DOM after expand-icon picker-icon - interest-hint + interest-button marker backdrop column @@ -3446,8 +3608,11 @@ domain DOM file-selector-button details-content picker + select-listbox permission-icon overscroll-area-parent + overscroll-backdrop + skeleton # Shadow root type. type ShadowRootType extends string @@ -4185,6 +4350,11 @@ domain DOM # If true, opens the popover and keeps it open. If false, closes the # popover if it was previously force-opened. boolean enable + # 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. + optional BackendNodeId invokerNodeId returns # List of popovers that were closed in order to respect popover stacking order. array of NodeId nodeIds @@ -5359,7 +5529,6 @@ domain Emulation PressureSource source optional PressureMetadata metadata - # TODO: OBSOLETE: To remove when setPressureDataOverride is merged. # Provides a given pressure state that will be processed and eventually be # delivered to PressureObserver users. |source| must have been previously # overridden by setPressureSourceOverrideEnabled. @@ -5368,15 +5537,6 @@ domain Emulation PressureSource source PressureState state - # Provides a given pressure data set that will be processed and eventually be - # delivered to PressureObserver users. |source| must have been previously - # overridden by setPressureSourceOverrideEnabled. - experimental command setPressureDataOverride - parameters - PressureSource source - PressureState state - optional number ownContributionEstimate - # Overrides the Idle state. command setIdleOverride parameters @@ -5635,8 +5795,6 @@ experimental domain Extensions managed # Runs an extension default action. - # Available if the client is connected using the --remote-debugging-pipe - # flag and the --enable-unsafe-extension-debugging flag is set. command triggerAction parameters # Extension id. @@ -5646,9 +5804,7 @@ experimental domain Extensions # Installs an unpacked extension from the filesystem similar to # --load-extension CLI flags. Returns extension ID once the extension - # has been installed. Available if the client is connected using the - # --remote-debugging-pipe flag and the --enable-unsafe-extension-debugging - # flag is set. + # has been installed. command loadUnpacked parameters # Absolute file path. @@ -5674,15 +5830,11 @@ experimental domain Extensions boolean enabled # Gets a list of all unpacked extensions. - # Available if the client is connected using the --remote-debugging-pipe flag - # and the --enable-unsafe-extension-debugging flag is set. command getExtensions returns array of ExtensionInfo extensions # Uninstalls an unpacked extension (others not supported) from the profile. - # Available if the client is connected using the --remote-debugging-pipe flag - # and the --enable-unsafe-extension-debugging. command uninstall parameters # Extension id. @@ -8055,14 +8207,6 @@ domain Network None # The cookie should have been blocked by 3PCD but is exempted by explicit user setting. UserSetting - # The cookie should have been blocked by 3PCD but is exempted by metadata mitigation. - TPCDMetadata - # The cookie should have been blocked by 3PCD but is exempted by Deprecation Trial mitigation. - TPCDDeprecationTrial - # The cookie should have been blocked by 3PCD but is exempted by Top-level Deprecation Trial mitigation. - TopLevelTPCDDeprecationTrial - # The cookie should have been blocked by 3PCD but is exempted by heuristics mitigation. - TPCDHeuristics # The cookie should have been blocked by 3PCD but is exempted by Enterprise Policy. EnterprisePolicy # The cookie should have been blocked by 3PCD but is exempted by Storage Access API. @@ -8374,6 +8518,8 @@ domain Network optional integer packetQueueLength # WebRTC packetReordering feature. optional boolean packetReordering + # True to emulate internet disconnection. + optional boolean offline # Activates emulation of network conditions. This command is deprecated in favor of the emulateNetworkConditionsByRule # and overrideNetworkState commands, which can be used together to the same effect. @@ -8401,8 +8547,11 @@ domain Network # explicitly modify `navigator` behavior. experimental command emulateNetworkConditionsByRule parameters - # True to emulate internet disconnection. - boolean offline + # True to emulate internet disconnection. Deprecated, use the offline property in matchedNetworkConditions + # or emulateOfflineServiceWorker instead. + deprecated optional boolean offline + # True to emulate offline service worker. + optional boolean emulateOfflineServiceWorker # Configure conditions for matching requests. If multiple entries match a request, the first entry wins. Global # conditions can be configured by leaving the urlPattern for the conditions empty. These global conditions are # also applied for throttling of p2p connections. @@ -9474,6 +9623,7 @@ domain Network Success KeyError SigningError + TransientSigningError ServerRequestedTermination InvalidSessionId InvalidChallenge @@ -9539,6 +9689,7 @@ domain Network InvalidFederatedSessionProviderFailedToRestoreKey FailedToUnwrapKey SessionDeletedDuringRefresh + CrossOriginRegistrationSiteNotIncluded # Details about a failed device bound session network request. experimental type DeviceBoundSessionFailedRequest extends object @@ -9578,6 +9729,8 @@ domain Network RefreshQuotaExceeded FatalError SigningQuotaExceeded + RefreshedAsWaiter + TransientSigningError # If there was a fetch attempt, the result of that. optional DeviceBoundSessionFetchResult fetchResult # The session display if there was a newly created session. This is populated @@ -9602,6 +9755,7 @@ domain Network ServerRequested InvalidSessionParams RefreshFatalError + DevTools # Session event details specific to challenges. experimental type ChallengeEventDetails extends object @@ -9646,6 +9800,11 @@ domain Network # Whether to enable or disable events. boolean enable + # Deletes a device bound session. + experimental command deleteDeviceBoundSession + parameters + DeviceBoundSessionKey key + # Fetches the schemeful site for a specific origin. experimental command fetchSchemefulSite parameters @@ -9695,12 +9854,6 @@ domain Network # Whether 3pc restriction is enabled. boolean enableThirdPartyCookieRestriction - # Whether 3pc grace period exception should be enabled; false by default. - boolean disableThirdPartyCookieMetadata - - # Whether 3pc heuristics exceptions should be enabled; false by default. - boolean disableThirdPartyCookieHeuristics - # Copyright 2017 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -9911,6 +10064,36 @@ experimental domain Overlay # The content box highlight outline color (default: transparent). optional DOM.RGBA outlineColor + # Supported display cutout shapes. + type DisplayCutoutShape extends string + enum + pill + notch + circle + rectangle + + # Configuration for a display cutout. + type DisplayCutoutConfig extends object + properties + # A rectangle representing the cutout bounds. + DOM.Rect rect + # Shape used to draw the cutout. + DisplayCutoutShape shape + # Border radius for rounded cutout shapes. + optional integer borderRadius + # Upper shoulder radius for notch cutout shapes. + optional integer upperRadius + # Lower transition radius for notch cutout shapes. + optional integer lowerRadius + # Center x coordinate for circle cutout shapes. + optional integer cx + # Center y coordinate for circle cutout shapes. + optional integer cy + # Radius for circle cutout shapes. + optional integer radius + # The cutout fill color (default: black). + optional DOM.RGBA contentColor + # Configuration for Window Controls Overlay type WindowControlsOverlayConfig extends object properties @@ -10179,6 +10362,12 @@ experimental domain Overlay # hinge data, null means hideHinge optional HingeConfig hingeConfig + # Add a display cutout overlay. + command setShowDisplayCutout + parameters + # display cutout data, null means hide display cutout + optional DisplayCutoutConfig displayCutoutConfig + # Show elements in isolation mode with overlays. command setShowIsolatedElements parameters @@ -10487,7 +10676,6 @@ domain Page digital-credentials-get direct-sockets direct-sockets-multicast - direct-sockets-private display-capture document-domain encrypted-media @@ -10538,12 +10726,14 @@ domain Page sub-apps summarizer sync-xhr + tools translator unload usb usb-unrestricted vertical-scroll web-app-installation + webnn web-printing web-share window-management @@ -11009,6 +11199,13 @@ domain Page # Whether or not universal access should be granted to the isolated world. This is a powerful # option, use with caution. optional boolean grantUniveralAccess + # 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. + optional string contentSecurityPolicy returns # Execution context of the isolated world. Runtime.ExecutionContextId executionContextId @@ -11988,6 +12185,7 @@ domain Page EmbedderExtensionMessaging EmbedderExtensionMessagingForOpenPort EmbedderExtensionSentMessageToCachedFrame + EmbedderExtensionFrame RequestedByWebViewClient PostMessageByWebViewClient CacheControlNoStoreDeviceBoundSessionTerminated @@ -12449,6 +12647,7 @@ experimental domain Preload BrowsingDataRemoved PrerenderHostReused FormSubmitWhenPrerendering + CrossDocumentRestart # Fired when a preload enabled state is updated. event preloadEnabledStateUpdated @@ -12491,6 +12690,7 @@ experimental domain Preload PrefetchIneligibleRetryAfter PrefetchIsPrivacyDecoy PrefetchIsStale + PrefetchNotEligibleBlockedByConnectionAllowlist PrefetchNotEligibleBrowserContextOffTheRecord PrefetchNotEligibleDataSaverEnabled PrefetchNotEligibleExistingProxy @@ -12515,6 +12715,7 @@ experimental domain Preload # The prefetch finished successfully but was never used. PrefetchSuccessfulButNotUsed PrefetchNotUsedProbeFailed + PrefetchCancelledOnUserNavigation # Fired when a prefetch attempt is updated. event prefetchStatusUpdated @@ -12921,7 +13122,6 @@ experimental domain SmartCardEmulation shutdown # Maps to SCARD_E_UNKNOWN_CARD. - # TODO(crbug.com/472114998): Rename Mojo's kUnknownError to kUnknownCard to match. unknown-card # Error code that is not mapped in this enum. @@ -14062,18 +14262,24 @@ domain Target string url # Whether the target has an attached client. boolean attached + # Id of the parent target, if any. For example, "iframe" target may have a "page" parent. + optional TargetID parentId # Opener target Id optional TargetID openerId # Whether the target has access to the originating window. experimental boolean canAccessOpener # Frame id of originating window (is only set if target has an opener). experimental optional Page.FrameId openerFrameId - # Id of the parent frame, only present for the "iframe" targets. + # Id of the parent frame, present for "iframe" and "worker" targets. For nested workers, + # this is the "ancestor" frame that created the first worker in the nested chain. experimental optional Page.FrameId parentFrameId experimental optional Browser.BrowserContextID browserContextId # Provides additional details for specific target types. For example, for # the type of "page", this may be set to "prerender". experimental optional string subtype + # Embedder-specific target metadata. This is only set for targets of + # type "tab". + experimental optional object embedderData # A filter used by target query/discovery/auto-attach operations. experimental type FilterEntry extends object @@ -14208,13 +14414,11 @@ domain Target # present in the tab UI strip. Cannot be created with `forTab: true`, `newWindow: true` or # `background: false`. The life-time of the tab is limited to the life-time of the session. experimental optional boolean hidden - # 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. experimental optional boolean focus returns # The id of the page opened. @@ -14384,8 +14588,8 @@ domain Target # This can be the page or tab target ID. TargetID targetId # The id of the panel we want DevTools to open initially. Currently - # supported panels are elements, console, network, sources, resources - # and performance. + # supported panels are elements, console, network, sources, resources, + # timeline, chrome-recorder, heap-profiler, lighthouse, and security. optional string panelId returns # The targetId of DevTools page target. @@ -14552,6 +14756,17 @@ domain Tracing experimental optional binary perfettoConfig # Backend type (defaults to `auto`) experimental optional 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. + experimental optional integer screenshotMaxSize + # 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. + experimental optional integer screenshotMaxCount experimental event bufferUsage parameters @@ -15047,13 +15262,15 @@ experimental domain WebMCP properties # A hint indicating that the tool does not modify any state. optional boolean readOnly + # A hint indicating that the tool output may contain untrusted content, ex: UGC, 3rd party data. + optional boolean untrustedContent # If the declarative tool was declared with the autosubmit attribute. optional boolean autosubmit # Represents the status of a tool invocation. type InvocationStatus extends string enum - Success + Completed Canceled Error @@ -15083,17 +15300,44 @@ experimental domain WebMCP # Disables the WebMCP domain. command disable + # Invokes a registered tool. + command invokeTool + parameters + # Frame in which to invoke the tool. + Page.FrameId frameId + # Name of the tool to invoke. + string toolName + # Input parameters for the tool, matching the tool's inputSchema. + object input + returns + # Unique identifier for this invocation. Response is sent before tool events. + string invocationId + + # Cancels a pending tool invocation. + command cancelInvocation + parameters + # Invocation identifier to cancel. + string invocationId + # Event fired when new tools are added. event toolsAdded parameters # Array of tools that were added. array of Tool tools + # Definition of a tool that was removed. + type RemovedTool extends object + properties + # Tool name. + string name + # Frame identifier associated with the tool registration. + Page.FrameId frameId + # Event fired when tools are removed. event toolsRemoved parameters # Array of tools that were removed. - array of Tool tools + array of RemovedTool tools # Event fired when a tool invocation starts. event toolInvoked @@ -15114,7 +15358,8 @@ experimental domain WebMCP string invocationId # Status of the invocation. InvocationStatus status - # Output or error delivered as delivered to the agent. Missing if `status` is anything other than Success. + # Output or error delivered as delivered to the agent. Missing if `status` is anything other than Completed. + # Note: The output is untrusted and poses a prompt injection risk. Clients should treat this as potentially malicious user input. optional any output # Error text for protocol users. optional string errorText diff --git a/common/devtools/chromium/v148/js_protocol.pdl b/common/devtools/chromium/v151/js_protocol.pdl similarity index 100% rename from common/devtools/chromium/v148/js_protocol.pdl rename to common/devtools/chromium/v151/js_protocol.pdl diff --git a/common/repositories.bzl b/common/repositories.bzl index df50fa616a238..9d7a94a655c8a 100644 --- a/common/repositories.bzl +++ b/common/repositories.bzl @@ -12,8 +12,8 @@ def pin_browsers(): http_archive( name = "linux_firefox", - url = "https://ftp.mozilla.org/pub/firefox/releases/153.0/linux-x86_64/en-US/firefox-153.0.tar.xz", - sha256 = "bfc57e7b6b4e6204b11e7e03c4b93cff708e9fb37f6b9948be243455311d82ee", + url = "https://ftp.mozilla.org/pub/firefox/releases/153.0.3/linux-x86_64/en-US/firefox-153.0.3.tar.xz", + sha256 = "22b312280900bfb174b685ece32c7b3c6d72e7f8e53d6d30f21ac41a8dc500a2", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -34,8 +34,8 @@ js_library( dmg_archive( name = "mac_firefox", - url = "https://ftp.mozilla.org/pub/firefox/releases/153.0/mac/en-US/Firefox%20153.0.dmg", - sha256 = "2f9b5a20e546e7e79e4182f8fe10353a3e251635963ab3f6d399a3f290adeb96", + url = "https://ftp.mozilla.org/pub/firefox/releases/153.0.3/mac/en-US/Firefox%20153.0.3.dmg", + sha256 = "a0523b6f2f10f13c6071d8b53ed7678193d693febd8a5d4fd8d7417b3c661045", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -51,8 +51,8 @@ js_library( http_archive( name = "linux_beta_firefox", - url = "https://ftp.mozilla.org/pub/firefox/releases/154.0b3/linux-x86_64/en-US/firefox-154.0b3.tar.xz", - sha256 = "2d2d8e431242d6c063fc5af1548110e13201aa647415b43ac85269903e03472d", + url = "https://ftp.mozilla.org/pub/firefox/releases/154.0b8/linux-x86_64/en-US/firefox-154.0b8.tar.xz", + sha256 = "fb62f644ff2bccd22830395ec8bd5cb59e10083e7a62fc6e3dd328543cc2a3c0", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -73,8 +73,8 @@ js_library( dmg_archive( name = "mac_beta_firefox", - url = "https://ftp.mozilla.org/pub/firefox/releases/154.0b3/mac/en-US/Firefox%20154.0b3.dmg", - sha256 = "a339ad4160df2e8c1a3ed2b1c9c87512c7172b4e30e4f84233044457f3d772d6", + url = "https://ftp.mozilla.org/pub/firefox/releases/154.0b8/mac/en-US/Firefox%20154.0b8.dmg", + sha256 = "bd497c2f48bd5d011c4094de56a9d90dc3e61aafbee0ee154bf9a62f9650c945", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -124,10 +124,10 @@ js_library( pkg_archive( name = "mac_edge", - url = "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/716145f5-d360-45d2-b483-c2e902fd004b/MicrosoftEdge-150.0.4078.105.pkg", - sha256 = "74ce0195965c7276bb1f0a2929b4949cb4253f3040070c2d40f33f142858f924", + url = "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/45588d64-460f-48a7-a6ed-b94a656d170a/MicrosoftEdge-151.0.4129.72.pkg", + sha256 = "b0ef3c0cf91e2d3879ba9a858e11e1957bff3fc520071db515f5da8c987b8981", move = { - "MicrosoftEdge-150.0.4078.105.pkg/Payload/Microsoft Edge.app": "Edge.app", + "MicrosoftEdge-151.0.4129.72.pkg/Payload/Microsoft Edge.app": "Edge.app", }, build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") @@ -144,8 +144,8 @@ js_library( deb_archive( name = "linux_edge", - url = "https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-stable/microsoft-edge-stable_150.0.4078.105-1_amd64.deb", - sha256 = "7114192e2c7e8c12aeecb604b2732355e925716035729ebf8a0afe6895cdc01b", + url = "https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-stable/microsoft-edge-stable_151.0.4129.72-1_amd64.deb", + sha256 = "babd5ea470a86c84615e23a7d5aaf4e1be6811b3af1229bc234d78bdc6b4f55a", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -166,8 +166,8 @@ js_library( http_archive( name = "linux_edgedriver", - url = "https://msedgedriver.microsoft.com/150.0.4078.105/edgedriver_linux64.zip", - sha256 = "a5d1427aaefc299cae4afc4160708421e1a677c8d749f1ec6b60ff8c70fb117b", + url = "https://msedgedriver.microsoft.com/151.0.4129.72/edgedriver_linux64.zip", + sha256 = "3468ee61e613e25d81b398f2832b2ee43c886a31ac6321ee60b79e1c3e3defa7", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -183,8 +183,8 @@ js_library( http_archive( name = "mac_edgedriver", - url = "https://msedgedriver.microsoft.com/150.0.4078.99/edgedriver_mac64_m1.zip", - sha256 = "71dcdf98ea6a6714fcb63349685583d6b82f630c1a979c931ec525b746d2f846", + url = "https://msedgedriver.microsoft.com/151.0.4129.72/edgedriver_mac64_m1.zip", + sha256 = "2d85ac5abdf6b6c3a6caf8731b2253907d3ebfc75fb3f295e03ae16658dcfbdc", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -200,8 +200,8 @@ js_library( http_archive( name = "linux_chrome", - url = "https://storage.googleapis.com/chrome-for-testing-public/151.0.7922.47/linux64/chrome-linux64.zip", - sha256 = "14ac03a67e154e3f8bbc57e03ef03315fda8fedff8e045eee8b31500283a33f4", + url = "https://storage.googleapis.com/chrome-for-testing-public/151.0.7922.77/linux64/chrome-linux64.zip", + sha256 = "60a324a6e1d27b20f2035a2cdaf71641a739fe1f5571f63794773225820bce8a", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -221,8 +221,8 @@ js_library( ) http_archive( name = "mac_chrome", - url = "https://storage.googleapis.com/chrome-for-testing-public/151.0.7922.47/mac-arm64/chrome-mac-arm64.zip", - sha256 = "9529990b6afd9867a862c7a5bff2a4a8eef84614d910acac22e4c5fa5c24daee", + url = "https://storage.googleapis.com/chrome-for-testing-public/151.0.7922.77/mac-arm64/chrome-mac-arm64.zip", + sha256 = "4b3caaabb967070f1541ff5b0fd2c95b2ba839be33a58842a8a877ec5f3fbd9b", strip_prefix = "chrome-mac-arm64", patch_cmds = [ "mv 'Google Chrome for Testing.app' Chrome.app", @@ -242,8 +242,8 @@ js_library( ) http_archive( name = "linux_chromedriver", - url = "https://storage.googleapis.com/chrome-for-testing-public/151.0.7922.47/linux64/chromedriver-linux64.zip", - sha256 = "2faa72828261cd3c5ff00cbc71cfca57a12c26c1406e084e1a34d8d90e292140", + url = "https://storage.googleapis.com/chrome-for-testing-public/151.0.7922.77/linux64/chromedriver-linux64.zip", + sha256 = "65dca829d176845f864b794be1ecdd31e855f14ca9fa1eb93b2d1c0e7242abdd", strip_prefix = "chromedriver-linux64", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") @@ -260,8 +260,8 @@ js_library( http_archive( name = "mac_chromedriver", - url = "https://storage.googleapis.com/chrome-for-testing-public/151.0.7922.47/mac-arm64/chromedriver-mac-arm64.zip", - sha256 = "d450678936f5a2b39598a4e7d548177931a7a5c4759c0e4824f2cab1ae26523e", + url = "https://storage.googleapis.com/chrome-for-testing-public/151.0.7922.77/mac-arm64/chromedriver-mac-arm64.zip", + sha256 = "31e6009ba241f62362b9ca44c5b8dfb10c040f84dd79985d5ea91ea3168b5ce0", strip_prefix = "chromedriver-mac-arm64", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") @@ -278,8 +278,8 @@ js_library( http_archive( name = "linux_beta_chrome", - url = "https://storage.googleapis.com/chrome-for-testing-public/151.0.7922.47/linux64/chrome-linux64.zip", - sha256 = "14ac03a67e154e3f8bbc57e03ef03315fda8fedff8e045eee8b31500283a33f4", + url = "https://storage.googleapis.com/chrome-for-testing-public/152.0.7977.30/linux64/chrome-linux64.zip", + sha256 = "226fd1f3337641f2ca795f127d605d78c69f8c735206df7f394f8078e946a27d", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -299,8 +299,8 @@ js_library( ) http_archive( name = "mac_beta_chrome", - url = "https://storage.googleapis.com/chrome-for-testing-public/151.0.7922.47/mac-arm64/chrome-mac-arm64.zip", - sha256 = "9529990b6afd9867a862c7a5bff2a4a8eef84614d910acac22e4c5fa5c24daee", + url = "https://storage.googleapis.com/chrome-for-testing-public/152.0.7977.30/mac-arm64/chrome-mac-arm64.zip", + sha256 = "441bef11445a8642333ad88b0a7ed31e6742ef7fbb212812ec2f371c4a1b38eb", strip_prefix = "chrome-mac-arm64", patch_cmds = [ "mv 'Google Chrome for Testing.app' Chrome.app", @@ -320,8 +320,8 @@ js_library( ) http_archive( name = "linux_beta_chromedriver", - url = "https://storage.googleapis.com/chrome-for-testing-public/151.0.7922.47/linux64/chromedriver-linux64.zip", - sha256 = "2faa72828261cd3c5ff00cbc71cfca57a12c26c1406e084e1a34d8d90e292140", + url = "https://storage.googleapis.com/chrome-for-testing-public/152.0.7977.30/linux64/chromedriver-linux64.zip", + sha256 = "2ccb6ee76595ef679e6cb7e3177127d055871406b123005c9e8f77323497b640", strip_prefix = "chromedriver-linux64", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") @@ -338,8 +338,8 @@ js_library( http_archive( name = "mac_beta_chromedriver", - url = "https://storage.googleapis.com/chrome-for-testing-public/151.0.7922.47/mac-arm64/chromedriver-mac-arm64.zip", - sha256 = "d450678936f5a2b39598a4e7d548177931a7a5c4759c0e4824f2cab1ae26523e", + url = "https://storage.googleapis.com/chrome-for-testing-public/152.0.7977.30/mac-arm64/chromedriver-mac-arm64.zip", + sha256 = "c8a55cbd29b3909e9de878e376fd3503f97c5166c059c08bac3a65082a927258", strip_prefix = "chromedriver-mac-arm64", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") diff --git a/dotnet/AGENTS.md b/dotnet/AGENTS.md index 81c7b1766a874..2c48e93d2f7db 100644 --- a/dotnet/AGENTS.md +++ b/dotnet/AGENTS.md @@ -28,6 +28,14 @@ _logger.Debug("diagnostic: request details for debugging"); [Obsolete("Use NewMethod instead")] public void OldMethod() { } ``` +When code inside the assembly must still reference an obsolete member (e.g. a field +or method the obsolete API is built on), wrap just that usage to keep the build +warning-clean (see `UserPromptHandler.cs`): +```csharp +#pragma warning disable CS0618 // Type or member is obsolete +this.legacyThing.DoWork(); +#pragma warning restore CS0618 // Type or member is obsolete +``` ### Async patterns The codebase is migrating to async diff --git a/dotnet/src/webdriver/BiDi/BiDi.cs b/dotnet/src/webdriver/BiDi/BiDi.cs index 93dd627890567..3a51763890ba0 100644 --- a/dotnet/src/webdriver/BiDi/BiDi.cs +++ b/dotnet/src/webdriver/BiDi/BiDi.cs @@ -60,7 +60,11 @@ public static async Task ConnectAsync(Uri url, Action BiDiOptionsBuilder builder = new(); configure?.Invoke(builder); - var transport = await builder.TransportFactory(url, cancellationToken).ConfigureAwait(false); + var transportFactoryTask = builder.TransportFactory(url, cancellationToken) + ?? throw new InvalidOperationException("The transport factory must return a non-null Task instance."); + + var transport = await transportFactoryTask.ConfigureAwait(false) + ?? throw new InvalidOperationException("The transport factory must return a non-null ITransport instance."); BiDi bidi = new(); diff --git a/dotnet/src/webdriver/BiDi/BiDiOptionsBuilder.cs b/dotnet/src/webdriver/BiDi/BiDiOptionsBuilder.cs index 23b7d433fde4f..06c279cec02a7 100644 --- a/dotnet/src/webdriver/BiDi/BiDiOptionsBuilder.cs +++ b/dotnet/src/webdriver/BiDi/BiDiOptionsBuilder.cs @@ -27,8 +27,11 @@ namespace OpenQA.Selenium.BiDi; /// public sealed class BiDiOptionsBuilder { + private static readonly Func> DefaultTransportFactory = + (uri, ct) => WebSocketTransport.ConnectAsync(uri, null, ct); + internal Func> TransportFactory { get; private set; } - = (uri, ct) => WebSocketTransport.ConnectAsync(uri, null, ct); + = DefaultTransportFactory; /// /// Configures the BiDi connection to use a WebSocket transport. @@ -42,36 +45,27 @@ public sealed class BiDiOptionsBuilder /// The current instance for chaining. public BiDiOptionsBuilder UseWebSocket(Action? configure = null) { - return UseTransport((uri, ct) => WebSocketTransport.ConnectAsync(uri, configure, ct)); + TransportFactory = (uri, ct) => WebSocketTransport.ConnectAsync(uri, configure, ct); + return this; } /// - /// Configures the BiDi connection to use a transport created by the specified factory. + /// Composes a transport factory into the current transport pipeline. /// /// - /// BiDi takes ownership of the transport instance returned by the factory and will dispose it. + /// The callback receives the current transport factory and returns + /// the next factory in the chain. BiDi takes ownership of the transport instance returned by + /// the final factory and will dispose it. /// - /// A factory function that creates the instance. + /// A callback that composes a new transport factory from the current one. /// The current instance for chaining. - public BiDiOptionsBuilder UseTransport(Func factory) + public BiDiOptionsBuilder UseTransport(Func>, Func>> next) { - ArgumentNullException.ThrowIfNull(factory); - - return UseTransport((_, ct) => - { - if (ct.IsCancellationRequested) - { - return Task.FromCanceled(ct); - } + ArgumentNullException.ThrowIfNull(next); - var transport = factory() ?? throw new InvalidOperationException("The transport factory must return a non-null ITransport instance."); + var factory = next(TransportFactory) + ?? throw new InvalidOperationException("The transport factory decorator must return a non-null factory."); - return Task.FromResult(transport); - }); - } - - private BiDiOptionsBuilder UseTransport(Func> factory) - { TransportFactory = factory; return this; } diff --git a/dotnet/src/webdriver/BiDi/Broker.cs b/dotnet/src/webdriver/BiDi/Broker.cs index 8ab5d7ea66aac..69e20f6baaf35 100644 --- a/dotnet/src/webdriver/BiDi/Broker.cs +++ b/dotnet/src/webdriver/BiDi/Broker.cs @@ -78,12 +78,11 @@ public async Task ExecuteAsync(Command(TaskCreationOptions.RunContinuationsAsynchronously); - using var cts = cancellationToken.CanBeCanceled - ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken) - : new CancellationTokenSource(); + using CancellationTokenSource? cts = cancellationToken.CanBeCanceled + ? null + : new CancellationTokenSource(DefaultCommandTimeout); - var timeout = options?.Timeout ?? DefaultCommandTimeout; - cts.CancelAfter(timeout); + var effectiveToken = cts?.Token ?? cancellationToken; var sendBuffer = RentBuffer(); @@ -132,9 +131,9 @@ public async Task ExecuteAsync(Command + using var ctsRegistration = effectiveToken.Register(() => { - tcs.TrySetCanceled(cts.Token); + tcs.TrySetCanceled(effectiveToken); _pendingCommands.TryRemove(id, out _); }); @@ -149,7 +148,7 @@ public async Task ExecuteAsync(Command data) case TypeEvent: if (method is null) throw new BiDiException($"The remote end responded with 'event' message type, but missed required 'method' property. Message content: {System.Text.Encoding.UTF8.GetString(data.ToArray())}"); - if (!_bidi.EventDispatcher.TryDeserializeAndDispatch(method, ref paramsReader, additionalMessageData)) + try { - if (_logger.IsEnabled(LogEventLevel.Warn)) - { - _logger.Warn($"Received BiDi event with method '{method}', but no event type mapping was found. Event will be ignored. Message content: {System.Text.Encoding.UTF8.GetString(data.ToArray())}"); - } + _bidi.EventDispatcher.DeserializeAndDispatch(method, ref paramsReader, additionalMessageData); + } + catch (Exception ex) + { + _logger.Warn($"Failed to deserialize and dispatch '{method}' event: {ex}.\nMessage content: {System.Text.Encoding.UTF8.GetString(data.ToArray())}"); } break; diff --git a/dotnet/src/webdriver/BiDi/BrowsingContext/GetTree.cs b/dotnet/src/webdriver/BiDi/BrowsingContext/GetTree.cs index 526ff3d2b983d..1b8cd333da563 100644 --- a/dotnet/src/webdriver/BiDi/BrowsingContext/GetTree.cs +++ b/dotnet/src/webdriver/BiDi/BrowsingContext/GetTree.cs @@ -35,8 +35,7 @@ public sealed record ContextGetTreeOptions : CommandOptions internal static GetTreeOptions WithContext(ContextGetTreeOptions? options, BrowsingContext context) => new() { Root = context, - MaxDepth = options?.MaxDepth, - Timeout = options?.Timeout + MaxDepth = options?.MaxDepth }; } diff --git a/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewport.cs b/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewport.cs index 28f3b4b255ada..0fdf45fc3347b 100644 --- a/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewport.cs +++ b/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewport.cs @@ -50,8 +50,7 @@ public sealed record ContextSetViewportOptions : CommandOptions { Context = context, Viewport = options?.Viewport, - DevicePixelRatio = options?.DevicePixelRatio, - Timeout = options?.Timeout + DevicePixelRatio = options?.DevicePixelRatio }; } diff --git a/dotnet/src/webdriver/BiDi/Command.cs b/dotnet/src/webdriver/BiDi/Command.cs index 147548b967071..099c685c0618e 100644 --- a/dotnet/src/webdriver/BiDi/Command.cs +++ b/dotnet/src/webdriver/BiDi/Command.cs @@ -63,8 +63,6 @@ public AdditionalData AdditionalData public abstract record CommandOptions { - public TimeSpan? Timeout { get; init; } - public AdditionalData AdditionalData { get; init; } public AdditionalData AdditionalMessageData { get; init; } diff --git a/dotnet/src/webdriver/BiDi/EventDispatcher.cs b/dotnet/src/webdriver/BiDi/EventDispatcher.cs index 25927e21968ad..cabcc7d050a6d 100644 --- a/dotnet/src/webdriver/BiDi/EventDispatcher.cs +++ b/dotnet/src/webdriver/BiDi/EventDispatcher.cs @@ -111,35 +111,31 @@ public async Task> SubscribeReaderAsync( return (EventStream)subscription; } - public bool TryDeserializeAndDispatch(string method, ref Utf8JsonReader paramsReader, Dictionary? additionalMessageData = null) + public void DeserializeAndDispatch(string method, ref Utf8JsonReader paramsReader, Dictionary? additionalMessageData = null) { - if (!_events.TryGetValue(method, out var slot)) + if (_events.TryGetValue(method, out var slot)) { - return false; - } - - var eventArgs = (EventArgs)(JsonSerializer.Deserialize(ref paramsReader, slot.JsonTypeInfo) + var eventArgs = (EventArgs)(JsonSerializer.Deserialize(ref paramsReader, slot.JsonTypeInfo) ?? throw new BiDiException("Remote end returned null event args in the 'params' property.")); - eventArgs.BiDi = _bidi; + eventArgs.BiDi = _bidi; - if (additionalMessageData is not null) - eventArgs.AdditionalMessageData = AdditionalData.FromDictionary(additionalMessageData); + if (additionalMessageData is not null) + eventArgs.AdditionalMessageData = AdditionalData.FromDictionary(additionalMessageData); - foreach (var subscription in slot.GetSnapshot()) - { - try - { - subscription.Deliver(eventArgs); - } - catch (Exception ex) + foreach (var subscription in slot.GetSnapshot()) { - _logger.Error($"Failed to deliver '{method}' event to subscription: {ex.Message}"); - subscription.Complete(ex); + try + { + subscription.Deliver(eventArgs); + } + catch (Exception ex) + { + _logger.Error($"Failed to deliver '{method}' event to subscription: {ex.Message}"); + subscription.Complete(ex); + } } } - - return true; } public async Task CompleteAllAsync(Exception? error) diff --git a/dotnet/src/webdriver/BiDi/Network/AddDataCollector.cs b/dotnet/src/webdriver/BiDi/Network/AddDataCollector.cs index 4c7dc75352d19..5ce079fc26b98 100644 --- a/dotnet/src/webdriver/BiDi/Network/AddDataCollector.cs +++ b/dotnet/src/webdriver/BiDi/Network/AddDataCollector.cs @@ -43,8 +43,7 @@ public sealed record ContextAddDataCollectorOptions : CommandOptions { Contexts = [context], CollectorType = options?.CollectorType, - UserContexts = options?.UserContexts, - Timeout = options?.Timeout + UserContexts = options?.UserContexts }; } diff --git a/dotnet/src/webdriver/BiDi/Network/AddIntercept.cs b/dotnet/src/webdriver/BiDi/Network/AddIntercept.cs index ec4a6666890b7..b626581d14c98 100644 --- a/dotnet/src/webdriver/BiDi/Network/AddIntercept.cs +++ b/dotnet/src/webdriver/BiDi/Network/AddIntercept.cs @@ -29,7 +29,6 @@ public record AddInterceptOptions() : CommandOptions internal AddInterceptOptions(ContextAddInterceptOptions? options) : this() { UrlPatterns = options?.UrlPatterns; - Timeout = options?.Timeout; } public ImmutableArray? Contexts { get; init; } diff --git a/dotnet/src/webdriver/BiDi/Network/SetCacheBehavior.cs b/dotnet/src/webdriver/BiDi/Network/SetCacheBehavior.cs index 8ae745939c4c3..33eb84b13aaf5 100644 --- a/dotnet/src/webdriver/BiDi/Network/SetCacheBehavior.cs +++ b/dotnet/src/webdriver/BiDi/Network/SetCacheBehavior.cs @@ -33,8 +33,7 @@ public sealed record ContextSetCacheBehaviorOptions : CommandOptions { internal static SetCacheBehaviorOptions WithContext(ContextSetCacheBehaviorOptions? options, BrowsingContext.BrowsingContext context) => new() { - Contexts = [context], - Timeout = options?.Timeout + Contexts = [context] }; } diff --git a/dotnet/src/webdriver/BiDi/Script/AddPreloadScript.cs b/dotnet/src/webdriver/BiDi/Script/AddPreloadScript.cs index 2a859cee1fb42..c6e75a08fd463 100644 --- a/dotnet/src/webdriver/BiDi/Script/AddPreloadScript.cs +++ b/dotnet/src/webdriver/BiDi/Script/AddPreloadScript.cs @@ -44,8 +44,7 @@ public sealed record ContextAddPreloadScriptOptions : CommandOptions { Contexts = [context], Arguments = options?.Arguments, - Sandbox = options?.Sandbox, - Timeout = options?.Timeout + Sandbox = options?.Sandbox }; } diff --git a/dotnet/src/webdriver/BiDi/Script/GetRealms.cs b/dotnet/src/webdriver/BiDi/Script/GetRealms.cs index b4916e3abbcd5..ca91ec2bb35c4 100644 --- a/dotnet/src/webdriver/BiDi/Script/GetRealms.cs +++ b/dotnet/src/webdriver/BiDi/Script/GetRealms.cs @@ -35,8 +35,7 @@ public sealed record ContextGetRealmsOptions : CommandOptions internal static GetRealmsOptions WithContext(ContextGetRealmsOptions? options, BrowsingContext.BrowsingContext context) => new() { Context = context, - Type = options?.Type, - Timeout = options?.Timeout + Type = options?.Type }; } diff --git a/dotnet/src/webdriver/BiDi/Storage/DeleteCookies.cs b/dotnet/src/webdriver/BiDi/Storage/DeleteCookies.cs index e30fd6c0771ca..9559123bc73f2 100644 --- a/dotnet/src/webdriver/BiDi/Storage/DeleteCookies.cs +++ b/dotnet/src/webdriver/BiDi/Storage/DeleteCookies.cs @@ -35,8 +35,7 @@ public sealed record ContextDeleteCookiesOptions : CommandOptions internal static DeleteCookiesOptions WithContext(ContextDeleteCookiesOptions? options, BrowsingContext.BrowsingContext context) => new() { Partition = new ContextPartitionDescriptor(context), - Filter = options?.Filter, - Timeout = options?.Timeout + Filter = options?.Filter }; } diff --git a/dotnet/src/webdriver/BiDi/Storage/GetCookies.cs b/dotnet/src/webdriver/BiDi/Storage/GetCookies.cs index e51b483aa276d..352ff318125d2 100644 --- a/dotnet/src/webdriver/BiDi/Storage/GetCookies.cs +++ b/dotnet/src/webdriver/BiDi/Storage/GetCookies.cs @@ -38,8 +38,7 @@ public sealed record ContextGetCookiesOptions : CommandOptions internal static GetCookiesOptions WithContext(ContextGetCookiesOptions? options, BrowsingContext.BrowsingContext context) => new() { Filter = options?.Filter, - Partition = new ContextPartitionDescriptor(context), - Timeout = options?.Timeout + Partition = new ContextPartitionDescriptor(context) }; } diff --git a/dotnet/src/webdriver/BiDi/Storage/SetCookie.cs b/dotnet/src/webdriver/BiDi/Storage/SetCookie.cs index 2e26166ac8c51..357d0b9960f84 100644 --- a/dotnet/src/webdriver/BiDi/Storage/SetCookie.cs +++ b/dotnet/src/webdriver/BiDi/Storage/SetCookie.cs @@ -47,8 +47,7 @@ public sealed record ContextSetCookieOptions : CommandOptions { internal static SetCookieOptions WithContext(ContextSetCookieOptions? options, BrowsingContext.BrowsingContext context) => new() { - Partition = new ContextPartitionDescriptor(context), - Timeout = options?.Timeout + Partition = new ContextPartitionDescriptor(context) }; } diff --git a/dotnet/src/webdriver/Chrome/ChromeDriverService.cs b/dotnet/src/webdriver/Chrome/ChromeDriverService.cs index 0bc60d53c50d6..c24c77b7b8c53 100644 --- a/dotnet/src/webdriver/Chrome/ChromeDriverService.cs +++ b/dotnet/src/webdriver/Chrome/ChromeDriverService.cs @@ -40,6 +40,9 @@ private ChromeDriverService(string? executablePath, string? executableFileName, { } + /// + protected override string DriverServiceEnvironmentVariableName => "SE_CHROMEDRIVER"; + /// protected override DriverOptions GetDefaultDriverOptions() { diff --git a/dotnet/src/webdriver/Chromium/ChromiumDriver.cs b/dotnet/src/webdriver/Chromium/ChromiumDriver.cs index 522f425ba4d91..7b3197bc5c39a 100644 --- a/dotnet/src/webdriver/Chromium/ChromiumDriver.cs +++ b/dotnet/src/webdriver/Chromium/ChromiumDriver.cs @@ -156,7 +156,7 @@ private static async Task GenerateDriverServiceCommandExecutor throw new ArgumentNullException(nameof(options)); } - if (service.DriverServicePath == null) + if (service.DriverServicePath == null && service.DriverPathFromEnvironment == null) { DriverFinder finder = new DriverFinder(options); string fullServicePath = await finder.GetDriverPathAsync().ConfigureAwait(false); diff --git a/dotnet/src/webdriver/Chromium/ChromiumDriverService.cs b/dotnet/src/webdriver/Chromium/ChromiumDriverService.cs index 83bb05ae9a992..d6c8321139f95 100644 --- a/dotnet/src/webdriver/Chromium/ChromiumDriverService.cs +++ b/dotnet/src/webdriver/Chromium/ChromiumDriverService.cs @@ -174,8 +174,12 @@ protected override string CommandLineArguments argsBuilder.Append($" -allowed-ips={this.AllowedIPAddresses}"); } - // Unconditionally redirect browser logs to the same log as the driver - argsBuilder.Append(" --enable-chrome-logs"); + // Redirect browser logs to the driver log, unless the user set CHROME_LOG_FILE, which + // --enable-chrome-logs would otherwise override. + if (Environment.GetEnvironmentVariable("CHROME_LOG_FILE") is null) + { + argsBuilder.Append(" --enable-chrome-logs"); + } return argsBuilder.ToString(); } diff --git a/dotnet/src/webdriver/DevTools/DevToolsDomains.cs b/dotnet/src/webdriver/DevTools/DevToolsDomains.cs index 026820bd870c5..9710b561b352f 100644 --- a/dotnet/src/webdriver/DevTools/DevToolsDomains.cs +++ b/dotnet/src/webdriver/DevTools/DevToolsDomains.cs @@ -33,14 +33,14 @@ public abstract class DevToolsDomains // added to this array and to the method below. private static int[] SupportedDevToolsVersions => [ - 148, + 151, 150, 149, ]; private static DevToolsDomains? CreateDevToolsDomain(int protocolVersion, DevToolsSession session) => protocolVersion switch { - 148 => new V148.V148Domains(session), + 151 => new V151.V151Domains(session), 150 => new V150.V150Domains(session), 149 => new V149.V149Domains(session), _ => null diff --git a/dotnet/src/webdriver/DevTools/v148/V148Domains.cs b/dotnet/src/webdriver/DevTools/v151/V151Domains.cs similarity index 74% rename from dotnet/src/webdriver/DevTools/v148/V148Domains.cs rename to dotnet/src/webdriver/DevTools/v151/V151Domains.cs index 85b91c7e87703..41042b5ee52d5 100644 --- a/dotnet/src/webdriver/DevTools/v148/V148Domains.cs +++ b/dotnet/src/webdriver/DevTools/v151/V151Domains.cs @@ -1,4 +1,4 @@ -// +// // Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information @@ -17,38 +17,38 @@ // under the License. // -namespace OpenQA.Selenium.DevTools.V148; +namespace OpenQA.Selenium.DevTools.V151; /// -/// Class containing the domain implementation for version 148 of the DevTools Protocol. +/// Class containing the domain implementation for version 151 of the DevTools Protocol. /// -public class V148Domains : DevToolsDomains +public class V151Domains : DevToolsDomains { private readonly DevToolsSessionDomains domains; - private readonly Lazy network; - private readonly Lazy javaScript; - private readonly Lazy target; - private readonly Lazy log; + private readonly Lazy network; + private readonly Lazy javaScript; + private readonly Lazy target; + private readonly Lazy log; /// - /// Initializes a new instance of the V148Domains class. + /// Initializes a new instance of the V151Domains class. /// /// The DevToolsSession to use with this set of domains. /// If is . - public V148Domains(DevToolsSession session) + public V151Domains(DevToolsSession session) { ArgumentNullException.ThrowIfNull(session); this.domains = new DevToolsSessionDomains(session); - this.network = new Lazy(() => new V148Network(domains.Network, domains.Fetch)); - this.javaScript = new Lazy(() => new V148JavaScript(domains.Runtime, domains.Page)); - this.target = new Lazy(() => new V148Target(domains.Target)); - this.log = new Lazy(() => new V148Log(domains.Log)); + this.network = new Lazy(() => new V151Network(domains.Network, domains.Fetch)); + this.javaScript = new Lazy(() => new V151JavaScript(domains.Runtime, domains.Page)); + this.target = new Lazy(() => new V151Target(domains.Target)); + this.log = new Lazy(() => new V151Log(domains.Log)); } /// /// Gets the DevTools Protocol version for which this class is valid. /// - public static int DevToolsVersion => 148; + public static int DevToolsVersion => 151; /// /// Gets the version-specific domains for the DevTools session. This value must be cast to a version specific type to be at all useful. diff --git a/dotnet/src/webdriver/DevTools/v148/V148JavaScript.cs b/dotnet/src/webdriver/DevTools/v151/V151JavaScript.cs similarity index 94% rename from dotnet/src/webdriver/DevTools/v148/V148JavaScript.cs rename to dotnet/src/webdriver/DevTools/v151/V151JavaScript.cs index 2e4ddc1b759e5..1b915228bf54f 100644 --- a/dotnet/src/webdriver/DevTools/v148/V148JavaScript.cs +++ b/dotnet/src/webdriver/DevTools/v151/V151JavaScript.cs @@ -1,4 +1,4 @@ -// +// // Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information @@ -17,26 +17,26 @@ // under the License. // -using OpenQA.Selenium.DevTools.V148.Page; -using OpenQA.Selenium.DevTools.V148.Runtime; +using OpenQA.Selenium.DevTools.V151.Page; +using OpenQA.Selenium.DevTools.V151.Runtime; -namespace OpenQA.Selenium.DevTools.V148; +namespace OpenQA.Selenium.DevTools.V151; /// -/// Class containing the JavaScript implementation for version 148 of the DevTools Protocol. +/// Class containing the JavaScript implementation for version 151 of the DevTools Protocol. /// -public class V148JavaScript : JavaScript +public class V151JavaScript : JavaScript { private readonly RuntimeAdapter runtime; private readonly PageAdapter page; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The DevTools Protocol adapter for the Runtime domain. /// The DevTools Protocol adapter for the Page domain. /// If or are . - public V148JavaScript(RuntimeAdapter runtime, PageAdapter page) + public V151JavaScript(RuntimeAdapter runtime, PageAdapter page) { ArgumentNullException.ThrowIfNull(runtime); ArgumentNullException.ThrowIfNull(page); diff --git a/dotnet/src/webdriver/DevTools/v148/V148Log.cs b/dotnet/src/webdriver/DevTools/v151/V151Log.cs similarity index 88% rename from dotnet/src/webdriver/DevTools/v148/V148Log.cs rename to dotnet/src/webdriver/DevTools/v151/V151Log.cs index fded3f17375e7..52d7b0466d9cd 100644 --- a/dotnet/src/webdriver/DevTools/v148/V148Log.cs +++ b/dotnet/src/webdriver/DevTools/v151/V151Log.cs @@ -1,4 +1,4 @@ -// +// // Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information @@ -17,23 +17,23 @@ // under the License. // -using OpenQA.Selenium.DevTools.V148.Log; +using OpenQA.Selenium.DevTools.V151.Log; -namespace OpenQA.Selenium.DevTools.V148; +namespace OpenQA.Selenium.DevTools.V151; /// -/// Class containing the browser's log as referenced by version 148 of the DevTools Protocol. +/// Class containing the browser's log as referenced by version 151 of the DevTools Protocol. /// -public class V148Log : DevTools.Log +public class V151Log : DevTools.Log { private readonly LogAdapter adapter; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The adapter for the Log domain. /// If is . - public V148Log(LogAdapter adapter) + public V151Log(LogAdapter adapter) { ArgumentNullException.ThrowIfNull(adapter); this.adapter = adapter; diff --git a/dotnet/src/webdriver/DevTools/v148/V148Network.cs b/dotnet/src/webdriver/DevTools/v151/V151Network.cs similarity index 95% rename from dotnet/src/webdriver/DevTools/v148/V148Network.cs rename to dotnet/src/webdriver/DevTools/v151/V151Network.cs index c52a8003d35d2..a8b11c5d43b88 100644 --- a/dotnet/src/webdriver/DevTools/v148/V148Network.cs +++ b/dotnet/src/webdriver/DevTools/v151/V151Network.cs @@ -1,4 +1,4 @@ -// +// // Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information @@ -18,26 +18,26 @@ // using System.Text; -using OpenQA.Selenium.DevTools.V148.Fetch; -using OpenQA.Selenium.DevTools.V148.Network; +using OpenQA.Selenium.DevTools.V151.Fetch; +using OpenQA.Selenium.DevTools.V151.Network; -namespace OpenQA.Selenium.DevTools.V148; +namespace OpenQA.Selenium.DevTools.V151; /// -/// Class providing functionality for manipulating network calls using version 148 of the DevTools Protocol +/// Class providing functionality for manipulating network calls using version 151 of the DevTools Protocol /// -public class V148Network : DevTools.Network +public class V151Network : DevTools.Network { private readonly FetchAdapter fetch; private readonly NetworkAdapter network; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The adapter for the Network domain. /// The adapter for the Fetch domain. /// If or are . - public V148Network(NetworkAdapter network, FetchAdapter fetch) + public V151Network(NetworkAdapter network, FetchAdapter fetch) { ArgumentNullException.ThrowIfNull(network); ArgumentNullException.ThrowIfNull(fetch); @@ -231,9 +231,9 @@ public override async Task ContinueWithAuth(string requestId, string? userName, await fetch.ContinueWithAuth(new ContinueWithAuthCommandSettings() { RequestId = requestId, - AuthChallengeResponse = new V148.Fetch.AuthChallengeResponse() + AuthChallengeResponse = new V151.Fetch.AuthChallengeResponse() { - Response = V148.Fetch.AuthChallengeResponseResponseValues.ProvideCredentials, + Response = V151.Fetch.AuthChallengeResponseResponseValues.ProvideCredentials, Username = userName, Password = password } @@ -250,9 +250,9 @@ public override async Task CancelAuth(string requestId) await fetch.ContinueWithAuth(new ContinueWithAuthCommandSettings() { RequestId = requestId, - AuthChallengeResponse = new OpenQA.Selenium.DevTools.V148.Fetch.AuthChallengeResponse() + AuthChallengeResponse = new OpenQA.Selenium.DevTools.V151.Fetch.AuthChallengeResponse() { - Response = V148.Fetch.AuthChallengeResponseResponseValues.CancelAuth + Response = V151.Fetch.AuthChallengeResponseResponseValues.CancelAuth } }).ConfigureAwait(false); } diff --git a/dotnet/src/webdriver/DevTools/v148/V148Target.cs b/dotnet/src/webdriver/DevTools/v151/V151Target.cs similarity index 94% rename from dotnet/src/webdriver/DevTools/v148/V148Target.cs rename to dotnet/src/webdriver/DevTools/v151/V151Target.cs index de2a4bbb884c0..65d58f1650bbb 100644 --- a/dotnet/src/webdriver/DevTools/v148/V148Target.cs +++ b/dotnet/src/webdriver/DevTools/v151/V151Target.cs @@ -1,4 +1,4 @@ -// +// // Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information @@ -18,23 +18,23 @@ // using System.Collections.ObjectModel; -using OpenQA.Selenium.DevTools.V148.Target; +using OpenQA.Selenium.DevTools.V151.Target; -namespace OpenQA.Selenium.DevTools.V148; +namespace OpenQA.Selenium.DevTools.V151; /// -/// Class providing functionality for manipulating targets for version 148 of the DevTools Protocol +/// Class providing functionality for manipulating targets for version 151 of the DevTools Protocol /// -public class V148Target : DevTools.Target +public class V151Target : DevTools.Target { private readonly TargetAdapter adapter; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The adapter for the Target domain. /// If is . - public V148Target(TargetAdapter adapter) + public V151Target(TargetAdapter adapter) { ArgumentNullException.ThrowIfNull(adapter); this.adapter = adapter; diff --git a/dotnet/src/webdriver/DriverService.cs b/dotnet/src/webdriver/DriverService.cs index 6d80dceacd2ad..7a643a52671ef 100644 --- a/dotnet/src/webdriver/DriverService.cs +++ b/dotnet/src/webdriver/DriverService.cs @@ -147,6 +147,23 @@ public int ProcessId /// public string? DriverServicePath { get; set; } + /// + /// Gets the name of the environment variable used to specify the driver executable location, + /// or if the service does not support one. + /// + protected virtual string? DriverServiceEnvironmentVariableName => null; + + /// + /// Gets the driver executable path from , or + /// if it is unset. When set, Selenium Manager is not invoked. + /// + internal string? DriverPathFromEnvironment => + this.DriverServiceEnvironmentVariableName is string name + && Environment.GetEnvironmentVariable(name) is string path + && !string.IsNullOrWhiteSpace(path) + ? path + : null; + /// /// Gets the command-line arguments for the driver service. /// @@ -218,6 +235,15 @@ public async ValueTask StartAsync(CancellationToken cancellationToken = default) this.driverServiceProcess.StartInfo.FileName = Path.Combine(this.DriverServicePath, this.DriverServiceExecutableName); } + else if (this.DriverPathFromEnvironment is string environmentDriverPath) + { + if (_logger.IsEnabled(LogEventLevel.Debug)) + { + _logger.Debug($"Skipping Selenium Manager; using driver from {this.DriverServiceEnvironmentVariableName}: {environmentDriverPath}"); + } + + this.driverServiceProcess.StartInfo.FileName = environmentDriverPath; + } else { var driverFinder = new DriverFinder(this.GetDefaultDriverOptions()); diff --git a/dotnet/src/webdriver/Edge/EdgeDriverService.cs b/dotnet/src/webdriver/Edge/EdgeDriverService.cs index 74e59de4f80a3..5e03baef90b7a 100644 --- a/dotnet/src/webdriver/Edge/EdgeDriverService.cs +++ b/dotnet/src/webdriver/Edge/EdgeDriverService.cs @@ -40,6 +40,9 @@ private EdgeDriverService(string? executablePath, string? executableFileName, in { } + /// + protected override string DriverServiceEnvironmentVariableName => "SE_EDGEDRIVER"; + /// protected override DriverOptions GetDefaultDriverOptions() { diff --git a/dotnet/src/webdriver/Firefox/FirefoxDriver.cs b/dotnet/src/webdriver/Firefox/FirefoxDriver.cs index 685612893029e..af16ee93ae2d0 100644 --- a/dotnet/src/webdriver/Firefox/FirefoxDriver.cs +++ b/dotnet/src/webdriver/Firefox/FirefoxDriver.cs @@ -202,7 +202,7 @@ private static async Task GenerateDriverServiceCommandExecutor ArgumentNullException.ThrowIfNull(service); ArgumentNullException.ThrowIfNull(options); - if (service.DriverServicePath == null) + if (service.DriverServicePath == null && service.DriverPathFromEnvironment == null) { DriverFinder finder = new DriverFinder(options); string fullServicePath = await finder.GetDriverPathAsync().ConfigureAwait(false); diff --git a/dotnet/src/webdriver/Firefox/FirefoxDriverService.cs b/dotnet/src/webdriver/Firefox/FirefoxDriverService.cs index 3f6a75f6ccf09..7d558a176a1c0 100644 --- a/dotnet/src/webdriver/Firefox/FirefoxDriverService.cs +++ b/dotnet/src/webdriver/Firefox/FirefoxDriverService.cs @@ -47,6 +47,9 @@ private FirefoxDriverService(string? executablePath, string? executableFileName, { } + /// + protected override string DriverServiceEnvironmentVariableName => "SE_GECKODRIVER"; + /// protected override DriverOptions GetDefaultDriverOptions() { diff --git a/dotnet/src/webdriver/Firefox/FirefoxExtension.cs b/dotnet/src/webdriver/Firefox/FirefoxExtension.cs index be47e57e8a8f5..ce75367008141 100644 --- a/dotnet/src/webdriver/Firefox/FirefoxExtension.cs +++ b/dotnet/src/webdriver/Firefox/FirefoxExtension.cs @@ -29,6 +29,7 @@ namespace OpenQA.Selenium.Firefox; /// /// Provides the ability to install extensions into a . /// +[Obsolete("Use FirefoxDriver.InstallAddOnFromFile instead.")] public class FirefoxExtension { private const string EmNamespaceUri = "http://www.mozilla.org/2004/em-rdf#"; diff --git a/dotnet/src/webdriver/Firefox/FirefoxProfile.cs b/dotnet/src/webdriver/Firefox/FirefoxProfile.cs index 17619687051e2..ce0547d767450 100644 --- a/dotnet/src/webdriver/Firefox/FirefoxProfile.cs +++ b/dotnet/src/webdriver/Firefox/FirefoxProfile.cs @@ -33,7 +33,9 @@ public class FirefoxProfile private readonly string? sourceProfileDir; private readonly bool deleteSource; private readonly Preferences profilePreferences; +#pragma warning disable CS0618 // Type or member is obsolete private readonly Dictionary extensions = new Dictionary(); +#pragma warning restore CS0618 // Type or member is obsolete /// /// Initializes a new instance of the class. @@ -103,11 +105,14 @@ public static FirefoxProfile FromBase64String(string base64) /// /// The path to the new extension /// If is . + [Obsolete("Use FirefoxDriver.InstallAddOnFromFile instead.")] public void AddExtension(string extensionToInstall) { ArgumentNullException.ThrowIfNull(extensionToInstall); +#pragma warning disable CS0618 // Type or member is obsolete this.extensions.Add(Path.GetFileNameWithoutExtension(extensionToInstall), new FirefoxExtension(extensionToInstall)); +#pragma warning restore CS0618 // Type or member is obsolete } /// @@ -236,10 +241,12 @@ private void DeleteLockFiles(string profileDirectory) /// private void InstallExtensions(string profileDirectory) { +#pragma warning disable CS0618 // Type or member is obsolete foreach (string extensionKey in this.extensions.Keys) { this.extensions[extensionKey].Install(profileDirectory); } +#pragma warning restore CS0618 // Type or member is obsolete } /// diff --git a/dotnet/src/webdriver/IE/InternetExplorerDriver.cs b/dotnet/src/webdriver/IE/InternetExplorerDriver.cs index dfcd88c350b22..79d9308fa7e1b 100644 --- a/dotnet/src/webdriver/IE/InternetExplorerDriver.cs +++ b/dotnet/src/webdriver/IE/InternetExplorerDriver.cs @@ -162,7 +162,7 @@ private static async Task GenerateDriverServiceCommandExecutor ArgumentNullException.ThrowIfNull(service); ArgumentNullException.ThrowIfNull(options); - if (service.DriverServicePath == null) + if (service.DriverServicePath == null && service.DriverPathFromEnvironment == null) { DriverFinder finder = new DriverFinder(options); string fullServicePath = await finder.GetDriverPathAsync().ConfigureAwait(false); diff --git a/dotnet/src/webdriver/IE/InternetExplorerDriverService.cs b/dotnet/src/webdriver/IE/InternetExplorerDriverService.cs index afd534c9088cc..856af585ae658 100644 --- a/dotnet/src/webdriver/IE/InternetExplorerDriverService.cs +++ b/dotnet/src/webdriver/IE/InternetExplorerDriverService.cs @@ -41,6 +41,9 @@ private InternetExplorerDriverService(string? executablePath, string? executable { } + /// + protected override string DriverServiceEnvironmentVariableName => "SE_IEDRIVER"; + /// protected override DriverOptions GetDefaultDriverOptions() { diff --git a/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs b/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs index 2066ae14a3f3f..c226e8a943d14 100644 --- a/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs +++ b/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs @@ -111,6 +111,11 @@ public HttpCommandExecutor(Uri addressOfRemoteServer, TimeSpan timeout, bool ena /// public string UserAgent { get; set; } + /// + /// Gets the address of the remote end this executor connects to. + /// + internal Uri RemoteServerUri => this.remoteServerUri; + /// /// Gets the repository of objects containing information about commands. /// diff --git a/dotnet/src/webdriver/Remote/RemoteWebDriver.cs b/dotnet/src/webdriver/Remote/RemoteWebDriver.cs index 0ea164972c129..79200ecb4068e 100644 --- a/dotnet/src/webdriver/Remote/RemoteWebDriver.cs +++ b/dotnet/src/webdriver/Remote/RemoteWebDriver.cs @@ -21,7 +21,6 @@ using System.Diagnostics.CodeAnalysis; using System.IO.Compression; using OpenQA.Selenium.DevTools; -using OpenQA.Selenium.Internal.Logging; namespace OpenQA.Selenium.Remote; @@ -60,8 +59,6 @@ namespace OpenQA.Selenium.Remote; /// public class RemoteWebDriver : WebDriver, IDevTools, IHasDownloads { - private static readonly ILogger _logger = OpenQA.Selenium.Internal.Logging.Log.GetLogger(typeof(RemoteWebDriver)); - /// /// The name of the Selenium grid remote DevTools end point capability. /// @@ -426,10 +423,7 @@ public DevToolsSession GetDevToolsSession() { if (this.Capabilities.GetCapability(CapabilityType.BrowserName) is "firefox") { - if (_logger.IsEnabled(LogEventLevel.Warn)) - { - _logger.Warn("CDP support for Firefox is deprecated and will be removed in future versions. Please switch to WebDriver BiDi."); - } + throw new WebDriverException("CDP support for Firefox has been removed. Please switch to WebDriver BiDi."); } return GetDevToolsSession(new DevToolsOptions() { ProtocolVersion = DevToolsSession.AutoDetectDevToolsProtocolVersion }); @@ -445,6 +439,11 @@ public DevToolsSession GetDevToolsSession(DevToolsOptions options) { ArgumentNullException.ThrowIfNull(options); + if (this.Capabilities.GetCapability(CapabilityType.BrowserName) is "firefox") + { + throw new WebDriverException("CDP support for Firefox has been removed. Please switch to WebDriver BiDi."); + } + if (this.devToolsSession == null) { object? debuggerAddressObject = this.Capabilities.GetCapability(RemoteDevToolsEndPointCapabilityName); diff --git a/dotnet/src/webdriver/Safari/SafariDriver.cs b/dotnet/src/webdriver/Safari/SafariDriver.cs index 33c64b082cc89..52b95359b1cf5 100644 --- a/dotnet/src/webdriver/Safari/SafariDriver.cs +++ b/dotnet/src/webdriver/Safari/SafariDriver.cs @@ -167,7 +167,7 @@ private static async Task GenerateDriverServiceCommandExecutor ArgumentNullException.ThrowIfNull(service); ArgumentNullException.ThrowIfNull(options); - if (service.DriverServicePath == null) + if (service.DriverServicePath == null && service.DriverPathFromEnvironment == null) { DriverFinder finder = new DriverFinder(options); string fullServicePath = await finder.GetDriverPathAsync().ConfigureAwait(false); diff --git a/dotnet/src/webdriver/Safari/SafariDriverService.cs b/dotnet/src/webdriver/Safari/SafariDriverService.cs index 5c4f4af094e57..23df9008711a6 100644 --- a/dotnet/src/webdriver/Safari/SafariDriverService.cs +++ b/dotnet/src/webdriver/Safari/SafariDriverService.cs @@ -47,6 +47,9 @@ private SafariDriverService(string? executablePath, string? executableFileName, { } + /// + protected override string DriverServiceEnvironmentVariableName => "SE_SAFARIDRIVER"; + /// protected override DriverOptions GetDefaultDriverOptions() { diff --git a/dotnet/src/webdriver/WebDriver.cs b/dotnet/src/webdriver/WebDriver.cs index 6e01e32d72cc0..a1358d21a5ef9 100644 --- a/dotnet/src/webdriver/WebDriver.cs +++ b/dotnet/src/webdriver/WebDriver.cs @@ -604,6 +604,11 @@ protected void StartSession(ICapabilities capabilities) { Dictionary matchCapabilities = this.GetCapabilitiesDictionary(capabilities); + if (this.CommandExecutor is Remote.HttpCommandExecutor httpExecutor) + { + matchCapabilities["se:remoteUrl"] = httpExecutor.RemoteServerUri.AbsoluteUri; + } + List firstMatchCapabilitiesList = new List(); firstMatchCapabilitiesList.Add(matchCapabilities); @@ -614,7 +619,35 @@ protected void StartSession(ICapabilities capabilities) } else { - parameters.Add("capabilities", remoteSettings.ToDictionary()); + Dictionary remoteSettingsDictionary = remoteSettings.ToDictionary(); + + // Advertise se:remoteUrl on the caller's behalf, as every other binding does. It must be + // nested in alwaysMatch (the Grid drops top-level metadata), built into a fresh copy so + // the caller-owned RemoteSessionSettings is not mutated. Only a matched capability counts + // as explicit here: a se:remoteUrl set via AddMetadataSetting stays top-level, is ignored + // by the Grid, and does not suppress this injection (the executor URL stays authoritative). + // Skip only when se:remoteUrl already lives in alwaysMatch/firstMatch, to preserve that + // value and avoid an alwaysMatch/firstMatch overlap. If only one of several firstMatch + // alternatives sets it explicitly, injection is suppressed for all of them; that + // multi-alternative case is intentionally not supported. + if (this.CommandExecutor is Remote.HttpCommandExecutor remoteHttpExecutor + && !ContainsMatchCapability(remoteSettingsDictionary, "se:remoteUrl")) + { + Dictionary alwaysMatch = new Dictionary(); + if (remoteSettingsDictionary.TryGetValue("alwaysMatch", out object? existingAlwaysMatch) + && existingAlwaysMatch is IDictionary existingCapabilities) + { + foreach (KeyValuePair capability in existingCapabilities) + { + alwaysMatch[capability.Key] = capability.Value; + } + } + + alwaysMatch["se:remoteUrl"] = remoteHttpExecutor.RemoteServerUri.AbsoluteUri; + remoteSettingsDictionary["alwaysMatch"] = alwaysMatch; + } + + parameters.Add("capabilities", remoteSettingsDictionary); } Response response = this.Execute(DriverCommand.NewSession, parameters); @@ -632,6 +665,31 @@ protected void StartSession(ICapabilities capabilities) this.SessionId = new SessionId(sessionId); } + private static bool ContainsMatchCapability(Dictionary capabilitiesDictionary, string capabilityName) + { + if (capabilitiesDictionary.TryGetValue("alwaysMatch", out object? alwaysMatch) + && alwaysMatch is IDictionary alwaysMatchCapabilities + && alwaysMatchCapabilities.ContainsKey(capabilityName)) + { + return true; + } + + if (capabilitiesDictionary.TryGetValue("firstMatch", out object? firstMatch) + && firstMatch is IEnumerable firstMatchCandidates) + { + foreach (object candidate in firstMatchCandidates) + { + if (candidate is IDictionary firstMatchCapabilities + && firstMatchCapabilities.ContainsKey(capabilityName)) + { + return true; + } + } + } + + return false; + } + /// /// Gets the capabilities as a dictionary. /// diff --git a/dotnet/test/webdriver/BiDi/Session/SessionTests.cs b/dotnet/test/webdriver/BiDi/Session/SessionTests.cs index d7dacd3ef16c3..c27f3421f7328 100644 --- a/dotnet/test/webdriver/BiDi/Session/SessionTests.cs +++ b/dotnet/test/webdriver/BiDi/Session/SessionTests.cs @@ -137,10 +137,10 @@ public async Task EventStreamCancellationTokenFiresDuringEnumeration() await using var sub = await bidi.Log.EntryAdded.StreamAsync(); - Assert.ThrowsAsync(async () => + Assert.That(async () => { await foreach (var _ in sub.ReadAllAsync(cts.Token)) { } - }); + }, Throws.InstanceOf()); } [Test] diff --git a/dotnet/test/webdriver/BiDi/SessionUnitTests.cs b/dotnet/test/webdriver/BiDi/SessionUnitTests.cs index 8afa9eb9a9c9c..ac70fdafccf1a 100644 --- a/dotnet/test/webdriver/BiDi/SessionUnitTests.cs +++ b/dotnet/test/webdriver/BiDi/SessionUnitTests.cs @@ -34,7 +34,8 @@ class SessionUnitTests public async Task SetUp() { _transport = new FakeTransport(); - _bidi = await Selenium.BiDi.BiDi.ConnectAsync(new Uri("ws://fake"), opts => opts.UseTransport(() => _transport)); + _bidi = await Selenium.BiDi.BiDi.ConnectAsync(new Uri("ws://fake"), opts => + opts.UseTransport(_ => (_, _) => Task.FromResult(_transport))); } [TearDown] @@ -43,14 +44,6 @@ public async Task TearDown() await _bidi.DisposeAsync(); } - [Test] - public void ShouldRespectCommandTimeout() - { - Assert.That( - () => _bidi.StatusAsync(new() { Timeout = TimeSpan.FromMilliseconds(1) }), - Throws.InstanceOf()); - } - [Test] public void ShouldRespectCancellationToken() { diff --git a/dotnet/test/webdriver/DevTools/DevToolsConsoleTests.cs b/dotnet/test/webdriver/DevTools/DevToolsConsoleTests.cs index 4f33b9500f4ff..5ad320613aa10 100644 --- a/dotnet/test/webdriver/DevTools/DevToolsConsoleTests.cs +++ b/dotnet/test/webdriver/DevTools/DevToolsConsoleTests.cs @@ -17,7 +17,7 @@ // under the License. // -using CurrentCdpVersion = OpenQA.Selenium.DevTools.V150; +using CurrentCdpVersion = OpenQA.Selenium.DevTools.V151; namespace OpenQA.Selenium.Tests.DevTools; diff --git a/dotnet/test/webdriver/DevTools/DevToolsLogTests.cs b/dotnet/test/webdriver/DevTools/DevToolsLogTests.cs index 48e256b931549..a69b0392489bb 100644 --- a/dotnet/test/webdriver/DevTools/DevToolsLogTests.cs +++ b/dotnet/test/webdriver/DevTools/DevToolsLogTests.cs @@ -17,7 +17,7 @@ // under the License. // -using CurrentCdpVersion = OpenQA.Selenium.DevTools.V150; +using CurrentCdpVersion = OpenQA.Selenium.DevTools.V151; namespace OpenQA.Selenium.Tests.DevTools; diff --git a/dotnet/test/webdriver/DevTools/DevToolsNetworkTests.cs b/dotnet/test/webdriver/DevTools/DevToolsNetworkTests.cs index 503e6f7a6a73e..a3e988037976f 100644 --- a/dotnet/test/webdriver/DevTools/DevToolsNetworkTests.cs +++ b/dotnet/test/webdriver/DevTools/DevToolsNetworkTests.cs @@ -17,7 +17,7 @@ // under the License. // -using CurrentCdpVersion = OpenQA.Selenium.DevTools.V150; +using CurrentCdpVersion = OpenQA.Selenium.DevTools.V151; namespace OpenQA.Selenium.Tests.DevTools; diff --git a/dotnet/test/webdriver/DevTools/DevToolsPerformanceTests.cs b/dotnet/test/webdriver/DevTools/DevToolsPerformanceTests.cs index e8e15993ae735..604ae8e93a8b1 100644 --- a/dotnet/test/webdriver/DevTools/DevToolsPerformanceTests.cs +++ b/dotnet/test/webdriver/DevTools/DevToolsPerformanceTests.cs @@ -17,7 +17,7 @@ // under the License. // -using CurrentCdpVersion = OpenQA.Selenium.DevTools.V150; +using CurrentCdpVersion = OpenQA.Selenium.DevTools.V151; namespace OpenQA.Selenium.Tests.DevTools; diff --git a/dotnet/test/webdriver/DevTools/DevToolsProfilerTests.cs b/dotnet/test/webdriver/DevTools/DevToolsProfilerTests.cs index c84a3420ddbd7..eefc84369678f 100644 --- a/dotnet/test/webdriver/DevTools/DevToolsProfilerTests.cs +++ b/dotnet/test/webdriver/DevTools/DevToolsProfilerTests.cs @@ -17,7 +17,7 @@ // under the License. // -using CurrentCdpVersion = OpenQA.Selenium.DevTools.V150; +using CurrentCdpVersion = OpenQA.Selenium.DevTools.V151; namespace OpenQA.Selenium.Tests.DevTools; diff --git a/dotnet/test/webdriver/DevTools/DevToolsSecurityTests.cs b/dotnet/test/webdriver/DevTools/DevToolsSecurityTests.cs index a6a5ae7de1d3d..3dbb0fddfaa30 100644 --- a/dotnet/test/webdriver/DevTools/DevToolsSecurityTests.cs +++ b/dotnet/test/webdriver/DevTools/DevToolsSecurityTests.cs @@ -17,7 +17,7 @@ // under the License. // -using CurrentCdpVersion = OpenQA.Selenium.DevTools.V150; +using CurrentCdpVersion = OpenQA.Selenium.DevTools.V151; namespace OpenQA.Selenium.Tests.DevTools; diff --git a/dotnet/test/webdriver/DevTools/DevToolsTabsTests.cs b/dotnet/test/webdriver/DevTools/DevToolsTabsTests.cs index 7cbcfb54640b0..125b1b91f2b86 100644 --- a/dotnet/test/webdriver/DevTools/DevToolsTabsTests.cs +++ b/dotnet/test/webdriver/DevTools/DevToolsTabsTests.cs @@ -17,7 +17,7 @@ // under the License. // -using CurrentCdpVersion = OpenQA.Selenium.DevTools.V150; +using CurrentCdpVersion = OpenQA.Selenium.DevTools.V151; namespace OpenQA.Selenium.Tests.DevTools; diff --git a/dotnet/test/webdriver/DevTools/DevToolsTargetTests.cs b/dotnet/test/webdriver/DevTools/DevToolsTargetTests.cs index 41b356211818c..1bba68e771a15 100644 --- a/dotnet/test/webdriver/DevTools/DevToolsTargetTests.cs +++ b/dotnet/test/webdriver/DevTools/DevToolsTargetTests.cs @@ -17,14 +17,14 @@ // under the License. // -using CurrentCdpVersion = OpenQA.Selenium.DevTools.V150; +using CurrentCdpVersion = OpenQA.Selenium.DevTools.V151; namespace OpenQA.Selenium.Tests.DevTools; [TestFixture] public class DevToolsTargetTests : DevToolsTestFixture { - private const int id = 150; + private const int id = 151; [Test] [IgnoreBrowser(Browser.IE, "IE does not support Chrome DevTools Protocol")] diff --git a/dotnet/test/webdriver/DriverServiceTests.cs b/dotnet/test/webdriver/DriverServiceTests.cs new file mode 100644 index 0000000000000..6d1ba694c4018 --- /dev/null +++ b/dotnet/test/webdriver/DriverServiceTests.cs @@ -0,0 +1,61 @@ +// +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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. +// + +using System.ComponentModel; +using System.IO; +using OpenQA.Selenium.Chrome; +using OpenQA.Selenium.Edge; +using OpenQA.Selenium.Firefox; +using OpenQA.Selenium.IE; +using OpenQA.Selenium.Safari; + +namespace OpenQA.Selenium.Tests; + +[TestFixture] +[NonParallelizable] +public class DriverServiceTests +{ + private static IEnumerable DriverServices() + { + yield return new TestCaseData((Func)ChromeDriverService.CreateDefaultService, "SE_CHROMEDRIVER").SetName("Chrome"); + yield return new TestCaseData((Func)EdgeDriverService.CreateDefaultService, "SE_EDGEDRIVER").SetName("Edge"); + yield return new TestCaseData((Func)FirefoxDriverService.CreateDefaultService, "SE_GECKODRIVER").SetName("Firefox"); + yield return new TestCaseData((Func)InternetExplorerDriverService.CreateDefaultService, "SE_IEDRIVER").SetName("InternetExplorer"); + yield return new TestCaseData((Func)SafariDriverService.CreateDefaultService, "SE_SAFARIDRIVER").SetName("Safari"); + } + + [TestCaseSource(nameof(DriverServices))] + public void StartsDriverFromEnvironmentVariable(Func createService, string environmentVariable) + { + string original = Environment.GetEnvironmentVariable(environmentVariable); + string expectedPath = Path.Combine("path", "to", "driver"); + try + { + Environment.SetEnvironmentVariable(environmentVariable, expectedPath); + + Assert.That( + async () => await createService().StartAsync(), + Throws.InstanceOf().With.Message.Contains(expectedPath)); + } + finally + { + Environment.SetEnvironmentVariable(environmentVariable, original); + } + } +} diff --git a/dotnet/version.bzl b/dotnet/version.bzl index 44c3c2d443240..280b895f6165b 100644 --- a/dotnet/version.bzl +++ b/dotnet/version.bzl @@ -5,7 +5,7 @@ SE_VERSION = "4.47.0-nightly202607110055" SUPPORTED_DEVTOOLS_VERSIONS = [ "v149", "v150", - "v148", + "v151", ] ASSEMBLY_COMPANY = "Selenium Committers" diff --git a/java/java-dev.iml b/java/java-dev.iml new file mode 100644 index 0000000000000..605501dd9adfa --- /dev/null +++ b/java/java-dev.iml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/java.iml b/java/java.iml index dcdaca645e3e4..0477414f0cec9 100644 --- a/java/java.iml +++ b/java/java.iml @@ -5,20 +5,16 @@ - - + - - - - + - +