diff --git a/.github/release.yml b/.github/release.yml index 8f13b55a4e290..95dee31ec9630 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -1,6 +1,6 @@ changelog: exclude: labels: - -dependencies + - dependencies authors: - selenium-ci diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index 428ac9ae6f87a..7b86e983e85d1 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -70,7 +70,7 @@ on: jobs: bazel: name: ${{ inputs.name }} - runs-on: ${{ inputs.os == 'macos' && 'macos-13' || format('{0}-latest', inputs.os) }} + runs-on: ${{ format('{0}-latest', inputs.os) }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SEL_M2_USER: ${{ secrets.SEL_M2_USER }} @@ -116,7 +116,7 @@ jobs: node-version: ${{ inputs.node-version }} - name: Setup Bazel with caching if: inputs.caching - uses: bazel-contrib/setup-bazel@0.8.5 + uses: bazel-contrib/setup-bazel@0.9.1 with: bazelisk-cache: true bazelrc: common --color=yes @@ -130,7 +130,7 @@ jobs: repository-cache: true - name: Setup Bazel without caching if: inputs.caching == false - uses: bazel-contrib/setup-bazel@0.8.5 + uses: bazel-contrib/setup-bazel@0.9.1 with: bazelrc: common --color=yes - name: Setup Fluxbox and Xvfb diff --git a/.github/workflows/ci-dotnet.yml b/.github/workflows/ci-dotnet.yml index 65debef8c10e4..1e2a2c259382d 100644 --- a/.github/workflows/ci-dotnet.yml +++ b/.github/workflows/ci-dotnet.yml @@ -23,4 +23,5 @@ jobs: java-version: 17 os: windows run: | + fsutil 8dot3name set 0 bazel test //dotnet/test/common:ElementFindingTest-firefox //dotnet/test/common:ElementFindingTest-chrome --pin_browsers=true diff --git a/.github/workflows/ci-java.yml b/.github/workflows/ci-java.yml index 569d68d562440..1ebd054c05089 100644 --- a/.github/workflows/ci-java.yml +++ b/.github/workflows/ci-java.yml @@ -22,6 +22,7 @@ jobs: # https://github.com/bazelbuild/rules_jvm_external/issues/1046 java-version: 17 run: | + fsutil 8dot3name set 0 bazel test --flaky_test_attempts 3 //java/test/org/openqa/selenium/chrome:ChromeDriverFunctionalTest ` //java/test/org/openqa/selenium/federatedcredentialmanagement:FederatedCredentialManagementTest ` //java/test/org/openqa/selenium/firefox:FirefoxDriverBuilderTest ` diff --git a/.github/workflows/ci-python.yml b/.github/workflows/ci-python.yml index 9749fd4e7ad82..e0c906c4d8885 100644 --- a/.github/workflows/ci-python.yml +++ b/.github/workflows/ci-python.yml @@ -16,7 +16,7 @@ jobs: docs: name: Documentation needs: build - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Checkout source tree uses: actions/checkout@v4 @@ -36,7 +36,7 @@ jobs: lint: name: Lint needs: build - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Checkout source tree uses: actions/checkout@v4 @@ -58,7 +58,7 @@ jobs: mypy: name: Mypy needs: build - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Checkout source tree uses: actions/checkout@v4 @@ -81,8 +81,38 @@ jobs: name: Remote Tests needs: build uses: ./.github/workflows/bazel.yml + strategy: + fail-fast: false + matrix: + include: + - browser: firefox with: - name: Integration Tests (remote) - browser: firefox - cache-key: py-remote + name: Integration Tests (remote, ${{ matrix.browser }}) + browser: ${{ matrix.browser }} + cache-key: py-remote-${{ matrix.browser }} run: bazel test --local_test_jobs 1 --flaky_test_attempts 3 //py:test-remote + + browser-tests: + name: Browser Tests + needs: build + uses: ./.github/workflows/bazel.yml + strategy: + fail-fast: false + matrix: + include: + - browser: safari + os: macos + - browser: chrome + os: ubuntu + - browser: edge + os: ubuntu + - browser: firefox + os: ubuntu + with: + name: Integration Tests (${{ matrix.browser }}, ${{ matrix.os }}) + browser: ${{ matrix.browser }} + os: ${{ matrix.os }} + cache-key: py-browser-${{ matrix.browser }} + run: | + bazel test --local_test_jobs 1 --flaky_test_attempts 3 //py:common-${{ matrix.browser }}-bidi + bazel test --local_test_jobs 1 --flaky_test_attempts 3 //py:test-${{ matrix.browser }} diff --git a/.github/workflows/ci-rbe.yml b/.github/workflows/ci-rbe.yml index 382aea6409f2a..2e15949925580 100644 --- a/.github/workflows/ci-rbe.yml +++ b/.github/workflows/ci-rbe.yml @@ -15,7 +15,7 @@ jobs: with: name: Check format script run caching: false - ruby-version: jruby-9.4.5.0 + ruby-version: jruby-9.4.8.0 run: ./scripts/github-actions/check-format.sh test: @@ -25,5 +25,5 @@ jobs: with: name: All RBE tests caching: false - ruby-version: jruby-9.4.5.0 + ruby-version: jruby-9.4.8.0 run: ./scripts/github-actions/ci-build.sh diff --git a/.github/workflows/ci-ruby.yml b/.github/workflows/ci-ruby.yml index 419e17df5065c..88cf3512ebfba 100644 --- a/.github/workflows/ci-ruby.yml +++ b/.github/workflows/ci-ruby.yml @@ -39,17 +39,17 @@ jobs: fail-fast: false matrix: include: - - ruby-version: 3.0.6 + - ruby-version: 3.1.6 os: ubuntu - - ruby-version: 3.0.6 + - ruby-version: 3.1.6 os: windows - - ruby-version: 3.0.6 + - ruby-version: 3.1.6 os: macos - - ruby-version: 3.3.0 + - ruby-version: 3.3.5 os: ubuntu - - ruby-version: jruby-9.4.5.0 + - ruby-version: jruby-9.4.8.0 os: ubuntu - - ruby-version: truffleruby-23.1.1 + - ruby-version: truffleruby-24.1.1 os: ubuntu with: name: Unit Tests (${{ matrix.ruby-version }}, ${{ matrix.os }}) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2175fe47d3b96..db2c0a78857de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: with: fetch-depth: 50 - name: Setup Bazel - uses: bazel-contrib/setup-bazel@0.8.5 + uses: bazel-contrib/setup-bazel@0.9.1 with: bazelisk-cache: true cache-version: 2 diff --git a/.github/workflows/label-commenter.yml b/.github/workflows/label-commenter.yml index fddc487a797ef..205647d30daa0 100644 --- a/.github/workflows/label-commenter.yml +++ b/.github/workflows/label-commenter.yml @@ -12,7 +12,7 @@ permissions: jobs: comment: if: github.repository_owner == 'seleniumhq' - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Label Commenter diff --git a/.skipped-tests b/.skipped-tests index 82b2d1c796077..3a6807a2cbb1d 100644 --- a/.skipped-tests +++ b/.skipped-tests @@ -31,6 +31,8 @@ -//py:test-chrome-test/selenium/webdriver/chrome/chrome_launcher_tests.py -//py:test-chrome-test/selenium/webdriver/chrome/chrome_service_tests.py -//py:test-chrome-test/selenium/webdriver/chrome/proxy_tests.py +-//py:test-edge-test/selenium/webdriver/edge/edge_launcher_tests.py +-//py:test-edge-test/selenium/webdriver/edge/edge_service_tests.py -//rb/spec/integration/selenium/webdriver/chrome:service-chrome -//rb/spec/integration/selenium/webdriver/chrome:service-chrome-bidi -//rb/spec/integration/selenium/webdriver/chrome:service-chrome-remote diff --git a/AUTHORS b/AUTHORS index 6ed08840399de..c5dd7ca1b1756 100644 --- a/AUTHORS +++ b/AUTHORS @@ -91,6 +91,7 @@ Ashley Trinh Aslak Hellesøy asmundak Atsushi Tatsuma +Augustin Gottlieb <33221555+aguspe@users.noreply.github.com> Augustin Gottlieb Pequeno <33221555+aguspe@users.noreply.github.com> Aurélien Pupier Austin Michael Wilkins <42476341+amwilkins@users.noreply.github.com> @@ -106,6 +107,7 @@ bgermann bhecquet bhkwan Bill Agee +BlitzDestroyer <143762104+BlitzDestroyer@users.noreply.github.com> bob Bob Baron Bob Lubecker @@ -199,6 +201,7 @@ Darrin Cherry Dave Hoover Dave Hunt daviande +David Bernhard David Burns David English David Fischer @@ -696,6 +699,7 @@ PombaM Potapov Dmitriy Prakhar Rawat praveendvd <45095911+praveendvd@users.noreply.github.com> +Priyansh Garg Puja Jagani Pulkit Sharma Pydi Chandra @@ -719,6 +723,7 @@ richard.hines RichCrook richseviora Rishav Trivedi +Rob Brackett Rob Richardson Rob Wu Robert Elliot diff --git a/MODULE.bazel b/MODULE.bazel index c1817b08269e8..979f4cfa7885a 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -17,7 +17,7 @@ bazel_dep(name = "protobuf", version = "21.7", dev_dependency = True, repo_name # Required for rules_rust to import the crates properly bazel_dep(name = "rules_cc", version = "0.0.9", dev_dependency = True) -bazel_dep(name = "rules_dotnet", version = "0.16.0") +bazel_dep(name = "rules_dotnet", version = "0.16.1") bazel_dep(name = "rules_java", version = "7.11.1") bazel_dep(name = "rules_jvm_external", version = "6.3") bazel_dep(name = "rules_nodejs", version = "6.3.0") @@ -25,7 +25,7 @@ bazel_dep(name = "rules_oci", version = "1.7.6") bazel_dep(name = "rules_pkg", version = "0.10.1") bazel_dep(name = "rules_python", version = "0.33.0") bazel_dep(name = "rules_proto", version = "6.0.0") -bazel_dep(name = "rules_ruby", version = "0.11.0") +bazel_dep(name = "rules_ruby", version = "0.13.0") linter = use_extension("@apple_rules_lint//lint:extensions.bzl", "linter") linter.configure( @@ -173,54 +173,54 @@ maven.install( "com.github.spotbugs:spotbugs:4.8.6", "com.github.stephenc.jcip:jcip-annotations:1.0-1", "com.google.code.gson:gson:2.11.0", - "com.google.guava:guava:33.3.0-jre", + "com.google.guava:guava:33.3.1-jre", "com.google.auto:auto-common:1.2.2", "com.google.auto.service:auto-service:1.1.1", "com.google.auto.service:auto-service-annotations:1.1.1", - "com.google.googlejavaformat:google-java-format:jar:1.23.0", + "com.google.googlejavaformat:google-java-format:jar:1.24.0", "com.graphql-java:graphql-java:22.3", "dev.failsafe:failsafe:3.3.2", - "io.grpc:grpc-context:1.66.0", + "io.grpc:grpc-context:1.68.1", "io.lettuce:lettuce-core:6.4.0.RELEASE", - "io.netty:netty-buffer:4.1.113.Final", - "io.netty:netty-codec-http:4.1.113.Final", - "io.netty:netty-codec-http2:4.1.113.Final", - "io.netty:netty-common:4.1.113.Final", - "io.netty:netty-handler:4.1.113.Final", - "io.netty:netty-handler-proxy:4.1.113.Final", - "io.netty:netty-transport:4.1.113.Final", - "io.opentelemetry:opentelemetry-api:1.42.1", - "io.opentelemetry:opentelemetry-context:1.42.1", - "io.opentelemetry:opentelemetry-exporter-logging:1.42.1", - "io.opentelemetry:opentelemetry-sdk:1.42.1", - "io.opentelemetry:opentelemetry-sdk-common:1.42.1", - "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.42.1", - "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.42.1", - "io.opentelemetry:opentelemetry-sdk-testing:1.42.1", - "io.opentelemetry:opentelemetry-sdk-trace:1.42.1", + "io.netty:netty-buffer:4.1.114.Final", + "io.netty:netty-codec-http:4.1.114.Final", + "io.netty:netty-codec-http2:4.1.114.Final", + "io.netty:netty-common:4.1.114.Final", + "io.netty:netty-handler:4.1.114.Final", + "io.netty:netty-handler-proxy:4.1.114.Final", + "io.netty:netty-transport:4.1.114.Final", + "io.opentelemetry:opentelemetry-api:1.43.0", + "io.opentelemetry:opentelemetry-context:1.43.0", + "io.opentelemetry:opentelemetry-exporter-logging:1.43.0", + "io.opentelemetry:opentelemetry-sdk:1.43.0", + "io.opentelemetry:opentelemetry-sdk-common:1.43.0", + "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.43.0", + "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.43.0", + "io.opentelemetry:opentelemetry-sdk-testing:1.43.0", + "io.opentelemetry:opentelemetry-sdk-trace:1.43.0", "io.opentelemetry.semconv:opentelemetry-semconv:1.25.0-alpha", - "io.ous:jtoml:2.0.0", "it.ozimov:embedded-redis:0.7.3", - "net.bytebuddy:byte-buddy:1.15.1", - "org.htmlunit:htmlunit-core-js:4.4.0", + "net.bytebuddy:byte-buddy:1.15.10", + "org.htmlunit:htmlunit-core-js:4.5.0", "org.apache.commons:commons-exec:1.4.0", - "org.apache.logging.log4j:log4j-core:2.24.0", + "org.apache.logging.log4j:log4j-core:2.24.1", "org.assertj:assertj-core:3.26.3", "org.bouncycastle:bcpkix-jdk18on:1.78.1", "org.eclipse.mylyn.github:org.eclipse.egit.github.core:2.1.5", - "org.hsqldb:hsqldb:2.7.3", + "org.hsqldb:hsqldb:2.7.4", "org.jspecify:jspecify:1.0.0", - "org.junit.jupiter:junit-jupiter-api:5.11.0", - "org.junit.jupiter:junit-jupiter-engine:5.11.0", - "org.junit.jupiter:junit-jupiter-params:5.11.0", - "org.junit.platform:junit-platform-launcher:1.11.0", - "org.junit.platform:junit-platform-reporting:1.11.0", - "org.junit.platform:junit-platform-commons:1.11.0", - "org.junit.platform:junit-platform-engine:1.11.0", - "org.mockito:mockito-core:5.13.0", - "org.redisson:redisson:3.36.0", + "org.junit.jupiter:junit-jupiter-api:5.11.3", + "org.junit.jupiter:junit-jupiter-engine:5.11.3", + "org.junit.jupiter:junit-jupiter-params:5.11.3", + "org.junit.platform:junit-platform-launcher:1.11.3", + "org.junit.platform:junit-platform-reporting:1.11.3", + "org.junit.platform:junit-platform-commons:1.11.3", + "org.junit.platform:junit-platform-engine:1.11.3", + "org.mockito:mockito-core:5.14.2", + "org.redisson:redisson:3.37.0", "org.slf4j:slf4j-api:2.0.16", "org.slf4j:slf4j-jdk14:2.0.16", + "org.tomlj:tomlj:1.1.1", "org.zeromq:jeromq:0.6.0", ], excluded_artifacts = [ @@ -257,7 +257,7 @@ ruby.bundle_fetch( "//:rb/selenium-webdriver.gemspec", ], gem_checksums = { - "activesupport-7.2.1": "7557fa077a592a4f36f7ddacf4d9d71c34aff69ed20236b8a61c22d567da8c24", + "activesupport-7.2.1.2": "6c3f6ad50c4e52ce39d67aeda38f99e1372eca8b295987d072c19460ebce4cb1", "addressable-2.8.7": "462986537cf3735ab5f3c0f557f14155d778f4b43ea4f485a9deb9c8f7c58232", "ast-2.4.2": "1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12", "base64-0.2.0": "0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507", @@ -267,7 +267,7 @@ ruby.bundle_fetch( "connection_pool-2.4.1": "0f40cf997091f1f04ff66da67eabd61a9fe0d4928b9a3645228532512fab62f4", "crack-1.0.0": "c83aefdb428cdc7b66c7f287e488c796f055c0839e6e545fec2c7047743c4a49", "csv-3.3.0": "0bbd1defdc31134abefed027a639b3723c2753862150f4c3ee61cab71b20d67d", - "curb-1.0.5": "2c4755dfb5d6190e9ebb4407b23ac5a5c2c226be1449e6d3bdf625656352efd1", + "curb-1.0.6": "b369434efa91dc7310d72a74f8a228a5b920e3d5a89b0a3097e4c6a905af6eb2", "debug-1.9.2": "48e026c0852c7a10c60263e2e527968308958e266231e36d64e3efcabec7e7fc", "diff-lcs-1.5.1": "273223dfb40685548436d32b4733aa67351769c7dea621da7d9dd4813e63ddfe", "drb-2.2.1": "e9d472bf785f558b96b25358bae115646da0dbfd45107ad858b0bc0d935cb340", @@ -277,45 +277,45 @@ ruby.bundle_fetch( "fileutils-1.7.2": "36a0fb324218263e52b486ad7408e9a295378fe8edc9fd343709e523c0980631", "git-1.19.1": "b0a422d9f6517353c48a330d6114de4db9e0c82dbe7202964a1d9f1fbc827d70", "hashdiff-1.1.1": "c7966316726e0ceefe9f5c6aef107ebc3ccfef8b6db55fe3934f046b2cf0936a", - "i18n-1.14.5": "26dcbc05e364b57e27ab430148b3377bc413987d34cc042336271d8f42e9d1b9", + "i18n-1.14.6": "dc229a74f5d181f09942dd60ab5d6e667f7392c4ee826f35096db36d1fe3614c", "io-console-0.7.2": "f0dccff252f877a4f60d04a4dc6b442b185ebffb4b320ab69212a92b48a7a221", "io-console-0.7.2-java": "73aa382f8832b116613ceaf57b8ff5bf73dfedcaf39f0aa5420e10f63a4543ed", - "irb-1.14.0": "53d805013bbd194874b8c13a56aca6aebcd11dd79166d88724f8a434fedde615", + "irb-1.14.1": "5975003b58d36efaf492380baa982ceedf5aed36967a4d5b40996bc5c66e80f8", "jar-dependencies-0.4.1": "b2df2f1ecbff15334ce20ea7fdd5b8d8161faab67761ff72c7647d728e40d387", - "json-2.7.2": "1898b5cbc81cd36c0fd4d0b7ad2682c39fb07c5ff682fc6265f678f550d4982c", - "json-2.7.2-java": "138e3038b5361b3d06ee2e8aa2be00bed0d0de4ef5f1553fc5935e5b93aca7ee", + "json-2.7.4": "9ea6258b4add3abd25df965515be8b19be417f58b8c42619c7f2d3e86c158ece", + "json-2.7.4-java": "5d5d1593d8727a66f2e4161710dde06ba7043c9b3fa9eea0889fdc0450a107cd", "language_server-protocol-3.17.0.3": "3d5c58c02f44a20d972957a9febe386d7e7468ab3900ce6bd2b563dd910c6b3f", "listen-3.9.0": "db9e4424e0e5834480385197c139cb6b0ae0ef28cc13310cfd1ca78377d59c67", - "logger-1.6.0": "0ab7c120262dd8de2a18cb8d377f1f318cbe98535160a508af9e7710ff43ef3e", + "logger-1.6.1": "3ad9587ed3940bf7897ea64a673971415523f4f7d6b22c5e3af5219705669653", "minitest-5.25.1": "3db6795a80634def1cf86fda79d2d83b59b25ce5e186fa675f73c565589d2ad8", "parallel-1.26.3": "d86babb7a2b814be9f4b81587bf0b6ce2da7d45969fab24d8ae4bf2bb4d4c7ef", - "parser-3.3.4.2": "71efa8690c2a1ff8937258ff5713add4894dcea8b4ba19055692e28a2469c9e6", + "parser-3.3.5.0": "f30ebb71b7830c2e7cdc4b2b0e0ec2234900e3fca3fe2fba47f78be759181ab3", "psych-5.1.2": "337322f58fc2bf24827d2b9bd5ab595f6a72971867d151bb39980060ea40a368", "psych-5.1.2-java": "1dd68dc609eddbc884e6892e11da942e16f7256bd30ebde9d35449d43043a6fe", "public_suffix-6.0.1": "61d44e1cab5cbbbe5b31068481cf16976dd0dc1b6b07bd95617ef8c5e3e00c6f", "racc-1.8.1": "4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f", "racc-1.8.1-java": "54f2e6d1e1b91c154013277d986f52a90e5ececbe91465d29172e49342732b98", - "rack-2.2.9": "fd6301a97a1c1e955e68f85c861fcb1cde6145a32c532e1ea321a72ff8cc4042", + "rack-2.2.10": "e4a5ee3f8f2ba45614a4498114d6dc7da1c51a0f0dd810d891906ea71d3aa72b", "rainbow-3.1.1": "039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a", "rake-13.2.1": "46cb38dae65d7d74b6020a4ac9d48afed8eb8149c040eccf0523bec91907059d", "rb-fsevent-0.11.2": "43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe", "rb-inotify-0.11.1": "a0a700441239b0ff18eb65e3866236cd78613d6b9f78fea1f9ac47a85e47be6e", - "rbs-3.5.3": "f262eea0db1e40eaa154266096b1a4272bc965a81d78acb0e54b58b4dc11f052", + "rbs-3.6.1": "ed7273d018556844583d1785ac54194e67eec594d68e317d57fa90ad035532c0", "rchardet-1.8.0": "693acd5253d5ade81a51940697955f6dd4bb2f0d245bda76a8e23deec70a52c7", "rdoc-6.7.0": "b17d5f0f57b0853d7b880d4360a32c7caf8dbb81f8503a36426df809e617f379", "regexp_parser-2.9.2": "5a27e767ad634f8a4b544520d5cd28a0db7aa1198a5d7c9d7e11d7b3d9066446", - "reline-0.5.9": "5d2dd7ed0fd078e79a05e4eaa47dc91b8dacec7358e9e1dd6d9c4636cff7d378", - "rexml-3.3.6": "7af0459d108dfd6ffa7d38c67c8464e200f5c9d476d4c6a3d1899e92808d63c6", + "reline-0.5.10": "1660c969a792ebd034e6ceee8ca628f3b6698dcdb34f7a282a5edda37b958166", + "rexml-3.3.9": "d71875b85299f341edf47d44df0212e7658cbdf35aeb69cefdb63f57af3137c9", "rspec-3.13.0": "d490914ac1d5a5a64a0e1400c1d54ddd2a501324d703b8cfe83f458337bab993", - "rspec-core-3.13.0": "557792b4e88da883d580342b263d9652b6a10a12d5bda9ef967b01a48f15454c", - "rspec-expectations-3.13.2": "565fb94ab39923c0fe6a16cfc9570d1821b741917a50800373fcbbb752c7a45a", - "rspec-mocks-3.13.1": "087189899c337937bcf1d66a50dc3fc999ac88335bbeba4d385c2a38c87d7b38", + "rspec-core-3.13.2": "94fbda6e4738e478f1c7532b7cc241272fcdc8b9eac03a97338b1122e4573300", + "rspec-expectations-3.13.3": "0e6b5af59b900147698ea0ff80456c4f2e69cac4394fbd392fbd1ca561f66c58", + "rspec-mocks-3.13.2": "2327335def0e1665325a9b617e3af9ae20272741d80ac550336309a7c59abdef", "rspec-support-3.13.1": "48877d4f15b772b7538f3693c22225f2eda490ba65a0515c4e7cf6f2f17de70f", - "rubocop-1.65.1": "3a239b71fcfdeb32c654f4b48c2e6aeb4f77b128e348fa9442184f207e70718d", - "rubocop-ast-1.32.1": "6a86ce3b7aa8ff8b600396d8f75ef5f85873b94bf0a1ae11607c19e65ced79c1", + "rubocop-1.67.0": "8ccca7226e76d0a9974af960ea446d1fb38adf0c491214294e2fed75a85c378c", + "rubocop-ast-1.32.3": "40201e861c73a3c2d59428c7627828ef81fb2f8a306bc4a1c1801452afe3fe0f", "rubocop-capybara-2.21.0": "5d264efdd8b6c7081a3d4889decf1451a1cfaaec204d81534e236bc825b280ab", "rubocop-factory_bot-2.26.1": "8de13cd4edcee5ca800f255188167ecef8dbfc3d1fae9f15734e9d2e755392aa", - "rubocop-performance-1.21.1": "5cf20002a544275ad6aa99abca4b945d2a2ed71be925c38fe83700360ed8734e", + "rubocop-performance-1.22.1": "9ed9737af1ee90655654b483e0eac4e64702139e85d33335bf744b57a309a679", "rubocop-rake-0.6.0": "56b6f22189af4b33d4f4e490a555c09f1281b02f4d48c3a61f6e8fe5f401d8db", "rubocop-rspec-2.31.0": "2bae19388d78e1ceace44cd95fd34f3209f4ef20cac1b168d0a1325cbba3d672", "rubocop-rspec_rails-2.29.1": "4ae95abbe9ca5a9b6d8be14e50d230fb5b6ba033b05d4c0981b5b76fc44988e4", @@ -328,11 +328,11 @@ ruby.bundle_fetch( "strscan-3.1.0-java": "8645aa76e017e21764c6df572d2d79fcc1672284014f5bdbd806278cdbcd11b0", "terminal-table-3.0.2": "f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91", "tzinfo-2.0.6": "8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b", - "unicode-display_width-2.5.0": "7e7681dcade1add70cb9fda20dd77f300b8587c81ebbd165d14fd93144ff0ab4", - "webmock-3.23.1": "0fa738c0767d1c4ec8cc57f6b21998f0c238c8a5b32450df1c847f2767140d95", - "webrick-1.8.1": "19411ec6912911fd3df13559110127ea2badd0c035f7762873f58afc803e158f", + "unicode-display_width-2.6.0": "12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a", + "webmock-3.24.0": "be01357f6fc773606337ca79f3ba332b7d52cbe5c27587671abc0572dbec7122", + "webrick-1.8.2": "431746a349199546ff9dd272cae10849c865f938216e41c402a6489248f12f21", "websocket-1.2.11": "b7e7a74e2410b5e85c25858b26b3322f29161e300935f70a0e0d3c35e0462737", - "yard-0.9.36": "5505736c1b00c926f71053a606ab75f02070c5960d0778b901fe9d8b0a470be4", + "yard-0.9.37": "a6e910399e78e613f80ba9add9ba7c394b1a935f083cccbef82903a3d2a26992", }, gemfile = "//:rb/Gemfile", gemfile_lock = "//:rb/Gemfile.lock", diff --git a/README.md b/README.md index 637c6428c3198..fe7ce42b18ccb 100644 --- a/README.md +++ b/README.md @@ -317,6 +317,18 @@ Run unit tests with: bazel test //py:unit ``` +To run common tests with a specific browser: + +```sh +bazel test //py:common- +``` + +To run common tests with a specific browser (include BiDi tests): + +```sh +bazel test //py:common--bidi +``` + To run tests with a specific browser: ```sh diff --git a/Rakefile b/Rakefile index 57486c7976f03..24f41e429f1cd 100644 --- a/Rakefile +++ b/Rakefile @@ -99,7 +99,7 @@ JAVA_RELEASE_TARGETS = %w[ //java/src/org/openqa/selenium/chromium:chromium.publish //java/src/org/openqa/selenium/devtools/v128:v128.publish //java/src/org/openqa/selenium/devtools/v129:v129.publish - //java/src/org/openqa/selenium/devtools/v127:v127.publish + //java/src/org/openqa/selenium/devtools/v130:v130.publish //java/src/org/openqa/selenium/devtools/v85:v85.publish //java/src/org/openqa/selenium/edge:edge.publish //java/src/org/openqa/selenium/firefox:firefox.publish @@ -791,7 +791,7 @@ namespace :dotnet do sh 'docfx dotnet/docs/docfx.json' rescue StandardError case $CHILD_STATUS.exitstatus - when 127 + when 130 raise 'Ensure the dotnet/tools directory is added to your PATH environment variable (e.g., `~/.dotnet/tools`)' when 255 puts '.NET documentation build failed, likely because of DevTools namespacing. This is ok; continuing' diff --git a/common/devtools/chromium/v127/BUILD.bazel b/common/devtools/chromium/v130/BUILD.bazel similarity index 100% rename from common/devtools/chromium/v127/BUILD.bazel rename to common/devtools/chromium/v130/BUILD.bazel diff --git a/common/devtools/chromium/v127/browser_protocol.pdl b/common/devtools/chromium/v130/browser_protocol.pdl similarity index 97% rename from common/devtools/chromium/v127/browser_protocol.pdl rename to common/devtools/chromium/v130/browser_protocol.pdl index f1ac9eeb5d46c..8a9325e6f6528 100644 --- a/common/devtools/chromium/v127/browser_protocol.pdl +++ b/common/devtools/chromium/v130/browser_protocol.pdl @@ -156,6 +156,7 @@ experimental domain Accessibility flowto labelledby owns + url # A node in the accessibility tree. type AXNode extends object @@ -742,6 +743,7 @@ experimental domain Audits NoRegisterTriggerHeader NoRegisterOsSourceHeader NoRegisterOsTriggerHeader + NavigationRegistrationUniqueScopeAlreadySet type SharedDictionaryError extends string enum @@ -802,7 +804,6 @@ experimental domain Audits type GenericIssueErrorType extends string enum - CrossOriginPortalPostMessageError FormLabelForNameError FormDuplicateIdForInputError FormInputWithNoLabelError @@ -891,7 +892,9 @@ experimental domain Audits ClientMetadataNoResponse ClientMetadataInvalidResponse ClientMetadataInvalidContentType + IdpNotPotentiallyTrustworthy DisabledInSettings + DisabledInFlags ErrorFetchingSignin InvalidSigninResponse AccountsHttpNotFound @@ -914,6 +917,7 @@ experimental domain Audits NotSignedInWithIdp MissingTransientUserActivation ReplacedByButtonMode + InvalidFieldsSpecified RelyingPartyOriginIsOpaque TypeNotMatching @@ -1099,13 +1103,20 @@ experimental domain Audits parameters InspectorIssue issue -# Defines commands and events for browser extensions. Available if the client -# is connected using the --remote-debugging-pipe flag and -# the --enable-unsafe-extension-debugging flag is set. +# Defines commands and events for browser extensions. experimental domain Extensions + # Storage areas. + type StorageArea extends string + enum + session + local + sync + managed # Installs an unpacked extension from the filesystem similar to # --load-extension CLI flags. Returns extension ID once the extension - # has been installed. + # has been installed. Available if the client is connected using the + # --remote-debugging-pipe flag and the --enable-unsafe-extension-debugging + # flag is set. command loadUnpacked parameters # Absolute file path. @@ -1113,6 +1124,44 @@ experimental domain Extensions returns # Extension id. string id + # Gets data from extension storage in the given `storageArea`. If `keys` is + # specified, these are used to filter the result. + command getStorageItems + parameters + # ID of extension. + string id + # StorageArea to retrieve data from. + StorageArea storageArea + # Keys to retrieve. + optional array of string keys + returns + object data + # Removes `keys` from extension storage in the given `storageArea`. + command removeStorageItems + parameters + # ID of extension. + string id + # StorageArea to remove data from. + StorageArea storageArea + # Keys to remove. + array of string keys + # Clears extension storage in the given `storageArea`. + command clearStorageItems + parameters + # ID of extension. + string id + # StorageArea to remove data from. + StorageArea storageArea + # Sets `values` in extension storage in the given `storageArea`. The provided `values` + # will be merged with existing values in the storage area. + command setStorageItems + parameters + # ID of extension. + string id + # StorageArea to set data in. + StorageArea storageArea + # Values to set. + object values # Defines commands and events for Autofill. experimental domain Autofill @@ -1344,6 +1393,7 @@ domain Browser videoCapturePanTiltZoom wakeLockScreen wakeLockSystem + webAppInstallation windowManagement experimental type PermissionSetting extends string @@ -1366,6 +1416,8 @@ domain Browser optional boolean userVisibleOnly # For "clipboard" permission, may specify allowWithoutSanitization. optional boolean allowWithoutSanitization + # For "fullscreen" permission, must specify allowWithoutGesture:true. + optional boolean allowWithoutGesture # For "camera" permission, may specify panTiltZoom. optional boolean panTiltZoom @@ -2006,13 +2058,6 @@ experimental domain CSS # Associated style declaration. CSSStyle style - # CSS position-fallback rule representation. - deprecated type CSSPositionFallbackRule extends object - properties - Value name - # List of keyframes. - array of CSSTryRule tryRules - # CSS @position-try rule representation. type CSSPositionTryRule extends object properties @@ -2025,6 +2070,7 @@ experimental domain CSS StyleSheetOrigin origin # Associated style declaration. CSSStyle style + boolean active # CSS keyframes rule representation. type CSSKeyframesRule extends object @@ -2198,10 +2244,11 @@ experimental domain CSS optional array of InheritedPseudoElementMatches inheritedPseudoElements # A list of CSS keyframed animations matching this node. optional array of CSSKeyframesRule cssKeyframesRules - # A list of CSS position fallbacks matching this node. - deprecated optional array of CSSPositionFallbackRule cssPositionFallbackRules - # A list of CSS @position-try rules matching this node, based on the position-try-options property. + # A list of CSS @position-try rules matching this node, based on the position-try-fallbacks property. optional array of CSSPositionTryRule cssPositionTryRules + # Index of the active fallback in the applied position-try-fallback property, + # will not be set if there is no active position-try fallback. + optional integer activePositionFallbackIndex # A list of CSS at-property rules matching this node. optional array of CSSPropertyRule cssPropertyRules # A list of CSS property registrations matching this node. @@ -2632,6 +2679,7 @@ domain DOM after marker backdrop + column selection search-text target-text @@ -2641,6 +2689,8 @@ domain DOM first-line-inherited scroll-marker scroll-marker-group + scroll-next-button + scroll-prev-button scrollbar scrollbar-thumb scrollbar-button @@ -2654,6 +2704,12 @@ domain DOM view-transition-image-pair view-transition-old view-transition-new + placeholder + file-selector-button + details-content + select-fallback-button + select-fallback-button-text + picker # Shadow root type. type ShadowRootType extends string @@ -2758,6 +2814,13 @@ domain DOM optional boolean isSVG optional CompatibilityMode compatibilityMode optional BackendNode assignedSlot + experimental optional boolean isScrollable + + # A structure to hold the top-level node of a detached tree and an array of its retained descendants. + type DetachedElementInfo extends object + properties + Node treeNode + array of NodeId retainedNodeIds # A structure holding an RGBA color. type RGBA extends object @@ -3269,6 +3332,12 @@ domain DOM returns string path + # Returns list of detached nodes + experimental command getDetachedDomNodes + returns + # The list of detached nodes + array of DetachedElementInfo detachedNodes + # Enables console to refer to the node with given id via $x (see Command Line API for more details # $x functions). experimental command setInspectedNode @@ -3435,6 +3504,14 @@ domain DOM # Called when top layer elements are changed. experimental event topLayerElementsUpdated + # Fired when a node's scrollability state changes. + experimental event scrollableFlagUpdated + parameters + # The id of the node. + DOM.NodeId nodeId + # If the node is scrollable. + boolean isScrollable + # Called when a pseudo element is removed from an element. experimental event pseudoElementRemoved parameters @@ -4176,6 +4253,21 @@ domain Emulation optional SensorReadingXYZ xyz optional SensorReadingQuaternion quaternion + experimental type PressureSource extends string + enum + cpu + + experimental type PressureState extends string + enum + nominal + fair + serious + critical + + experimental type PressureMetadata extends object + properties + optional boolean available + # Tells whether emulation is supported. deprecated command canEmulate returns @@ -4345,6 +4437,24 @@ domain Emulation SensorType type SensorReading reading + # Overrides a pressure source of a given type, as used by the Compute + # Pressure API, so that updates to PressureObserver.observe() are provided + # via setPressureStateOverride instead of being retrieved from + # platform-provided telemetry data. + experimental command setPressureSourceOverrideEnabled + parameters + boolean enabled + PressureSource source + optional PressureMetadata metadata + + # Provides a given pressure state that will be processed and eventually be + # delivered to PressureObserver users. |source| must have been previously + # overridden by setPressureSourceOverrideEnabled. + experimental command setPressureStateOverride + parameters + PressureSource source + PressureState state + # Overrides the Idle state. command setIdleOverride parameters @@ -4553,6 +4663,42 @@ domain IO # UUID of the specified Blob. string uuid +experimental domain FileSystem + depends on Network + depends on Storage + + type File extends object + properties + string name + # Timestamp + Network.TimeSinceEpoch lastModified + # Size in bytes + number size + string type + + type Directory extends object + properties + string name + array of string nestedDirectories + # Files that are directly nested under this directory. + array of File nestedFiles + + type BucketFileSystemLocator extends object + properties + # Storage key + Storage.SerializedStorageKey storageKey + # Bucket name. Not passing a `bucketName` will retrieve the default Bucket. (https://developer.mozilla.org/en-US/docs/Web/API/Storage_API#storage_buckets) + optional string bucketName + # Path to the directory using each path component as an array item. + array of string pathComponents + + command getDirectory + parameters + BucketFileSystemLocator bucketFileSystemLocator + returns + # Returns the directory object at the path. + Directory directory + experimental domain IndexedDB depends on Runtime depends on Storage @@ -5394,12 +5540,21 @@ experimental domain Memory moderate critical + # Retruns current DOM object counters. command getDOMCounters returns integer documents integer nodes integer jsEventListeners + # Retruns DOM object counters after preparing renderer for leak detection. + command getDOMCountersForLeakDetection + returns + # DOM object counters. + array of DOMCounter counters + + # Prepares for leak detection by terminating workers, stopping spellcheckers, + # dropping non-essential internal caches, running garbage collections, etc. command prepareForLeakDetection # Simulate OomIntervention by purging V8 memory. @@ -5475,6 +5630,15 @@ experimental domain Memory # Size of the module in bytes. number size + # DOM object counter data. + type DOMCounter extends object + properties + # Object name. Note: object names should be presumed volatile and clients should not expect + # the returned names to be consistent across runs. + string name + # Object count. + integer count + # Network domain allows tracking network activities of the page. It exposes information about http, # file, data and other requests and responses, their headers, bodies, timing, etc. domain Network @@ -6212,6 +6376,8 @@ domain Network 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. @@ -6220,8 +6386,6 @@ domain Network StorageAccess # The cookie should have been blocked by 3PCD but is exempted by Top-level Storage Access API. TopLevelStorageAccess - # The cookie should have been blocked by 3PCD but is exempted by CORS opt-in. - CorsOptIn # The cookie should have been blocked by 3PCD but is exempted by the first-party URL scheme. Scheme @@ -7118,6 +7282,9 @@ domain Network # The number of obtained Trust Tokens on a successful "Issuance" operation. optional integer issuedTokenCount + # Fired once security policy has been updated. + experimental event policyUpdated + # Fired once when parsing the .wbn file has succeeded. # The event contains the information about the web bundle contents. experimental event subresourceWebBundleMetadataReceived @@ -7170,6 +7337,7 @@ domain Network UnsafeNone SameOriginPlusCoep RestrictPropertiesPlusCoep + NoopenerAllowPopups experimental type CrossOriginOpenerPolicyStatus extends object properties @@ -7882,6 +8050,7 @@ domain Page experimental type PermissionsPolicyFeature extends string enum accelerometer + all-screens-capture ambient-light-sensor attribution-reporting autoplay @@ -7915,8 +8084,10 @@ domain Page clipboard-read clipboard-write compute-pressure + controlled-frame cross-origin-isolated deferred-fetch + digital-credentials-get direct-sockets display-capture document-domain @@ -7937,11 +8108,13 @@ domain Page keyboard-map local-fonts magnetometer + media-playback-while-not-visible microphone midi otp-credentials payment picture-in-picture + popins private-aggregation private-state-token-issuance private-state-token-redemption @@ -7962,6 +8135,7 @@ domain Page usb usb-unrestricted vertical-scroll + web-app-installation web-printing web-share window-management @@ -8274,14 +8448,16 @@ domain Page experimental type ClientNavigationReason extends string enum + anchorClick formSubmissionGet formSubmissionPost httpHeaderRefresh - scriptInitiated + initialFrameNavigation metaTagRefresh + other pageBlockInterstitial reload - anchorClick + scriptInitiated experimental type ClientNavigationDisposition extends string enum @@ -9059,6 +9235,13 @@ domain Page # A new frame target will be created (see Target.attachedToTarget). swap + # Fired before frame subtree is detached. Emitted before any frame of the + # subtree is actually detached. + experimental event frameSubtreeWillBeDetached + parameters + # Id of the frame that is the root of the subtree that will be detached. + FrameId frameId + # The type of a frameNavigated event. experimental type NavigationType extends string enum @@ -9284,7 +9467,6 @@ domain Page Printing WebDatabase PictureInPicture - Portal SpeechRecognizer IdleManager PaymentManager @@ -9317,6 +9499,7 @@ domain Page ContentWebUSB ContentMediaSessionService ContentScreenReader + ContentDiscarded # See components/back_forward_cache/back_forward_cache_disable.h for explanations. EmbedderPopupBlockerTabHelper @@ -9402,6 +9585,15 @@ domain Page FrameId frameId # Frame's new url. string url + # Navigation type + enum navigationType + # Navigation due to fragment navigation. + fragment + # Navigation due to history API usage. + historyApi + # Navigation due to other reasons. + other + # Compressed image data requested by the `startScreencast`. experimental event screencastFrame @@ -10474,6 +10666,31 @@ experimental domain Storage exact modulus + experimental type AttributionReportingAggregatableDebugReportingData extends object + properties + UnsignedInt128AsBase16 keyPiece + # number instead of integer because not all uint32 can be represented by + # int + number value + array of string types + + experimental type AttributionReportingAggregatableDebugReportingConfig extends object + properties + # number instead of integer because not all uint32 can be represented by + # int, only present for source registrations + optional number budget + UnsignedInt128AsBase16 keyPiece + array of AttributionReportingAggregatableDebugReportingData debugData + optional string aggregationCoordinatorOrigin + + experimental type AttributionScopesData extends object + properties + array of string values + # number instead of integer because not all uint32 can be represented by + # int + number limit + number maxEventStates + experimental type AttributionReportingSourceRegistration extends object properties Network.TimeSinceEpoch time @@ -10492,6 +10709,9 @@ experimental domain Storage array of AttributionReportingAggregationKeysEntry aggregationKeys optional UnsignedInt64AsBase10 debugKey AttributionReportingTriggerDataMatching triggerDataMatching + SignedInt64AsBase10 destinationLimitPriority + AttributionReportingAggregatableDebugReportingConfig aggregatableDebugReportingConfig + optional AttributionScopesData scopesData experimental type AttributionReportingSourceRegistrationResult extends string enum @@ -10507,7 +10727,10 @@ experimental domain Storage destinationBothLimitsReached reportingOriginsPerSiteLimitReached exceedsMaxChannelCapacity + exceedsMaxScopesChannelCapacity exceedsMaxTriggerStateCardinality + exceedsMaxEventStatesLimit + destinationPerDayReportingLimitReached experimental event attributionReportingSourceRegistered parameters @@ -10525,6 +10748,8 @@ experimental domain Storage # number instead of integer because not all uint32 can be represented by # int number value + UnsignedInt64AsBase10 filteringId + experimental type AttributionReportingAggregatableValueEntry extends object properties @@ -10557,10 +10782,13 @@ experimental domain Storage array of AttributionReportingEventTriggerData eventTriggerData array of AttributionReportingAggregatableTriggerData aggregatableTriggerData array of AttributionReportingAggregatableValueEntry aggregatableValues + integer aggregatableFilteringIdMaxBytes boolean debugReporting optional string aggregationCoordinatorOrigin AttributionReportingSourceRegistrationTimeConfig sourceRegistrationTimeConfig optional string triggerContextId + AttributionReportingAggregatableDebugReportingConfig aggregatableDebugReportingConfig + array of string scopes experimental type AttributionReportingEventLevelResult extends string enum @@ -10788,7 +11016,7 @@ domain Target experimental optional Page.FrameId openerFrameId experimental optional Browser.BrowserContextID browserContextId # Provides additional details for specific target types. For example, for - # the type of "page", this may be set to "portal" or "prerender". + # the type of "page", this may be set to "prerender". experimental optional string subtype # A filter used by target query/discovery/auto-attach operations. @@ -12176,6 +12404,9 @@ experimental domain Preload JavaScriptInterfaceAdded JavaScriptInterfaceRemoved AllPrerenderingCanceled + WindowClosed + SlowNetwork + OtherPrerenderedPageActivated # Fired when a preload enabled state is updated. event preloadEnabledStateUpdated @@ -12209,7 +12440,6 @@ experimental domain Preload PrefetchFailedMIMENotSupported PrefetchFailedNetError PrefetchFailedNon2XX - PrefetchFailedPerPageLimitExceeded PrefetchEvictedAfterCandidateRemoved PrefetchEvictedForNewerPrefetch PrefetchHeldback @@ -12340,7 +12570,7 @@ experimental domain FedCm parameters # Allows callers to disable the promise rejection delay that would # normally happen, if this is unimportant to what's being tested. - # (step 4 of https://w3c-fedid.github.io/FedCM/#browser-api-rp-sign-in) + # (step 4 of https://fedidcg.github.io/FedCM/#browser-api-rp-sign-in) optional boolean disableRejectionDelay command disable @@ -12416,7 +12646,7 @@ experimental domain PWA # manifestId. optional string installUrlOrBundleUrl - # Uninstals the given manifest_id and closes any opened app windows. + # Uninstalls the given manifest_id and closes any opened app windows. command uninstall parameters string manifestId @@ -12438,7 +12668,7 @@ experimental domain PWA # used to attach to via Target.attachToTarget or similar APIs. # If some files in the parameters cannot be handled by the web app, they will # be ignored. If none of the files can be handled, this API returns an error. - # If no files provided as the parameter, this API also returns an error. + # If no files are provided as the parameter, this API also returns an error. # # According to the definition of the file handlers in the manifest file, one # Target.TargetID may represent a page handling one or more files. The order @@ -12455,7 +12685,103 @@ experimental domain PWA # Opens the current page in its web app identified by the manifest id, needs # to be called on a page target. This function returns immediately without - # waiting for the app finishing loading. + # waiting for the app to finish loading. command openCurrentPageInApp parameters string manifestId + + # If user prefers opening the app in browser or an app window. + type DisplayMode extends string + enum + standalone + browser + + # Changes user settings of the web app identified by its manifestId. If the + # app was not installed, this command returns an error. Unset parameters will + # be ignored; unrecognized values will cause an error. + # + # Unlike the ones defined in the manifest files of the web apps, these + # settings are provided by the browser and controlled by the users, they + # impact the way the browser handling the web apps. + # + # See the comment of each parameter. + command changeAppUserSettings + parameters + string manifestId + # If user allows the links clicked on by the user in the app's scope, or + # extended scope if the manifest has scope extensions and the flags + # `DesktopPWAsLinkCapturingWithScopeExtensions` and + # `WebAppEnableScopeExtensions` are enabled. + # + # Note, the API does not support resetting the linkCapturing to the + # initial value, uninstalling and installing the web app again will reset + # it. + # + # TODO(crbug.com/339453269): Setting this value on ChromeOS is not + # supported yet. + optional boolean linkCapturing + optional DisplayMode displayMode + +# This domain allows configuring virtual Bluetooth devices to test +# the web-bluetooth API. +experimental domain BluetoothEmulation + # Indicates the various states of Central. + type CentralState extends string + enum + absent + powered-off + powered-on + + # Stores the manufacturer data + type ManufacturerData extends object + properties + # Company identifier + # https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/company_identifiers/company_identifiers.yaml + # https://usb.org/developers + integer key + # Manufacturer-specific data + binary data + + # Stores the byte data of the advertisement packet sent by a Bluetooth device. + type ScanRecord extends object + properties + optional string name + optional array of string uuids + # Stores the external appearance description of the device. + optional integer appearance + # Stores the transmission power of a broadcasting device. + optional integer txPower + # Key is the company identifier and the value is an array of bytes of + # manufacturer specific data. + optional array of ManufacturerData manufacturerData + + # Stores the advertisement packet information that is sent by a Bluetooth device. + type ScanEntry extends object + properties + string deviceAddress + integer rssi + ScanRecord scanRecord + + # Enable the BluetoothEmulation domain. + command enable + parameters + # State of the simulated central. + CentralState state + + # Disable the BluetoothEmulation domain. + command disable + + # Simulates a peripheral with |address|, |name| and |knownServiceUuids| + # that has already been connected to the system. + command simulatePreconnectedPeripheral + parameters + string address + string name + array of ManufacturerData manufacturerData + array of string knownServiceUuids + + # Simulates an advertisement packet described in |entry| being received by + # the central. + command simulateAdvertisement + parameters + ScanEntry entry diff --git a/common/devtools/chromium/v127/js_protocol.pdl b/common/devtools/chromium/v130/js_protocol.pdl similarity index 100% rename from common/devtools/chromium/v127/js_protocol.pdl rename to common/devtools/chromium/v130/js_protocol.pdl diff --git a/common/extensions/webextensions-selenium-example.crx b/common/extensions/webextensions-selenium-example.crx index 38b38003b7ec3..941114eb446e8 100644 Binary files a/common/extensions/webextensions-selenium-example.crx and b/common/extensions/webextensions-selenium-example.crx differ diff --git a/common/extensions/webextensions-selenium-example/manifest.json b/common/extensions/webextensions-selenium-example/manifest.json index 60f2e5460c500..69e480dc60dda 100644 --- a/common/extensions/webextensions-selenium-example/manifest.json +++ b/common/extensions/webextensions-selenium-example/manifest.json @@ -14,6 +14,14 @@ ] } ], + "permissions": [ + "storage", + "scripting" + ], + "host_permissions": [ + "https://*/*", + "http://*/*" + ], "browser_specific_settings": { "gecko": { "id": "webextensions-selenium-example-v3@example.com" diff --git a/common/mirror/selenium b/common/mirror/selenium index 5c1e9a15d6f24..664bf8d6e5a1b 100644 --- a/common/mirror/selenium +++ b/common/mirror/selenium @@ -3,13 +3,33 @@ "tag_name": "nightly", "assets": [ { - "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/nightly/selenium-java-4.26.0-SNAPSHOT.zip" + "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/nightly/selenium-java-4.27.0-SNAPSHOT.zip" }, { - "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/nightly/selenium-server-4.26.0-SNAPSHOT.jar" + "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/nightly/selenium-server-4.27.0-SNAPSHOT.jar" }, { - "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/nightly/selenium-server-4.26.0-SNAPSHOT.zip" + "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/nightly/selenium-server-4.27.0-SNAPSHOT.zip" + } + ] + }, + { + "tag_name": "selenium-4.26.0", + "assets": [ + { + "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.26.0/selenium-dotnet-4.26.0.zip" + }, + { + "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.26.0/selenium-dotnet-strongnamed-4.26.0.zip" + }, + { + "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.26.0/selenium-java-4.26.0.zip" + }, + { + "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.26.0/selenium-server-4.26.0.jar" + }, + { + "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.26.0/selenium-server-4.26.0.zip" } ] }, @@ -935,28 +955,5 @@ "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.0.0-rc-2/selenium-server-4.0.0-rc-2.zip" } ] - }, - { - "tag_name": "selenium-4.0.0-rc-1", - "assets": [ - { - "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.0.0-rc-1/selenium-dotnet-4.0.0-rc1.zip" - }, - { - "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.0.0-rc-1/selenium-dotnet-strongnamed-4.0.0-rc1.zip" - }, - { - "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.0.0-rc-1/selenium-html-runner-4.0.0-rc-1.jar" - }, - { - "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.0.0-rc-1/selenium-java-4.0.0-rc-1.zip" - }, - { - "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.0.0-rc-1/selenium-server-4.0.0-rc-1.jar" - }, - { - "browser_download_url": "https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.0.0-rc-1/selenium-server-4.0.0-rc-1.zip" - } - ] } ] diff --git a/common/repositories.bzl b/common/repositories.bzl index 1a9e782144475..cdbdd4d13140a 100644 --- a/common/repositories.bzl +++ b/common/repositories.bzl @@ -11,8 +11,8 @@ def pin_browsers(): http_archive( name = "linux_firefox", - url = "https://ftp.mozilla.org/pub/firefox/releases/131.0/linux-x86_64/en-US/firefox-131.0.tar.bz2", - sha256 = "4ca8504a62a31472ecb8c3a769d4301dd4ac692d4cc5d51b8fe2cf41e7b11106", + url = "https://ftp.mozilla.org/pub/firefox/releases/132.0.1/linux-x86_64/en-US/firefox-132.0.1.tar.bz2", + sha256 = "ecdcb4787263cacd31aa3a1b62c14f1d3b69af44a0b40f9eb040852f401097c1", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -33,8 +33,8 @@ js_library( dmg_archive( name = "mac_firefox", - url = "https://ftp.mozilla.org/pub/firefox/releases/131.0/mac/en-US/Firefox%20131.0.dmg", - sha256 = "cd243b44746f56ee2042572cccab2736c0c6d419f85f90ad163a4ba04979ccb2", + url = "https://ftp.mozilla.org/pub/firefox/releases/132.0.1/mac/en-US/Firefox%20132.0.1.dmg", + sha256 = "409c0bdb4e434c1191b71504626f8165b01e2582e561a870591940186614be2e", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -50,8 +50,8 @@ js_library( http_archive( name = "linux_beta_firefox", - url = "https://ftp.mozilla.org/pub/firefox/releases/132.0b2/linux-x86_64/en-US/firefox-132.0b2.tar.bz2", - sha256 = "f4fb251b50661f27a5f4d87a3dc2839ed329793e232aca47b146d84384208167", + url = "https://ftp.mozilla.org/pub/firefox/releases/133.0b6/linux-x86_64/en-US/firefox-133.0b6.tar.bz2", + sha256 = "0f600dd3225ca6824004d28449e94d3768012f49eaea5506c618a199f234486e", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -72,8 +72,8 @@ js_library( dmg_archive( name = "mac_beta_firefox", - url = "https://ftp.mozilla.org/pub/firefox/releases/132.0b2/mac/en-US/Firefox%20132.0b2.dmg", - sha256 = "b7be772dc40c7065580e8556f7f037ae1f2960be0276c6f04ed453d4543712a1", + url = "https://ftp.mozilla.org/pub/firefox/releases/133.0b6/mac/en-US/Firefox%20133.0b6.dmg", + sha256 = "a6be00e4471d07eefc149a73e564232f2051fa8a39c1e12385550c8822f128b5", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -123,10 +123,10 @@ js_library( pkg_archive( name = "mac_edge", - url = "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/1077769e-236a-4f55-801d-2f782881fe18/MicrosoftEdge-129.0.2792.65.pkg", - sha256 = "a89a37ddfd655c47a44401a157c6a903f04a87622b64faa86d3897f5506f7481", + url = "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/1225faee-37ac-4c03-8f2b-153353d900c6/MicrosoftEdge-130.0.2849.80.pkg", + sha256 = "a0f3353555a7057158fd0335ecc783a45e1ed5bf38da9975396b63008591af80", move = { - "MicrosoftEdge-129.0.2792.65.pkg/Payload/Microsoft Edge.app": "Edge.app", + "MicrosoftEdge-130.0.2849.80.pkg/Payload/Microsoft Edge.app": "Edge.app", }, build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") @@ -143,8 +143,8 @@ js_library( deb_archive( name = "linux_edge", - url = "https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-stable/microsoft-edge-stable_129.0.2792.65-1_amd64.deb", - sha256 = "c6e0ad1e9b44d821b86a263b82edac596dca9b8a9f07413f2ff3c100221a28e7", + url = "https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-stable/microsoft-edge-stable_130.0.2849.80-1_amd64.deb", + sha256 = "1b6f5743703e6da81c65c28dbcfd949d605466e226acc7cde9efbd4beabfa05d", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -165,8 +165,8 @@ js_library( http_archive( name = "linux_edgedriver", - url = "https://msedgedriver.azureedge.net/129.0.2792.65/edgedriver_linux64.zip", - sha256 = "36811ca6700532a98c27f696f79aa06762d882408645e6d6a09f3ed2673a6951", + url = "https://msedgedriver.azureedge.net/130.0.2849.78/edgedriver_linux64.zip", + sha256 = "aec868f31bd714a5c12405f6fd6e0e7bfb3d0d06ae79690ecffde4af73da2075", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -182,8 +182,8 @@ js_library( http_archive( name = "mac_edgedriver", - url = "https://msedgedriver.azureedge.net/129.0.2792.65/edgedriver_mac64.zip", - sha256 = "53d7dd53564cf20aaf51d505b364f28d30a83d38b87a08d6bf22d26e04d17c2b", + url = "https://msedgedriver.azureedge.net/130.0.2849.81/edgedriver_mac64.zip", + sha256 = "da719a1170c2e93a88186dd9dabe485dda816280539522af03535d6c02eb1943", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") package(default_visibility = ["//visibility:public"]) @@ -199,8 +199,8 @@ js_library( http_archive( name = "linux_chrome", - url = "https://storage.googleapis.com/chrome-for-testing-public/129.0.6668.89/linux64/chrome-linux64.zip", - sha256 = "fd61ba345c840c6d5404d8abdb42c5d86832b212e9bc401a72dc86495c0cd7d3", + url = "https://storage.googleapis.com/chrome-for-testing-public/130.0.6723.116/linux64/chrome-linux64.zip", + sha256 = "3e71b99204dc191a8692048526a4ad41803b8b9035ea4ef14eb9b1d37331e1f1", 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/129.0.6668.89/mac-x64/chrome-mac-x64.zip", - sha256 = "dd67b65ed1eac994032b02d48c6aaeef7c0a20ba0450bf124e2fb558a1ab4a1b", + url = "https://storage.googleapis.com/chrome-for-testing-public/130.0.6723.116/mac-x64/chrome-mac-x64.zip", + sha256 = "2f535efa4d92c9aa3c7a1fd896ae4088d6a306150649bb7c1e6ef20f7d536476", strip_prefix = "chrome-mac-x64", patch_cmds = [ "mv 'Google Chrome for Testing.app' Chrome.app", @@ -243,8 +243,8 @@ js_library( http_archive( name = "linux_chromedriver", - url = "https://storage.googleapis.com/chrome-for-testing-public/129.0.6668.89/linux64/chromedriver-linux64.zip", - sha256 = "0dafb169734d3fc79171cabcecb07131bfb6a1727859118b756a020211b6c804", + url = "https://storage.googleapis.com/chrome-for-testing-public/130.0.6723.116/linux64/chromedriver-linux64.zip", + sha256 = "35b6751942cb589e8bde4733cf6dc7a1484693a56004e5dc8d994beede19e847", strip_prefix = "chromedriver-linux64", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") @@ -261,8 +261,8 @@ js_library( http_archive( name = "mac_chromedriver", - url = "https://storage.googleapis.com/chrome-for-testing-public/129.0.6668.89/mac-x64/chromedriver-mac-x64.zip", - sha256 = "71663c3f70fe3eb2256c474c60fa7ee1c15065f38b6c679e6c2d46960e774b1e", + url = "https://storage.googleapis.com/chrome-for-testing-public/130.0.6723.116/mac-x64/chromedriver-mac-x64.zip", + sha256 = "f8f71dfbed7dfe7255b2a1abda3cf28cc4d661f6f653dc1282735c703c86b865", strip_prefix = "chromedriver-mac-x64", build_file_content = """ load("@aspect_rules_js//js:defs.bzl", "js_library") diff --git a/common/selenium_manager.bzl b/common/selenium_manager.bzl index bb0133c12e949..50f890f1e2029 100644 --- a/common/selenium_manager.bzl +++ b/common/selenium_manager.bzl @@ -6,22 +6,22 @@ def selenium_manager(): http_file( name = "download_sm_linux", executable = True, - sha256 = "d4d775c38f5403d4a719e69903e6f70d15d2454d03da80ad6b82515a4ebfb986", - url = "https://github.com/SeleniumHQ/selenium_manager_artifacts/releases/download/selenium-manager-dffb534/selenium-manager-linux", + sha256 = "97ab346b907a813c236f1c6b9eb0e1b878702374b0768894415629c2cf05d97e", + url = "https://github.com/SeleniumHQ/selenium_manager_artifacts/releases/download/selenium-manager-33ad1b2/selenium-manager-linux", ) http_file( name = "download_sm_macos", executable = True, - sha256 = "2d6b20c603c4ca913423b3725cdc7ffa7e6a1554c9c161e3da226b186ba71054", - url = "https://github.com/SeleniumHQ/selenium_manager_artifacts/releases/download/selenium-manager-dffb534/selenium-manager-macos", + sha256 = "ef27b5c2d274dc4ab4417334116a1530571edc3deaf4740068e35484e275f28a", + url = "https://github.com/SeleniumHQ/selenium_manager_artifacts/releases/download/selenium-manager-33ad1b2/selenium-manager-macos", ) http_file( name = "download_sm_windows", executable = True, - sha256 = "58c47a131fd4323c647a95cb37baeafc5a14a536885ccc152457e87a4fd2188d", - url = "https://github.com/SeleniumHQ/selenium_manager_artifacts/releases/download/selenium-manager-dffb534/selenium-manager-windows.exe", + sha256 = "15113137d8d0d3648be9948c52e56e1f4c605bc5d9623962991198e8d0d413b6", + url = "https://github.com/SeleniumHQ/selenium_manager_artifacts/releases/download/selenium-manager-33ad1b2/selenium-manager-windows.exe", ) def _selenium_manager_artifacts_impl(_ctx): diff --git a/common/src/web/formPage.html b/common/src/web/formPage.html index 748e61bcc9d8a..70a31da55cae6 100644 --- a/common/src/web/formPage.html +++ b/common/src/web/formPage.html @@ -80,6 +80,12 @@ + + - Platform - Status - ID + label="Sort By" style={{ minWidth: '170px' }}> + {Object.keys(sortProperties).map((key) => ( + + {sortPropertiesLabel[key]} + + ))} { if (!(node instanceof ReferenceValue)) { throw Error(`Pass in a ReferenceValue object. Received: ${node}`) + } else { + startNodesSerialized.push(node.asMap()) } }) } @@ -544,7 +548,7 @@ class BrowsingContext { maxNodeCount: maxNodeCount, sandbox: sandbox, serializationOptions: serializationOptions, - startNodes: startNodes, + startNodes: startNodesSerialized, }, } diff --git a/javascript/node/selenium-webdriver/bidi/continueRequestParameters.js b/javascript/node/selenium-webdriver/bidi/continueRequestParameters.js index 622fd35846670..31e5f472abdaa 100644 --- a/javascript/node/selenium-webdriver/bidi/continueRequestParameters.js +++ b/javascript/node/selenium-webdriver/bidi/continueRequestParameters.js @@ -47,7 +47,7 @@ class ContinueRequestParameters { * Sets the cookies for the request. * * @param {Header[]} cookieHeaders - An array of cookie headers. - * @returns {continueRequestParameters} - The current instance of the ContinueRequestParameters for chaining. + * @returns {ContinueRequestParameters} - The current instance of the ContinueRequestParameters for chaining. * @throws {Error} - If a cookie header is not an instance of Header. */ cookies(cookieHeaders) { diff --git a/javascript/node/selenium-webdriver/bidi/filterBy.js b/javascript/node/selenium-webdriver/bidi/filterBy.js index 0c93f35ea2d2b..5d32d46131022 100644 --- a/javascript/node/selenium-webdriver/bidi/filterBy.js +++ b/javascript/node/selenium-webdriver/bidi/filterBy.js @@ -21,7 +21,7 @@ class FilterBy { } static logLevel(level) { - if (level === undefined || (level != undefined && !['debug', 'error', 'info', 'warning'].includes(level))) { + if (level === undefined || (level !== undefined && !['debug', 'error', 'info', 'warning'].includes(level))) { throw Error( `Please pass valid log level. Valid log levels are 'debug', 'error', 'info' and 'warning'. Received: ${level}`, ) diff --git a/javascript/node/selenium-webdriver/bidi/logEntries.js b/javascript/node/selenium-webdriver/bidi/logEntries.js index cba08adb77424..ff15e0c8dfb79 100644 --- a/javascript/node/selenium-webdriver/bidi/logEntries.js +++ b/javascript/node/selenium-webdriver/bidi/logEntries.js @@ -21,12 +21,13 @@ const { Source } = require('./scriptTypes') /** * Represents a base log entry. - * Desribed in https://w3c.github.io/webdriver-bidi/#types-log-logentry. + * Described in https://w3c.github.io/webdriver-bidi/#types-log-logentry. */ class BaseLogEntry { /** * Creates a new instance of BaseLogEntry. * @param {string} level - The log level. + * @param {source} source - Script Source * @param {string} text - The log source. * @param {string} text - The log text. * @param {number} timeStamp - The log timestamp. @@ -86,6 +87,7 @@ class GenericLogEntry extends BaseLogEntry { /** * Creates an instance of GenericLogEntry. * @param {string} level - The log level. + * @param {source} source - Script Source * @param {string} text - The log text. * @param {Date} timeStamp - The log timestamp. * @param {string} type - The log type. diff --git a/javascript/node/selenium-webdriver/bidi/protocolValue.js b/javascript/node/selenium-webdriver/bidi/protocolValue.js index 4bfb27aa221ab..18a0e67fb8c9f 100644 --- a/javascript/node/selenium-webdriver/bidi/protocolValue.js +++ b/javascript/node/selenium-webdriver/bidi/protocolValue.js @@ -145,7 +145,7 @@ class LocalValue { /** * Creates a new LocalValue object from the passed object. * - * @param {Object} map - The object. + * @param {Object} object - The object. * @returns {LocalValue} - The created LocalValue object. */ static createObjectValue(object) { @@ -431,7 +431,7 @@ class ChannelValue { } } - if (resultOwnership != undefined) { + if (resultOwnership !== undefined) { if (['root', 'none'].includes(resultOwnership)) { this.resultOwnership = resultOwnership } else { diff --git a/javascript/node/selenium-webdriver/bidi/storage.js b/javascript/node/selenium-webdriver/bidi/storage.js index 86eba93eef656..0289495ef7bec 100644 --- a/javascript/node/selenium-webdriver/bidi/storage.js +++ b/javascript/node/selenium-webdriver/bidi/storage.js @@ -138,11 +138,7 @@ class Storage { Object.prototype.hasOwnProperty.call(response.result.partitionKey, 'userContext') && Object.prototype.hasOwnProperty.call(response.result.partitionKey, 'sourceOrigin') ) { - let partitionKey = new PartitionKey( - response.result.partitionKey.userContext, - response.result.partitionKey.sourceOrigin, - ) - return partitionKey + return new PartitionKey(response.result.partitionKey.userContext, response.result.partitionKey.sourceOrigin) } } } @@ -182,11 +178,7 @@ class Storage { Object.prototype.hasOwnProperty.call(response.result.partitionKey, 'userContext') && Object.prototype.hasOwnProperty.call(response.result.partitionKey, 'sourceOrigin') ) { - let partitionKey = new PartitionKey( - response.result.partitionKey.userContext, - response.result.partitionKey.sourceOrigin, - ) - return partitionKey + return new PartitionKey(response.result.partitionKey.userContext, response.result.partitionKey.sourceOrigin) } } } diff --git a/javascript/node/selenium-webdriver/lib/network.js b/javascript/node/selenium-webdriver/lib/network.js index 5ab24c4b0da9f..1c55264653484 100644 --- a/javascript/node/selenium-webdriver/lib/network.js +++ b/javascript/node/selenium-webdriver/lib/network.js @@ -20,9 +20,10 @@ const { InterceptPhase } = require('../bidi/interceptPhase') const { AddInterceptParameters } = require('../bidi/addInterceptParameters') class Network { + #callbackId = 0 #driver #network - #callBackInterceptIdMap = new Map() + #authHandlers = new Map() constructor(driver) { this.#driver = driver @@ -39,39 +40,51 @@ class Network { return } this.#network = await network(this.#driver) - } - async addAuthenticationHandler(username, password) { - await this.#init() + await this.#network.addIntercept(new AddInterceptParameters(InterceptPhase.AUTH_REQUIRED)) - const interceptId = await this.#network.addIntercept(new AddInterceptParameters(InterceptPhase.AUTH_REQUIRED)) + await this.#network.authRequired(async (event) => { + const requestId = event.request.request + const uri = event.request.url + const credentials = this.getAuthCredentials(uri) + if (credentials !== null) { + await this.#network.continueWithAuth(requestId, credentials.username, credentials.password) + return + } - const id = await this.#network.authRequired(async (event) => { - await this.#network.continueWithAuth(event.request.request, username, password) + await this.#network.continueWithAuthNoCredentials(requestId) }) + } + + getAuthCredentials(uri) { + for (let [, value] of this.#authHandlers) { + if (uri.match(value.uri)) { + return value + } + } + return null + } + async addAuthenticationHandler(username, password, uri = '//') { + await this.#init() - this.#callBackInterceptIdMap.set(id, interceptId) + const id = this.#callbackId++ + + this.#authHandlers.set(id, { username, password, uri }) return id } async removeAuthenticationHandler(id) { await this.#init() - const interceptId = this.#callBackInterceptIdMap.get(id) - - await this.#network.removeIntercept(interceptId) - await this.#network.removeCallback(id) - - this.#callBackInterceptIdMap.delete(id) + if (this.#authHandlers.has(id)) { + this.#authHandlers.delete(id) + } else { + throw Error(`Callback with id ${id} not found`) + } } async clearAuthenticationHandlers() { - for (const [key, value] of this.#callBackInterceptIdMap.entries()) { - await this.#network.removeIntercept(value) - await this.#network.removeCallback(key) - } - - this.#callBackInterceptIdMap.clear() + this.#authHandlers.clear() } } diff --git a/javascript/node/selenium-webdriver/lib/webdriver.js b/javascript/node/selenium-webdriver/lib/webdriver.js index cb60563695a06..2c6a9aa48e259 100644 --- a/javascript/node/selenium-webdriver/lib/webdriver.js +++ b/javascript/node/selenium-webdriver/lib/webdriver.js @@ -1230,6 +1230,12 @@ class WebDriver { const caps = await this.getCapabilities() + if (caps['map_'].get('browserName') === 'firefox') { + console.warn( + 'CDP support for Firefox is deprecated and will be removed in future versions. Please switch to WebDriver BiDi.', + ) + } + if (process.env.SELENIUM_REMOTE_URL) { const host = new URL(process.env.SELENIUM_REMOTE_URL).host const sessionId = await this.getSession().then((session) => session.getId()) @@ -2752,6 +2758,7 @@ class WebElement { keys = await this.driver_.fileDetector_.handleFile(this.driver_, keys.join('')) } catch (ex) { this.log_.severe('Error trying parse string as a file with file detector; sending keys instead' + ex) + keys = keys.join('') } return this.execute_( diff --git a/javascript/node/selenium-webdriver/package.json b/javascript/node/selenium-webdriver/package.json index 308b4a9caf5c6..e06d9132c886a 100644 --- a/javascript/node/selenium-webdriver/package.json +++ b/javascript/node/selenium-webdriver/package.json @@ -1,6 +1,6 @@ { "name": "selenium-webdriver", - "version": "4.26.0-nightly202409202352", + "version": "4.27.0-nightly202410301443", "description": "The official WebDriver JavaScript bindings from the Selenium project", "license": "Apache-2.0", "keywords": [ @@ -23,25 +23,25 @@ "node": ">= 14.21.0" }, "dependencies": { - "@bazel/runfiles": "^6.3.0", + "@bazel/runfiles": "^6.3.1", "jszip": "^3.10.1", "tmp": "^0.2.3", "ws": "^8.18.0" }, "devDependencies": { - "@eslint/js": "^9.12.0", + "@eslint/js": "^9.14.0", "clean-jsdoc-theme": "^4.3.0", - "eslint": "^9.12.0", + "eslint": "^9.14.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-mocha": "^10.5.0", - "eslint-plugin-n": "^17.10.3", + "eslint-plugin-n": "^17.13.1", "eslint-plugin-no-only-tests": "^3.3.0", "eslint-plugin-prettier": "^5.2.1", - "express": "^4.21.0", - "globals": "^15.10.0", + "express": "^4.21.1", + "globals": "^15.12.0", "has-flag": "^5.0.1", - "jsdoc": "^4.0.3", - "mocha": "^10.7.3", + "jsdoc": "^4.0.4", + "mocha": "^10.8.2", "mocha-junit-reporter": "^2.2.1", "multer": "1.4.5-lts.1", "prettier": "^3.3.3", diff --git a/javascript/node/selenium-webdriver/test/bidi/locate_nodes_test.js b/javascript/node/selenium-webdriver/test/bidi/locate_nodes_test.js index 109f2e07ad00e..a41bb344d79e8 100644 --- a/javascript/node/selenium-webdriver/test/bidi/locate_nodes_test.js +++ b/javascript/node/selenium-webdriver/test/bidi/locate_nodes_test.js @@ -19,7 +19,7 @@ const assert = require('node:assert') const { Browser } = require('selenium-webdriver') -const { Pages, suite } = require('../../lib/test') +const { Pages, suite, ignore } = require('../../lib/test') const BrowsingContext = require('selenium-webdriver/bidi/browsingContext') const { Locator } = require('selenium-webdriver/bidi/browsingContext') const { ScriptManager } = require('selenium-webdriver/index') @@ -80,7 +80,7 @@ suite( assert.notEqual(element.sharedId, undefined) }) - xit('can locate node with xpath locator', async function () { + it('can locate node with xpath locator', async function () { const id = await driver.getWindowHandle() const browsingContext = await BrowsingContext(driver, { browsingContextId: id, @@ -97,7 +97,7 @@ suite( assert.notEqual(element.sharedId, undefined) }) - xit('can locate node with inner test locator', async function () { + ignore(env.browsers(Browser.FIREFOX)).it('can locate node with inner test locator', async function () { const id = await driver.getWindowHandle() const browsingContext = await BrowsingContext(driver, { browsingContextId: id, @@ -109,12 +109,9 @@ suite( const element = elements[0] assert.strictEqual(element.type, 'node') assert.notEqual(element.value, undefined) - assert.strictEqual(element.value.localName, 'div') - assert.strictEqual(element.value.attributes.class, 'content') - assert.notEqual(element.sharedId, undefined) }) - xit('can locate node with max node count', async function () { + it('can locate node with max node count', async function () { const id = await driver.getWindowHandle() const browsingContext = await BrowsingContext(driver, { browsingContextId: id, @@ -126,7 +123,7 @@ suite( assert.strictEqual(elements.length, 4) }) - xit('can locate node with given start nodes', async function () { + it('can locate node with given start nodes', async function () { const id = await driver.getWindowHandle() const browsingContext = await BrowsingContext(driver, { browsingContextId: id, @@ -155,14 +152,7 @@ suite( startNodes.push(new ReferenceValue(node.handle, node.sharedId)) }) - const elements = await browsingContext.locateNodes( - Locator.css('input'), - 50, - 'none', - undefined, - undefined, - startNodes, - ) + const elements = await browsingContext.locateNodes(Locator.css('input'), 50, 'none', undefined, startNodes) assert.strictEqual(elements.length, 35) }) @@ -236,5 +226,5 @@ suite( }) }) }, - { browsers: [Browser.FIREFOX] }, + { browsers: [Browser.FIREFOX, Browser.CHROME, Browser.EDGE] }, ) diff --git a/javascript/node/selenium-webdriver/test/lib/webdriver_network_test.js b/javascript/node/selenium-webdriver/test/lib/webdriver_network_test.js index 538fefa87916d..ff1ce496bf038 100644 --- a/javascript/node/selenium-webdriver/test/lib/webdriver_network_test.js +++ b/javascript/node/selenium-webdriver/test/lib/webdriver_network_test.js @@ -45,6 +45,35 @@ suite( assert.equal(source.includes('Access granted'), true) }) + it('can add authentication handler with filter', async function () { + await driver.network().addAuthenticationHandler('genie', 'bottle', 'basicAuth') + await driver.get(Pages.basicAuth) + + await driver.wait(until.elementLocated(By.css('pre'))) + let source = await driver.getPageSource() + assert.equal(source.includes('Access granted'), true) + }) + + it('can add multiple authentication handlers with filter', async function () { + await driver.network().addAuthenticationHandler('genie', 'bottle', 'basicAuth') + await driver.network().addAuthenticationHandler('test', 'test', 'test') + await driver.get(Pages.basicAuth) + + await driver.wait(until.elementLocated(By.css('pre'))) + let source = await driver.getPageSource() + assert.equal(source.includes('Access granted'), true) + }) + + it('can add multiple authentication handlers with the same filter', async function () { + await driver.network().addAuthenticationHandler('genie', 'bottle', 'basicAuth') + await driver.network().addAuthenticationHandler('genie', 'bottle', 'basicAuth') + await driver.get(Pages.basicAuth) + + await driver.wait(until.elementLocated(By.css('pre'))) + let source = await driver.getPageSource() + assert.equal(source.includes('Access granted'), true) + }) + it('can remove authentication handler', async function () { const id = await driver.network().addAuthenticationHandler('genie', 'bottle') @@ -59,8 +88,17 @@ suite( } }) + it('throws an error when remove authentication handler that does not exist', async function () { + try { + await driver.network().removeAuthenticationHandler(10) + assert.fail('Expected error not thrown. Non-existent handler cannot be removed') + } catch (e) { + assert.strictEqual(e.message, 'Callback with id 10 not found') + } + }) + it('can clear authentication handlers', async function () { - await driver.network().addAuthenticationHandler('genie', 'bottle') + await driver.network().addAuthenticationHandler('genie', 'bottle', 'basicAuth') await driver.network().addAuthenticationHandler('bottle', 'genie') diff --git a/javascript/node/selenium-webdriver/test/lib/webdriver_test.js b/javascript/node/selenium-webdriver/test/lib/webdriver_test.js index f850c3e4fcac8..419fdf6f80212 100644 --- a/javascript/node/selenium-webdriver/test/lib/webdriver_test.js +++ b/javascript/node/selenium-webdriver/test/lib/webdriver_test.js @@ -932,6 +932,32 @@ describe('WebDriver', function () { return driver.findElement(By.id('foo')).sendKeys('original/', 'path') }) + + it('sendKeysWithAFileDetector_handlerError', function () { + let executor = new FakeExecutor() + .expect(CName.FIND_ELEMENT, { + using: 'css selector', + value: '*[id="foo"]', + }) + .andReturnSuccess(WebElement.buildId('one')) + .expect(CName.SEND_KEYS_TO_ELEMENT, { + id: WebElement.buildId('one'), + text: 'original/path', + value: 'original/path'.split(''), + }) + .andReturnSuccess() + .end() + + let driver = executor.createDriver() + let handleFile = function (d, path) { + assert.strictEqual(driver, d) + assert.strictEqual(path, 'original/path') + return Promise.reject('unhandled file error') + } + driver.setFileDetector({ handleFile }) + + return driver.findElement(By.id('foo')).sendKeys('original/', 'path') + }) }) describe('switchTo()', function () { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ba6191ac72a5..7ad1e348f4198 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,26 +11,26 @@ importers: javascript/grid-ui: dependencies: '@apollo/client': - specifier: 3.10.4 - version: 3.10.4(@types/react@18.2.72)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0) + specifier: 3.11.9 + version: 3.11.9(@types/react@18.2.72)(graphql@16.9.0)(react-dom@18.3.1)(react@18.3.1) '@emotion/react': - specifier: 11.11.4 - version: 11.11.4(@types/react@18.2.72)(react@18.2.0) + specifier: 11.13.3 + version: 11.13.3(@types/react@18.2.72)(react@18.3.1) '@emotion/styled': - specifier: 11.11.5 - version: 11.11.5(@emotion/react@11.11.4)(@types/react@18.2.72)(react@18.2.0) + specifier: 11.13.0 + version: 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.72)(react@18.3.1) '@mui/icons-material': specifier: 5.15.18 - version: 5.15.18(@mui/material@5.15.18)(@types/react@18.2.72)(react@18.2.0) + version: 5.15.18(@mui/material@5.15.18)(@types/react@18.2.72)(react@18.3.1) '@mui/material': specifier: 5.15.18 - version: 5.15.18(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.2.72)(react-dom@18.2.0)(react@18.2.0) + version: 5.15.18(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.72)(react-dom@18.3.1)(react@18.3.1) '@novnc/novnc': specifier: 1.4.0 version: 1.4.0 '@types/jest': - specifier: 29.5.12 - version: 29.5.12 + specifier: 29.5.14 + version: 29.5.14 '@types/node': specifier: 20.12.12 version: 20.12.12 @@ -47,60 +47,60 @@ importers: specifier: 5.3.3 version: 5.3.3 graphql: - specifier: 16.8.1 - version: 16.8.1 + specifier: 16.9.0 + version: 16.9.0 graphql.macro: specifier: 1.4.2 - version: 1.4.2(@babel/core@7.25.7)(graphql@16.8.1) + version: 1.4.2(@babel/core@7.26.0)(graphql@16.9.0) path-browserify: specifier: 1.0.1 version: 1.0.1 pretty-ms: - specifier: 9.0.0 - version: 9.0.0 + specifier: 9.1.0 + version: 9.1.0 react: - specifier: 18.2.0 - version: 18.2.0 + specifier: 18.3.1 + version: 18.3.1 react-dom: - specifier: 18.2.0 - version: 18.2.0(react@18.2.0) + specifier: 18.3.1 + version: 18.3.1(react@18.3.1) react-modal: specifier: 3.16.1 - version: 3.16.1(react-dom@18.2.0)(react@18.2.0) + version: 3.16.1(react-dom@18.3.1)(react@18.3.1) react-router-dom: - specifier: 6.22.3 - version: 6.22.3(react-dom@18.2.0)(react@18.2.0) + specifier: 6.27.0 + version: 6.27.0(react-dom@18.3.1)(react@18.3.1) source-map-explorer: specifier: 2.5.3 version: 2.5.3 devDependencies: '@babel/preset-react': - specifier: 7.24.7 - version: 7.24.7(@babel/core@7.25.7) + specifier: 7.25.9 + version: 7.25.9(@babel/core@7.26.0) '@testing-library/jest-dom': - specifier: 6.4.5 - version: 6.4.5(@types/jest@29.5.12) + specifier: 6.6.3 + version: 6.6.3 '@testing-library/react': specifier: 14.3.1 - version: 14.3.1(react-dom@18.2.0)(react@18.2.0) + version: 14.3.1(react-dom@18.3.1)(react@18.3.1) '@testing-library/user-event': specifier: 14.5.2 version: 14.5.2(@testing-library/dom@10.4.0) esbuild: - specifier: 0.19.12 - version: 0.19.12 + specifier: 0.24.0 + version: 0.24.0 ts-standard: specifier: 12.0.2 - version: 12.0.2(typescript@5.4.5) + version: 12.0.2(typescript@5.6.3) typescript: - specifier: 5.4.5 - version: 5.4.5 + specifier: 5.6.3 + version: 5.6.3 javascript/node/selenium-webdriver: dependencies: '@bazel/runfiles': - specifier: ^6.3.0 - version: 6.3.0 + specifier: ^6.3.1 + version: 6.3.1 jszip: specifier: ^3.10.1 version: 3.10.1 @@ -112,47 +112,47 @@ importers: version: 8.18.0 devDependencies: '@eslint/js': - specifier: ^9.12.0 - version: 9.12.0 + specifier: ^9.14.0 + version: 9.14.0 clean-jsdoc-theme: specifier: ^4.3.0 - version: 4.3.0(jsdoc@4.0.3) + version: 4.3.0(jsdoc@4.0.4) eslint: - specifier: ^9.12.0 - version: 9.12.0(supports-color@9.4.0) + specifier: ^9.14.0 + version: 9.14.0(supports-color@9.4.0) eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.0(eslint@9.12.0) + version: 9.1.0(eslint@9.14.0) eslint-plugin-mocha: specifier: ^10.5.0 - version: 10.5.0(eslint@9.12.0) + version: 10.5.0(eslint@9.14.0) eslint-plugin-n: - specifier: ^17.10.3 - version: 17.10.3(eslint@9.12.0) + specifier: ^17.13.1 + version: 17.13.1(eslint@9.14.0) eslint-plugin-no-only-tests: specifier: ^3.3.0 version: 3.3.0 eslint-plugin-prettier: specifier: ^5.2.1 - version: 5.2.1(eslint-config-prettier@9.1.0)(eslint@9.12.0)(prettier@3.3.3) + version: 5.2.1(eslint-config-prettier@9.1.0)(eslint@9.14.0)(prettier@3.3.3) express: - specifier: ^4.21.0 - version: 4.21.0(supports-color@9.4.0) + specifier: ^4.21.1 + version: 4.21.1(supports-color@9.4.0) globals: - specifier: ^15.10.0 - version: 15.10.0 + specifier: ^15.12.0 + version: 15.12.0 has-flag: specifier: ^5.0.1 version: 5.0.1 jsdoc: - specifier: ^4.0.3 - version: 4.0.3 + specifier: ^4.0.4 + version: 4.0.4 mocha: - specifier: ^10.7.3 - version: 10.7.3 + specifier: ^10.8.2 + version: 10.8.2 mocha-junit-reporter: specifier: ^2.2.1 - version: 2.2.1(mocha@10.7.3)(supports-color@9.4.0) + version: 2.2.1(mocha@10.8.2)(supports-color@9.4.0) multer: specifier: 1.4.5-lts.1 version: 1.4.5-lts.1 @@ -182,13 +182,13 @@ packages: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - /@apollo/client@3.10.4(@types/react@18.2.72)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-51gk0xOwN6Ls1EbTG5svFva1kdm2APHYTzmFhaAdvUQoJFDxfc0UwQgDxGptzH84vkPlo1qunY1FuboyF9LI3Q==} + /@apollo/client@3.11.9(@types/react@18.2.72)(graphql@16.9.0)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-aQ6VL+CPO1G4DLS/3FelfA+nl+ZQCP5qeN1NS6J8xh9wumUM/2W1ccneqCYmbTMDtoSunxE1BV2W6u0FF4axwQ==} peerDependencies: graphql: ^15.0.0 || ^16.0.0 graphql-ws: ^5.5.5 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 subscriptions-transport-ws: ^0.9.0 || ^0.11.0 peerDependenciesMeta: graphql-ws: @@ -200,52 +200,53 @@ packages: subscriptions-transport-ws: optional: true dependencies: - '@graphql-typed-document-node/core': 3.2.0(graphql@16.8.1) + '@graphql-typed-document-node/core': 3.2.0(graphql@16.9.0) '@wry/caches': 1.0.1 '@wry/equality': 0.5.7 '@wry/trie': 0.5.0 - graphql: 16.8.1 - graphql-tag: 2.12.6(graphql@16.8.1) + graphql: 16.9.0 + graphql-tag: 2.12.6(graphql@16.9.0) hoist-non-react-statics: 3.3.2 optimism: 0.18.0 prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - rehackt: 0.1.0(@types/react@18.2.72)(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + rehackt: 0.1.0(@types/react@18.2.72)(react@18.3.1) response-iterator: 0.2.6 symbol-observable: 4.0.0 ts-invariant: 0.10.3 - tslib: 2.7.0 + tslib: 2.8.1 zen-observable-ts: 1.2.5 transitivePeerDependencies: - '@types/react' dev: false - /@babel/code-frame@7.25.7: - resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==} + /@babel/code-frame@7.26.2: + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/highlight': 7.25.7 - picocolors: 1.1.0 + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 - /@babel/compat-data@7.25.7: - resolution: {integrity: sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw==} + /@babel/compat-data@7.26.2: + resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} engines: {node: '>=6.9.0'} - /@babel/core@7.25.7: - resolution: {integrity: sha512-yJ474Zv3cwiSOO9nXJuqzvwEeM+chDuQ8GJirw+pZ91sCGCyOZ3dJkVE09fTV0VEVzXyLWhh3G/AolYTPX7Mow==} + /@babel/core@7.26.0: + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} engines: {node: '>=6.9.0'} dependencies: '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.25.7 - '@babel/generator': 7.25.7 - '@babel/helper-compilation-targets': 7.25.7 - '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.7) - '@babel/helpers': 7.25.7 - '@babel/parser': 7.25.7 - '@babel/template': 7.25.7 - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.7 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 convert-source-map: 2.0.0 debug: 4.3.7 gensync: 1.0.0-beta.2 @@ -254,225 +255,206 @@ packages: transitivePeerDependencies: - supports-color - /@babel/generator@7.25.7: - resolution: {integrity: sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==} + /@babel/generator@7.26.2: + resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.25.7 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.0.2 - /@babel/helper-annotate-as-pure@7.25.7: - resolution: {integrity: sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==} + /@babel/helper-annotate-as-pure@7.25.9: + resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.25.7 + '@babel/types': 7.26.0 dev: true - /@babel/helper-compilation-targets@7.25.7: - resolution: {integrity: sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==} + /@babel/helper-compilation-targets@7.25.9: + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/compat-data': 7.25.7 - '@babel/helper-validator-option': 7.25.7 - browserslist: 4.24.0 + '@babel/compat-data': 7.26.2 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.2 lru-cache: 5.1.1 semver: 6.3.1 - /@babel/helper-module-imports@7.25.7: - resolution: {integrity: sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==} + /@babel/helper-module-imports@7.25.9: + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.7 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color - /@babel/helper-module-transforms@7.25.7(@babel/core@7.25.7): - resolution: {integrity: sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==} + /@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0): + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.25.7 - '@babel/helper-module-imports': 7.25.7 - '@babel/helper-simple-access': 7.25.7 - '@babel/helper-validator-identifier': 7.25.7 - '@babel/traverse': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color - /@babel/helper-plugin-utils@7.25.7: - resolution: {integrity: sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==} + /@babel/helper-plugin-utils@7.25.9: + resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} engines: {node: '>=6.9.0'} dev: true - /@babel/helper-simple-access@7.25.7: - resolution: {integrity: sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.7 - transitivePeerDependencies: - - supports-color - - /@babel/helper-string-parser@7.25.7: - resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==} - engines: {node: '>=6.9.0'} - - /@babel/helper-validator-identifier@7.25.7: - resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} + /@babel/helper-string-parser@7.25.9: + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} engines: {node: '>=6.9.0'} - /@babel/helper-validator-option@7.25.7: - resolution: {integrity: sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==} + /@babel/helper-validator-identifier@7.25.9: + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} - /@babel/helpers@7.25.7: - resolution: {integrity: sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==} + /@babel/helper-validator-option@7.25.9: + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.25.7 - '@babel/types': 7.25.7 - /@babel/highlight@7.25.7: - resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==} + /@babel/helpers@7.26.0: + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.25.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.1.0 + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 - /@babel/parser@7.25.7: - resolution: {integrity: sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==} + /@babel/parser@7.26.2: + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.25.7 + '@babel/types': 7.26.0 - /@babel/plugin-syntax-jsx@7.25.7(@babel/core@7.25.7): - resolution: {integrity: sha512-ruZOnKO+ajVL/MVx+PwNBPOkrnXTXoWMtte1MBpegfCArhqOe3Bj52avVj1huLLxNKYKXYaSxZ2F+woK1ekXfw==} + /@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0): + resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 dev: true - /@babel/plugin-transform-react-display-name@7.25.7(@babel/core@7.25.7): - resolution: {integrity: sha512-r0QY7NVU8OnrwE+w2IWiRom0wwsTbjx4+xH2RTd7AVdof3uurXOF+/mXHQDRk+2jIvWgSaCHKMgggfvM4dyUGA==} + /@babel/plugin-transform-react-display-name@7.25.9(@babel/core@7.26.0): + resolution: {integrity: sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 dev: true - /@babel/plugin-transform-react-jsx-development@7.25.7(@babel/core@7.25.7): - resolution: {integrity: sha512-5yd3lH1PWxzW6IZj+p+Y4OLQzz0/LzlOG8vGqonHfVR3euf1vyzyMUJk9Ac+m97BH46mFc/98t9PmYLyvgL3qg==} + /@babel/plugin-transform-react-jsx-development@7.25.9(@babel/core@7.26.0): + resolution: {integrity: sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.25.7 - '@babel/plugin-transform-react-jsx': 7.25.7(@babel/core@7.25.7) + '@babel/core': 7.26.0 + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.0) transitivePeerDependencies: - supports-color dev: true - /@babel/plugin-transform-react-jsx@7.25.7(@babel/core@7.25.7): - resolution: {integrity: sha512-vILAg5nwGlR9EXE8JIOX4NHXd49lrYbN8hnjffDtoULwpL9hUx/N55nqh2qd0q6FyNDfjl9V79ecKGvFbcSA0Q==} + /@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0): + resolution: {integrity: sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.25.7 - '@babel/helper-annotate-as-pure': 7.25.7 - '@babel/helper-module-imports': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-syntax-jsx': 7.25.7(@babel/core@7.25.7) - '@babel/types': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color dev: true - /@babel/plugin-transform-react-pure-annotations@7.25.7(@babel/core@7.25.7): - resolution: {integrity: sha512-6YTHJ7yjjgYqGc8S+CbEXhLICODk0Tn92j+vNJo07HFk9t3bjFgAKxPLFhHwF2NjmQVSI1zBRfBWUeVBa2osfA==} + /@babel/plugin-transform-react-pure-annotations@7.25.9(@babel/core@7.26.0): + resolution: {integrity: sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.25.7 - '@babel/helper-annotate-as-pure': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 dev: true - /@babel/preset-react@7.24.7(@babel/core@7.25.7): - resolution: {integrity: sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==} + /@babel/preset-react@7.25.9(@babel/core@7.26.0): + resolution: {integrity: sha512-D3to0uSPiWE7rBrdIICCd0tJSIGpLaaGptna2+w7Pft5xMqLpA1sz99DK5TZ1TjGbdQ/VI1eCSZ06dv3lT4JOw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/helper-validator-option': 7.25.7 - '@babel/plugin-transform-react-display-name': 7.25.7(@babel/core@7.25.7) - '@babel/plugin-transform-react-jsx': 7.25.7(@babel/core@7.25.7) - '@babel/plugin-transform-react-jsx-development': 7.25.7(@babel/core@7.25.7) - '@babel/plugin-transform-react-pure-annotations': 7.25.7(@babel/core@7.25.7) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-transform-react-display-name': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-jsx-development': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-pure-annotations': 7.25.9(@babel/core@7.26.0) transitivePeerDependencies: - supports-color dev: true - /@babel/runtime@7.25.7: - resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==} + /@babel/runtime@7.26.0: + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 - /@babel/template@7.25.7: - resolution: {integrity: sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==} + /@babel/template@7.25.9: + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.25.7 - '@babel/parser': 7.25.7 - '@babel/types': 7.25.7 + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 - /@babel/traverse@7.25.7: - resolution: {integrity: sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==} + /@babel/traverse@7.25.9: + resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.25.7 - '@babel/generator': 7.25.7 - '@babel/parser': 7.25.7 - '@babel/template': 7.25.7 - '@babel/types': 7.25.7 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 debug: 4.3.7 globals: 11.12.0 transitivePeerDependencies: - supports-color - /@babel/types@7.25.7: - resolution: {integrity: sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==} + /@babel/types@7.26.0: + resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-string-parser': 7.25.7 - '@babel/helper-validator-identifier': 7.25.7 - to-fast-properties: 2.0.0 + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 - /@bazel/runfiles@6.3.0: - resolution: {integrity: sha512-2Q+KC0ys+BLIBp6zsI8E6htEw1/R1cjA1Yt+3JiqdEB597aGpZKiJJP6FeJSA4LIAWlH/FDVDtgvJ77eeLNyiA==} + /@bazel/runfiles@6.3.1: + resolution: {integrity: sha512-1uLNT5NZsUVIGS4syuHwTzZ8HycMPyr6POA3FCE4GbMtc4rhoJk8aZKtNIRthJYfL+iioppi+rTfH3olMPr9nA==} dev: false /@emotion/babel-plugin@11.12.0: resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==} dependencies: - '@babel/helper-module-imports': 7.25.7 - '@babel/runtime': 7.25.7 + '@babel/helper-module-imports': 7.25.9 + '@babel/runtime': 7.26.0 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 '@emotion/serialize': 1.3.2 @@ -510,8 +492,8 @@ packages: resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} dev: false - /@emotion/react@11.11.4(@types/react@18.2.72)(react@18.2.0): - resolution: {integrity: sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==} + /@emotion/react@11.13.3(@types/react@18.2.72)(react@18.3.1): + resolution: {integrity: sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==} peerDependencies: '@types/react': '*' react: '>=16.8.0' @@ -519,16 +501,16 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.26.0 '@emotion/babel-plugin': 11.12.0 '@emotion/cache': 11.13.1 '@emotion/serialize': 1.3.2 - '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.2.0) + '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1) '@emotion/utils': 1.4.1 - '@emotion/weak-memoize': 0.3.1 + '@emotion/weak-memoize': 0.4.0 '@types/react': 18.2.72 hoist-non-react-statics: 3.3.2 - react: 18.2.0 + react: 18.3.1 transitivePeerDependencies: - supports-color dev: false @@ -547,8 +529,8 @@ packages: resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} dev: false - /@emotion/styled@11.11.5(@emotion/react@11.11.4)(@types/react@18.2.72)(react@18.2.0): - resolution: {integrity: sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==} + /@emotion/styled@11.13.0(@emotion/react@11.13.3)(@types/react@18.2.72)(react@18.3.1): + resolution: {integrity: sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==} peerDependencies: '@emotion/react': ^11.0.0-rc.0 '@types/react': '*' @@ -557,15 +539,15 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.26.0 '@emotion/babel-plugin': 11.12.0 '@emotion/is-prop-valid': 1.3.1 - '@emotion/react': 11.11.4(@types/react@18.2.72)(react@18.2.0) + '@emotion/react': 11.13.3(@types/react@18.2.72)(react@18.3.1) '@emotion/serialize': 1.3.2 - '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.2.0) + '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1) '@emotion/utils': 1.4.1 '@types/react': 18.2.72 - react: 18.2.0 + react: 18.3.1 transitivePeerDependencies: - supports-color dev: false @@ -574,235 +556,240 @@ packages: resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} dev: false - /@emotion/use-insertion-effect-with-fallbacks@1.1.0(react@18.2.0): + /@emotion/use-insertion-effect-with-fallbacks@1.1.0(react@18.3.1): resolution: {integrity: sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==} peerDependencies: react: '>=16.8.0' dependencies: - react: 18.2.0 + react: 18.3.1 dev: false /@emotion/utils@1.4.1: resolution: {integrity: sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==} dev: false - /@emotion/weak-memoize@0.3.1: - resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==} - dev: false - /@emotion/weak-memoize@0.4.0: resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} dev: false - /@esbuild/aix-ppc64@0.19.12: - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} - engines: {node: '>=12'} + /@esbuild/aix-ppc64@0.24.0: + resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} + engines: {node: '>=18'} cpu: [ppc64] os: [aix] requiresBuild: true dev: true optional: true - /@esbuild/android-arm64@0.19.12: - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} - engines: {node: '>=12'} + /@esbuild/android-arm64@0.24.0: + resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} + engines: {node: '>=18'} cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@esbuild/android-arm@0.19.12: - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} - engines: {node: '>=12'} + /@esbuild/android-arm@0.24.0: + resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} + engines: {node: '>=18'} cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@esbuild/android-x64@0.19.12: - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} - engines: {node: '>=12'} + /@esbuild/android-x64@0.24.0: + resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} + engines: {node: '>=18'} cpu: [x64] os: [android] requiresBuild: true dev: true optional: true - /@esbuild/darwin-arm64@0.19.12: - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} - engines: {node: '>=12'} + /@esbuild/darwin-arm64@0.24.0: + resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@esbuild/darwin-x64@0.19.12: - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} - engines: {node: '>=12'} + /@esbuild/darwin-x64@0.24.0: + resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@esbuild/freebsd-arm64@0.19.12: - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} - engines: {node: '>=12'} + /@esbuild/freebsd-arm64@0.24.0: + resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] requiresBuild: true dev: true optional: true - /@esbuild/freebsd-x64@0.19.12: - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} - engines: {node: '>=12'} + /@esbuild/freebsd-x64@0.24.0: + resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] requiresBuild: true dev: true optional: true - /@esbuild/linux-arm64@0.19.12: - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} - engines: {node: '>=12'} + /@esbuild/linux-arm64@0.24.0: + resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/linux-arm@0.19.12: - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} - engines: {node: '>=12'} + /@esbuild/linux-arm@0.24.0: + resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} + engines: {node: '>=18'} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/linux-ia32@0.19.12: - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} - engines: {node: '>=12'} + /@esbuild/linux-ia32@0.24.0: + resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/linux-loong64@0.19.12: - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} - engines: {node: '>=12'} + /@esbuild/linux-loong64@0.24.0: + resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/linux-mips64el@0.19.12: - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} - engines: {node: '>=12'} + /@esbuild/linux-mips64el@0.24.0: + resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/linux-ppc64@0.19.12: - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} - engines: {node: '>=12'} + /@esbuild/linux-ppc64@0.24.0: + resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/linux-riscv64@0.19.12: - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} - engines: {node: '>=12'} + /@esbuild/linux-riscv64@0.24.0: + resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/linux-s390x@0.19.12: - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} - engines: {node: '>=12'} + /@esbuild/linux-s390x@0.24.0: + resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/linux-x64@0.19.12: - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} - engines: {node: '>=12'} + /@esbuild/linux-x64@0.24.0: + resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} + engines: {node: '>=18'} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/netbsd-x64@0.19.12: - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} - engines: {node: '>=12'} + /@esbuild/netbsd-x64@0.24.0: + resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] requiresBuild: true dev: true optional: true - /@esbuild/openbsd-x64@0.19.12: - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} - engines: {node: '>=12'} + /@esbuild/openbsd-arm64@0.24.0: + resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.24.0: + resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] requiresBuild: true dev: true optional: true - /@esbuild/sunos-x64@0.19.12: - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} - engines: {node: '>=12'} + /@esbuild/sunos-x64@0.24.0: + resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] requiresBuild: true dev: true optional: true - /@esbuild/win32-arm64@0.19.12: - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} - engines: {node: '>=12'} + /@esbuild/win32-arm64@0.24.0: + resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@esbuild/win32-ia32@0.19.12: - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} - engines: {node: '>=12'} + /@esbuild/win32-ia32@0.24.0: + resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@esbuild/win32-x64@0.19.12: - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} - engines: {node: '>=12'} + /@esbuild/win32-x64@0.24.0: + resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} + engines: {node: '>=18'} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.57.1): - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + /@eslint-community/eslint-utils@4.4.1(eslint@8.57.1): + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -811,18 +798,18 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /@eslint-community/eslint-utils@4.4.0(eslint@9.12.0): - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + /@eslint-community/eslint-utils@4.4.1(eslint@9.14.0): + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 9.12.0(supports-color@9.4.0) + eslint: 9.14.0(supports-color@9.4.0) eslint-visitor-keys: 3.4.3 dev: true - /@eslint-community/regexpp@4.11.1: - resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} + /@eslint-community/regexpp@4.12.1: + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true @@ -837,8 +824,8 @@ packages: - supports-color dev: true - /@eslint/core@0.6.0: - resolution: {integrity: sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==} + /@eslint/core@0.7.0: + resolution: {integrity: sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true @@ -865,7 +852,7 @@ packages: dependencies: ajv: 6.12.6 debug: 4.3.7(supports-color@9.4.0) - espree: 10.2.0 + espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 @@ -881,8 +868,8 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@eslint/js@9.12.0: - resolution: {integrity: sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==} + /@eslint/js@9.14.0: + resolution: {integrity: sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true @@ -891,8 +878,8 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true - /@eslint/plugin-kit@0.2.0: - resolution: {integrity: sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==} + /@eslint/plugin-kit@0.2.2: + resolution: {integrity: sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: levn: 0.4.1 @@ -904,46 +891,46 @@ packages: '@floating-ui/utils': 0.2.8 dev: false - /@floating-ui/dom@1.6.11: - resolution: {integrity: sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==} + /@floating-ui/dom@1.6.12: + resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==} dependencies: '@floating-ui/core': 1.6.8 '@floating-ui/utils': 0.2.8 dev: false - /@floating-ui/react-dom@2.1.2(react-dom@18.2.0)(react@18.2.0): + /@floating-ui/react-dom@2.1.2(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: - '@floating-ui/dom': 1.6.11 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@floating-ui/dom': 1.6.12 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: false /@floating-ui/utils@0.2.8: resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} dev: false - /@graphql-typed-document-node/core@3.2.0(graphql@16.8.1): + /@graphql-typed-document-node/core@3.2.0(graphql@16.9.0): resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 dependencies: - graphql: 16.8.1 + graphql: 16.9.0 dev: false - /@humanfs/core@0.19.0: - resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==} + /@humanfs/core@0.19.1: + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} dev: true - /@humanfs/node@0.16.5: - resolution: {integrity: sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==} + /@humanfs/node@0.16.6: + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} engines: {node: '>=18.18.0'} dependencies: - '@humanfs/core': 0.19.0 + '@humanfs/core': 0.19.1 '@humanwhocodes/retry': 0.3.1 dev: true @@ -974,17 +961,24 @@ packages: engines: {node: '>=18.18'} dev: true + /@humanwhocodes/retry@0.4.1: + resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + engines: {node: '>=18.18'} + dev: true + /@jest/expect-utils@29.7.0: resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: jest-get-type: 29.6.3 + dev: false /@jest/schemas@29.6.3: resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@sinclair/typebox': 0.27.8 + dev: false /@jest/types@29.6.3: resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} @@ -996,6 +990,7 @@ packages: '@types/node': 20.12.12 '@types/yargs': 17.0.33 chalk: 4.1.2 + dev: false /@jridgewell/gen-mapping@0.3.5: resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} @@ -1036,7 +1031,7 @@ packages: lodash: 4.17.21 dev: true - /@mui/base@5.0.0-beta.40(@types/react@18.2.72)(react-dom@18.2.0)(react@18.2.0): + /@mui/base@5.0.0-beta.40(@types/react@18.2.72)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==} engines: {node: '>=12.0.0'} peerDependencies: @@ -1047,23 +1042,23 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.7 - '@floating-ui/react-dom': 2.1.2(react-dom@18.2.0)(react@18.2.0) - '@mui/types': 7.2.17(@types/react@18.2.72) - '@mui/utils': 5.16.6(@types/react@18.2.72)(react@18.2.0) + '@babel/runtime': 7.26.0 + '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1)(react@18.3.1) + '@mui/types': 7.2.19(@types/react@18.2.72) + '@mui/utils': 5.16.6(@types/react@18.2.72)(react@18.3.1) '@popperjs/core': 2.11.8 '@types/react': 18.2.72 clsx: 2.1.1 prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: false /@mui/core-downloads-tracker@5.16.7: resolution: {integrity: sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ==} dev: false - /@mui/icons-material@5.15.18(@mui/material@5.15.18)(@types/react@18.2.72)(react@18.2.0): + /@mui/icons-material@5.15.18(@mui/material@5.15.18)(@types/react@18.2.72)(react@18.3.1): resolution: {integrity: sha512-jGhyw02TSLM0NgW+MDQRLLRUD/K4eN9rlK2pTBTL1OtzyZmQ8nB060zK1wA0b7cVrIiG+zyrRmNAvGWXwm2N9Q==} engines: {node: '>=12.0.0'} peerDependencies: @@ -1074,13 +1069,13 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.7 - '@mui/material': 5.15.18(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.2.72)(react-dom@18.2.0)(react@18.2.0) + '@babel/runtime': 7.26.0 + '@mui/material': 5.15.18(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.72)(react-dom@18.3.1)(react@18.3.1) '@types/react': 18.2.72 - react: 18.2.0 + react: 18.3.1 dev: false - /@mui/material@5.15.18(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.2.72)(react-dom@18.2.0)(react@18.2.0): + /@mui/material@5.15.18(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.72)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-n+/dsiqux74fFfcRUJjok+ieNQ7+BEk6/OwX9cLcLvriZrZb+/7Y8+Fd2HlUUbn5N0CDurgAHm0VH1DqyJ9HAw==} engines: {node: '>=12.0.0'} peerDependencies: @@ -1097,26 +1092,26 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.7 - '@emotion/react': 11.11.4(@types/react@18.2.72)(react@18.2.0) - '@emotion/styled': 11.11.5(@emotion/react@11.11.4)(@types/react@18.2.72)(react@18.2.0) - '@mui/base': 5.0.0-beta.40(@types/react@18.2.72)(react-dom@18.2.0)(react@18.2.0) + '@babel/runtime': 7.26.0 + '@emotion/react': 11.13.3(@types/react@18.2.72)(react@18.3.1) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.72)(react@18.3.1) + '@mui/base': 5.0.0-beta.40(@types/react@18.2.72)(react-dom@18.3.1)(react@18.3.1) '@mui/core-downloads-tracker': 5.16.7 - '@mui/system': 5.16.7(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.2.72)(react@18.2.0) - '@mui/types': 7.2.17(@types/react@18.2.72) - '@mui/utils': 5.16.6(@types/react@18.2.72)(react@18.2.0) + '@mui/system': 5.16.7(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.72)(react@18.3.1) + '@mui/types': 7.2.19(@types/react@18.2.72) + '@mui/utils': 5.16.6(@types/react@18.2.72)(react@18.3.1) '@types/react': 18.2.72 '@types/react-transition-group': 4.4.11 clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) react-is: 18.3.1 - react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) + react-transition-group: 4.4.5(react-dom@18.3.1)(react@18.3.1) dev: false - /@mui/private-theming@5.16.6(@types/react@18.2.72)(react@18.2.0): + /@mui/private-theming@5.16.6(@types/react@18.2.72)(react@18.3.1): resolution: {integrity: sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==} engines: {node: '>=12.0.0'} peerDependencies: @@ -1126,14 +1121,14 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.7 - '@mui/utils': 5.16.6(@types/react@18.2.72)(react@18.2.0) + '@babel/runtime': 7.26.0 + '@mui/utils': 5.16.6(@types/react@18.2.72)(react@18.3.1) '@types/react': 18.2.72 prop-types: 15.8.1 - react: 18.2.0 + react: 18.3.1 dev: false - /@mui/styled-engine@5.16.6(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(react@18.2.0): + /@mui/styled-engine@5.16.6(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1): resolution: {integrity: sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==} engines: {node: '>=12.0.0'} peerDependencies: @@ -1146,16 +1141,16 @@ packages: '@emotion/styled': optional: true dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.26.0 '@emotion/cache': 11.13.1 - '@emotion/react': 11.11.4(@types/react@18.2.72)(react@18.2.0) - '@emotion/styled': 11.11.5(@emotion/react@11.11.4)(@types/react@18.2.72)(react@18.2.0) + '@emotion/react': 11.13.3(@types/react@18.2.72)(react@18.3.1) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.72)(react@18.3.1) csstype: 3.1.3 prop-types: 15.8.1 - react: 18.2.0 + react: 18.3.1 dev: false - /@mui/system@5.16.7(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.2.72)(react@18.2.0): + /@mui/system@5.16.7(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.72)(react@18.3.1): resolution: {integrity: sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==} engines: {node: '>=12.0.0'} peerDependencies: @@ -1171,22 +1166,22 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.7 - '@emotion/react': 11.11.4(@types/react@18.2.72)(react@18.2.0) - '@emotion/styled': 11.11.5(@emotion/react@11.11.4)(@types/react@18.2.72)(react@18.2.0) - '@mui/private-theming': 5.16.6(@types/react@18.2.72)(react@18.2.0) - '@mui/styled-engine': 5.16.6(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(react@18.2.0) - '@mui/types': 7.2.17(@types/react@18.2.72) - '@mui/utils': 5.16.6(@types/react@18.2.72)(react@18.2.0) + '@babel/runtime': 7.26.0 + '@emotion/react': 11.13.3(@types/react@18.2.72)(react@18.3.1) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.72)(react@18.3.1) + '@mui/private-theming': 5.16.6(@types/react@18.2.72)(react@18.3.1) + '@mui/styled-engine': 5.16.6(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1) + '@mui/types': 7.2.19(@types/react@18.2.72) + '@mui/utils': 5.16.6(@types/react@18.2.72)(react@18.3.1) '@types/react': 18.2.72 clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 - react: 18.2.0 + react: 18.3.1 dev: false - /@mui/types@7.2.17(@types/react@18.2.72): - resolution: {integrity: sha512-oyumoJgB6jDV8JFzRqjBo2daUuHpzDjoO/e3IrRhhHo/FxJlaVhET6mcNrKHUq2E+R+q3ql0qAtvQ4rfWHhAeQ==} + /@mui/types@7.2.19(@types/react@18.2.72): + resolution: {integrity: sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: @@ -1196,7 +1191,7 @@ packages: '@types/react': 18.2.72 dev: false - /@mui/utils@5.16.6(@types/react@18.2.72)(react@18.2.0): + /@mui/utils@5.16.6(@types/react@18.2.72)(react@18.3.1): resolution: {integrity: sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==} engines: {node: '>=12.0.0'} peerDependencies: @@ -1206,13 +1201,13 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.7 - '@mui/types': 7.2.17(@types/react@18.2.72) + '@babel/runtime': 7.26.0 + '@mui/types': 7.2.19(@types/react@18.2.72) '@types/prop-types': 15.7.13 '@types/react': 18.2.72 clsx: 2.1.1 prop-types: 15.8.1 - react: 18.2.0 + react: 18.3.1 react-is: 18.3.1 dev: false @@ -1250,8 +1245,8 @@ packages: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false - /@remix-run/router@1.15.3: - resolution: {integrity: sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==} + /@remix-run/router@1.20.0: + resolution: {integrity: sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==} engines: {node: '>=14.0.0'} dev: false @@ -1261,6 +1256,7 @@ packages: /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: false /@sinonjs/commons@3.0.1: resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} @@ -1268,8 +1264,8 @@ packages: type-detect: 4.0.8 dev: true - /@sinonjs/fake-timers@13.0.2: - resolution: {integrity: sha512-4Bb+oqXZTSTZ1q27Izly9lv8B9dlV61CROxPiVtywwzv5SnytJqhvYe6FclHYuXml4cd1VHPo1zd5PmTeJozvA==} + /@sinonjs/fake-timers@13.0.5: + resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} dependencies: '@sinonjs/commons': 3.0.1 dev: true @@ -1290,8 +1286,8 @@ packages: resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} dependencies: - '@babel/code-frame': 7.25.7 - '@babel/runtime': 7.25.7 + '@babel/code-frame': 7.26.2 + '@babel/runtime': 7.26.0 '@types/aria-query': 5.0.4 aria-query: 5.3.0 chalk: 4.1.2 @@ -1304,8 +1300,8 @@ packages: resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} engines: {node: '>=14'} dependencies: - '@babel/code-frame': 7.25.7 - '@babel/runtime': 7.25.7 + '@babel/code-frame': 7.26.2 + '@babel/runtime': 7.26.0 '@types/aria-query': 5.0.4 aria-query: 5.1.3 chalk: 4.1.2 @@ -1314,30 +1310,11 @@ packages: pretty-format: 27.5.1 dev: true - /@testing-library/jest-dom@6.4.5(@types/jest@29.5.12): - resolution: {integrity: sha512-AguB9yvTXmCnySBP1lWjfNNUwpbElsaQ567lt2VdGqAdHtpieLgjmcVyv1q7PMIvLbgpDdkWV5Ydv3FEejyp2A==} + /@testing-library/jest-dom@6.6.3: + resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} - peerDependencies: - '@jest/globals': '>= 28' - '@types/bun': latest - '@types/jest': '>= 28' - jest: '>= 28' - vitest: '>= 0.32' - peerDependenciesMeta: - '@jest/globals': - optional: true - '@types/bun': - optional: true - '@types/jest': - optional: true - jest: - optional: true - vitest: - optional: true dependencies: '@adobe/css-tools': 4.4.0 - '@babel/runtime': 7.25.7 - '@types/jest': 29.5.12 aria-query: 5.3.2 chalk: 3.0.0 css.escape: 1.5.1 @@ -1346,18 +1323,18 @@ packages: redent: 3.0.0 dev: true - /@testing-library/react@14.3.1(react-dom@18.2.0)(react@18.2.0): + /@testing-library/react@14.3.1(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==} engines: {node: '>=14'} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.26.0 '@testing-library/dom': 9.3.4 '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true /@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0): @@ -1383,22 +1360,26 @@ packages: /@types/istanbul-lib-coverage@2.0.6: resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + dev: false /@types/istanbul-lib-report@3.0.3: resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} dependencies: '@types/istanbul-lib-coverage': 2.0.6 + dev: false /@types/istanbul-reports@3.0.4: resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} dependencies: '@types/istanbul-lib-report': 3.0.3 + dev: false - /@types/jest@29.5.12: - resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} + /@types/jest@29.5.14: + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} dependencies: expect: 29.7.0 pretty-format: 29.7.0 + dev: false /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1427,6 +1408,7 @@ packages: resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==} dependencies: undici-types: 5.26.5 + dev: false /@types/parse-json@4.0.2: resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -1479,16 +1461,19 @@ packages: /@types/stack-utils@2.0.3: resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + dev: false /@types/yargs-parser@21.0.3: resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + dev: false /@types/yargs@17.0.33: resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} dependencies: '@types/yargs-parser': 21.0.3 + dev: false - /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@5.4.5): + /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@5.6.3): resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1499,24 +1484,24 @@ packages: typescript: optional: true dependencies: - '@eslint-community/regexpp': 4.11.1 - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.4.5) + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.6.3) '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.1)(typescript@5.4.5) - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.4.5) + '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.6.3) debug: 4.3.7 eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 natural-compare-lite: 1.4.0 semver: 7.6.3 - tsutils: 3.21.0(typescript@5.4.5) - typescript: 5.4.5 + tsutils: 3.21.0(typescript@5.6.3) + typescript: 5.6.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.4.5): + /@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3): resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1528,10 +1513,10 @@ packages: dependencies: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.4.5) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.6.3) debug: 4.3.7 eslint: 8.57.1 - typescript: 5.4.5 + typescript: 5.6.3 transitivePeerDependencies: - supports-color dev: true @@ -1544,7 +1529,7 @@ packages: '@typescript-eslint/visitor-keys': 5.62.0 dev: true - /@typescript-eslint/type-utils@5.62.0(eslint@8.57.1)(typescript@5.4.5): + /@typescript-eslint/type-utils@5.62.0(eslint@8.57.1)(typescript@5.6.3): resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1554,12 +1539,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.4.5) - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.4.5) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.6.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.6.3) debug: 4.3.7 eslint: 8.57.1 - tsutils: 3.21.0(typescript@5.4.5) - typescript: 5.4.5 + tsutils: 3.21.0(typescript@5.6.3) + typescript: 5.6.3 transitivePeerDependencies: - supports-color dev: true @@ -1569,7 +1554,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree@5.62.0(typescript@5.4.5): + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.6.3): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1584,24 +1569,24 @@ packages: globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.3 - tsutils: 3.21.0(typescript@5.4.5) - typescript: 5.4.5 + tsutils: 3.21.0(typescript@5.6.3) + typescript: 5.6.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.4.5): + /@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.6.3): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.4.5) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.6.3) eslint: 8.57.1 eslint-scope: 5.1.1 semver: 7.6.3 @@ -1626,35 +1611,35 @@ packages: resolution: {integrity: sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA==} engines: {node: '>=8'} dependencies: - tslib: 2.7.0 + tslib: 2.8.1 dev: false /@wry/context@0.7.4: resolution: {integrity: sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ==} engines: {node: '>=8'} dependencies: - tslib: 2.7.0 + tslib: 2.8.1 dev: false /@wry/equality@0.5.7: resolution: {integrity: sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw==} engines: {node: '>=8'} dependencies: - tslib: 2.7.0 + tslib: 2.8.1 dev: false /@wry/trie@0.4.3: resolution: {integrity: sha512-I6bHwH0fSf6RqQcnnXLJKhkSXG45MFral3GxPaY4uAl0LYDZM+YDVDAiU9bYwjTuysy1S0IeecWtmq1SZA3M1w==} engines: {node: '>=8'} dependencies: - tslib: 2.7.0 + tslib: 2.8.1 dev: false /@wry/trie@0.5.0: resolution: {integrity: sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==} engines: {node: '>=8'} dependencies: - tslib: 2.7.0 + tslib: 2.8.1 dev: false /accepts@1.3.8: @@ -1665,16 +1650,16 @@ packages: negotiator: 0.6.3 dev: true - /acorn-jsx@5.3.2(acorn@8.12.1): + /acorn-jsx@5.3.2(acorn@8.14.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 8.12.1 + acorn: 8.14.0 dev: true - /acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + /acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true dev: true @@ -1697,12 +1682,6 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - /ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - dependencies: - color-convert: 1.9.3 - /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1855,15 +1834,15 @@ packages: possible-typed-array-names: 1.0.0 dev: true - /babel-literal-to-ast@2.1.0(@babel/core@7.25.7): + /babel-literal-to-ast@2.1.0(@babel/core@7.26.0): resolution: {integrity: sha512-CxfpQ0ysQ0bZOhlaPgcWjl79Em16Rhqc6++UAFn0A3duiXmuyhhj8yyl9PYbj0I0CyjrHovdDbp2QEKT7uIMxw==} peerDependencies: '@babel/core': ^7.1.2 dependencies: - '@babel/core': 7.25.7 - '@babel/parser': 7.25.7 - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.7 + '@babel/core': 7.26.0 + '@babel/parser': 7.26.2 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color dev: false @@ -1871,7 +1850,7 @@ packages: /babel-plugin-macros@2.8.0: resolution: {integrity: sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==} dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.26.0 cosmiconfig: 6.0.0 resolve: 1.22.8 dev: false @@ -1880,7 +1859,7 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.26.0 cosmiconfig: 7.1.0 resolve: 1.22.8 dev: false @@ -1942,15 +1921,15 @@ packages: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} dev: true - /browserslist@4.24.0: - resolution: {integrity: sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==} + /browserslist@4.24.2: + resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001667 - electron-to-chromium: 1.5.32 + caniuse-lite: 1.0.30001679 + electron-to-chromium: 1.5.55 node-releases: 2.0.18 - update-browserslist-db: 1.1.1(browserslist@4.24.0) + update-browserslist-db: 1.1.1(browserslist@4.24.2) /btoa@1.2.1: resolution: {integrity: sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==} @@ -1999,7 +1978,7 @@ packages: resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} dependencies: pascal-case: 3.1.2 - tslib: 2.7.0 + tslib: 2.8.1 dev: true /camelcase@6.3.0: @@ -2007,8 +1986,8 @@ packages: engines: {node: '>=10'} dev: true - /caniuse-lite@1.0.30001667: - resolution: {integrity: sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==} + /caniuse-lite@1.0.30001679: + resolution: {integrity: sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==} /catharsis@0.9.0: resolution: {integrity: sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==} @@ -2017,14 +1996,6 @@ packages: lodash: 4.17.21 dev: true - /chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - /chalk@3.0.0: resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} engines: {node: '>=8'} @@ -2062,6 +2033,7 @@ packages: /ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} + dev: false /clean-css@5.3.3: resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} @@ -2070,7 +2042,7 @@ packages: source-map: 0.6.1 dev: true - /clean-jsdoc-theme@4.3.0(jsdoc@4.0.3): + /clean-jsdoc-theme@4.3.0(jsdoc@4.0.4): resolution: {integrity: sha512-QMrBdZ2KdPt6V2Ytg7dIt0/q32U4COpxvR0UDhPjRRKRL0o0MvRCR5YpY37/4rPF1SI1AYEKAWyof7ndCb/dzA==} peerDependencies: jsdoc: '>=3.x <=4.x' @@ -2078,7 +2050,7 @@ packages: '@jsdoc/salty': 0.2.8 fs-extra: 10.1.0 html-minifier-terser: 7.2.0 - jsdoc: 4.0.3 + jsdoc: 4.0.4 klaw-sync: 6.0.0 lodash: 4.17.21 showdown: 2.1.0 @@ -2096,20 +2068,12 @@ packages: engines: {node: '>=6'} dev: false - /color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - dependencies: - color-name: 1.1.3 - /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - /color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -2163,8 +2127,8 @@ packages: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} dev: true - /cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + /cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} dev: true @@ -2193,8 +2157,8 @@ packages: yaml: 1.10.2 dev: false - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + /cross-spawn@7.0.5: + resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==} engines: {node: '>= 8'} dependencies: path-key: 3.1.1 @@ -2374,6 +2338,7 @@ packages: /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: false /diff@5.2.0: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} @@ -2417,7 +2382,7 @@ packages: /dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.26.0 csstype: 3.1.3 dev: false @@ -2425,7 +2390,7 @@ packages: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: no-case: 3.0.4 - tslib: 2.7.0 + tslib: 2.8.1 dev: true /duplexer@0.1.2: @@ -2444,8 +2409,8 @@ packages: jake: 10.9.2 dev: false - /electron-to-chromium@1.5.32: - resolution: {integrity: sha512-M+7ph0VGBQqqpTT2YrabjNKSQ2fEl9PVx6AK3N558gDH9NO8O6XN9SXXFWRo9u9PbEg/bWq+tjXQr+eXmxubCw==} + /electron-to-chromium@1.5.55: + resolution: {integrity: sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg==} /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2556,8 +2521,8 @@ packages: stop-iteration-iterator: 1.0.0 dev: true - /es-iterator-helpers@1.0.19: - resolution: {integrity: sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==} + /es-iterator-helpers@1.2.0: + resolution: {integrity: sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 @@ -2568,11 +2533,12 @@ packages: function-bind: 1.1.2 get-intrinsic: 1.2.4 globalthis: 1.0.4 + gopd: 1.0.1 has-property-descriptors: 1.0.2 has-proto: 1.0.3 has-symbols: 1.0.3 internal-slot: 1.0.7 - iterator.prototype: 1.1.2 + iterator.prototype: 1.1.3 safe-array-concat: 1.1.2 dev: true @@ -2607,35 +2573,36 @@ packages: is-symbol: 1.0.4 dev: true - /esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} - engines: {node: '>=12'} + /esbuild@0.24.0: + resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==} + engines: {node: '>=18'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/aix-ppc64': 0.19.12 - '@esbuild/android-arm': 0.19.12 - '@esbuild/android-arm64': 0.19.12 - '@esbuild/android-x64': 0.19.12 - '@esbuild/darwin-arm64': 0.19.12 - '@esbuild/darwin-x64': 0.19.12 - '@esbuild/freebsd-arm64': 0.19.12 - '@esbuild/freebsd-x64': 0.19.12 - '@esbuild/linux-arm': 0.19.12 - '@esbuild/linux-arm64': 0.19.12 - '@esbuild/linux-ia32': 0.19.12 - '@esbuild/linux-loong64': 0.19.12 - '@esbuild/linux-mips64el': 0.19.12 - '@esbuild/linux-ppc64': 0.19.12 - '@esbuild/linux-riscv64': 0.19.12 - '@esbuild/linux-s390x': 0.19.12 - '@esbuild/linux-x64': 0.19.12 - '@esbuild/netbsd-x64': 0.19.12 - '@esbuild/openbsd-x64': 0.19.12 - '@esbuild/sunos-x64': 0.19.12 - '@esbuild/win32-arm64': 0.19.12 - '@esbuild/win32-ia32': 0.19.12 - '@esbuild/win32-x64': 0.19.12 + '@esbuild/aix-ppc64': 0.24.0 + '@esbuild/android-arm': 0.24.0 + '@esbuild/android-arm64': 0.24.0 + '@esbuild/android-x64': 0.24.0 + '@esbuild/darwin-arm64': 0.24.0 + '@esbuild/darwin-x64': 0.24.0 + '@esbuild/freebsd-arm64': 0.24.0 + '@esbuild/freebsd-x64': 0.24.0 + '@esbuild/linux-arm': 0.24.0 + '@esbuild/linux-arm64': 0.24.0 + '@esbuild/linux-ia32': 0.24.0 + '@esbuild/linux-loong64': 0.24.0 + '@esbuild/linux-mips64el': 0.24.0 + '@esbuild/linux-ppc64': 0.24.0 + '@esbuild/linux-riscv64': 0.24.0 + '@esbuild/linux-s390x': 0.24.0 + '@esbuild/linux-x64': 0.24.0 + '@esbuild/netbsd-x64': 0.24.0 + '@esbuild/openbsd-arm64': 0.24.0 + '@esbuild/openbsd-x64': 0.24.0 + '@esbuild/sunos-x64': 0.24.0 + '@esbuild/win32-arm64': 0.24.0 + '@esbuild/win32-ia32': 0.24.0 + '@esbuild/win32-x64': 0.24.0 dev: true /escalade@3.2.0: @@ -2645,10 +2612,6 @@ packages: /escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - /escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - /escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} @@ -2657,36 +2620,36 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - /eslint-compat-utils@0.5.1(eslint@9.12.0): + /eslint-compat-utils@0.5.1(eslint@9.14.0): resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} engines: {node: '>=12'} peerDependencies: eslint: '>=6.0.0' dependencies: - eslint: 9.12.0(supports-color@9.4.0) + eslint: 9.14.0(supports-color@9.4.0) semver: 7.6.3 dev: true - /eslint-config-prettier@9.1.0(eslint@9.12.0): + /eslint-config-prettier@9.1.0(eslint@9.14.0): resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 9.12.0(supports-color@9.4.0) + eslint: 9.14.0(supports-color@9.4.0) dev: true - /eslint-config-standard-jsx@11.0.0(eslint-plugin-react@7.37.1)(eslint@8.57.1): + /eslint-config-standard-jsx@11.0.0(eslint-plugin-react@7.37.2)(eslint@8.57.1): resolution: {integrity: sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==} peerDependencies: eslint: ^8.8.0 eslint-plugin-react: ^7.28.0 dependencies: eslint: 8.57.1 - eslint-plugin-react: 7.37.1(eslint@8.57.1) + eslint-plugin-react: 7.37.2(eslint@8.57.1) dev: true - /eslint-config-standard-with-typescript@23.0.0(@typescript-eslint/eslint-plugin@5.62.0)(eslint-plugin-import@2.31.0)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.6.0)(eslint@8.57.1)(typescript@5.4.5): + /eslint-config-standard-with-typescript@23.0.0(@typescript-eslint/eslint-plugin@5.62.0)(eslint-plugin-import@2.31.0)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.6.0)(eslint@8.57.1)(typescript@5.6.3): resolution: {integrity: sha512-iaaWifImn37Z1OXbNW1es7KI+S7D408F9ys0bpaQf2temeBWlvb0Nc5qHkOgYaRb5QxTZT32GGeN1gtswASOXA==} deprecated: Please use eslint-config-love, instead. peerDependencies: @@ -2697,14 +2660,14 @@ packages: eslint-plugin-promise: ^6.0.0 typescript: '*' dependencies: - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@5.4.5) - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.4.5) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-config-standard: 17.0.0(eslint-plugin-import@2.31.0)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.6.0)(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1) eslint-plugin-n: 15.7.0(eslint@8.57.1) eslint-plugin-promise: 6.6.0(eslint@8.57.1) - typescript: 5.4.5 + typescript: 5.6.3 transitivePeerDependencies: - supports-color dev: true @@ -2754,7 +2717,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.4.5) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.6.3) debug: 3.2.7 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 @@ -2762,16 +2725,16 @@ packages: - supports-color dev: true - /eslint-plugin-es-x@7.8.0(eslint@9.12.0): + /eslint-plugin-es-x@7.8.0(eslint@9.14.0): resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: eslint: '>=8' dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.12.0) - '@eslint-community/regexpp': 4.11.1 - eslint: 9.12.0(supports-color@9.4.0) - eslint-compat-utils: 0.5.1(eslint@9.12.0) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0) + '@eslint-community/regexpp': 4.12.1 + eslint: 9.14.0(supports-color@9.4.0) + eslint-compat-utils: 0.5.1(eslint@9.14.0) dev: true /eslint-plugin-es@4.1.0(eslint@8.57.1): @@ -2796,7 +2759,7 @@ packages: optional: true dependencies: '@rtsao/scc': 1.1.0 - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.4.5) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.6.3) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 @@ -2822,14 +2785,14 @@ packages: - supports-color dev: true - /eslint-plugin-mocha@10.5.0(eslint@9.12.0): + /eslint-plugin-mocha@10.5.0(eslint@9.14.0): resolution: {integrity: sha512-F2ALmQVPT1GoP27O1JTZGrV9Pqg8k79OeIuvw63UxMtQKREZtmkK1NFgkZQ2TW7L2JSSFKHFPTtHu5z8R9QNRw==} engines: {node: '>=14.0.0'} peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 9.12.0(supports-color@9.4.0) - eslint-utils: 3.0.0(eslint@9.12.0) + eslint: 9.14.0(supports-color@9.4.0) + eslint-utils: 3.0.0(eslint@9.14.0) globals: 13.24.0 rambda: 7.5.0 dev: true @@ -2851,18 +2814,18 @@ packages: semver: 7.6.3 dev: true - /eslint-plugin-n@17.10.3(eslint@9.12.0): - resolution: {integrity: sha512-ySZBfKe49nQZWR1yFaA0v/GsH6Fgp8ah6XV0WDz6CN8WO0ek4McMzb7A2xnf4DCYV43frjCygvb9f/wx7UUxRw==} + /eslint-plugin-n@17.13.1(eslint@9.14.0): + resolution: {integrity: sha512-97qzhk1z3DdSJNCqT45EslwCu5+LB9GDadSyBItgKUfGsXAmN/aa7LRQ0ZxHffUxUzvgbTPJL27/pE9ZQWHy7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: '>=8.23.0' dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.12.0) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0) enhanced-resolve: 5.17.1 - eslint: 9.12.0(supports-color@9.4.0) - eslint-plugin-es-x: 7.8.0(eslint@9.12.0) + eslint: 9.14.0(supports-color@9.4.0) + eslint-plugin-es-x: 7.8.0(eslint@9.14.0) get-tsconfig: 4.8.1 - globals: 15.10.0 + globals: 15.12.0 ignore: 5.3.2 minimatch: 9.0.5 semver: 7.6.3 @@ -2873,7 +2836,7 @@ packages: engines: {node: '>=5.0.0'} dev: true - /eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0)(eslint@9.12.0)(prettier@3.3.3): + /eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0)(eslint@9.14.0)(prettier@3.3.3): resolution: {integrity: sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -2887,8 +2850,8 @@ packages: eslint-config-prettier: optional: true dependencies: - eslint: 9.12.0(supports-color@9.4.0) - eslint-config-prettier: 9.1.0(eslint@9.12.0) + eslint: 9.14.0(supports-color@9.4.0) + eslint-config-prettier: 9.1.0(eslint@9.14.0) prettier: 3.3.3 prettier-linter-helpers: 1.0.0 synckit: 0.9.2 @@ -2903,8 +2866,8 @@ packages: eslint: 8.57.1 dev: true - /eslint-plugin-react@7.37.1(eslint@8.57.1): - resolution: {integrity: sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==} + /eslint-plugin-react@7.37.2(eslint@8.57.1): + resolution: {integrity: sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==} engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 @@ -2914,7 +2877,7 @@ packages: array.prototype.flatmap: 1.3.2 array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 - es-iterator-helpers: 1.0.19 + es-iterator-helpers: 1.2.0 eslint: 8.57.1 estraverse: 5.3.0 hasown: 2.0.2 @@ -2946,8 +2909,8 @@ packages: estraverse: 5.3.0 dev: true - /eslint-scope@8.1.0: - resolution: {integrity: sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==} + /eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: esrecurse: 4.3.0 @@ -2971,13 +2934,13 @@ packages: eslint-visitor-keys: 2.1.0 dev: true - /eslint-utils@3.0.0(eslint@9.12.0): + /eslint-utils@3.0.0(eslint@9.14.0): resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} peerDependencies: eslint: '>=5' dependencies: - eslint: 9.12.0(supports-color@9.4.0) + eslint: 9.14.0(supports-color@9.4.0) eslint-visitor-keys: 2.1.0 dev: true @@ -2996,8 +2959,8 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint-visitor-keys@4.1.0: - resolution: {integrity: sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==} + /eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true @@ -3007,8 +2970,8 @@ packages: deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) - '@eslint-community/regexpp': 4.11.1 + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.1 '@eslint/eslintrc': 2.1.4 '@eslint/js': 8.57.1 '@humanwhocodes/config-array': 0.13.0 @@ -3017,7 +2980,7 @@ packages: '@ungap/structured-clone': 1.2.0 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.5 debug: 4.3.7 doctrine: 3.0.0 escape-string-regexp: 4.0.0 @@ -3049,8 +3012,8 @@ packages: - supports-color dev: true - /eslint@9.12.0(supports-color@9.4.0): - resolution: {integrity: sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==} + /eslint@9.14.0(supports-color@9.4.0): + resolution: {integrity: sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -3059,26 +3022,26 @@ packages: jiti: optional: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.12.0) - '@eslint-community/regexpp': 4.11.1 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0) + '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.18.0(supports-color@9.4.0) - '@eslint/core': 0.6.0 + '@eslint/core': 0.7.0 '@eslint/eslintrc': 3.1.0(supports-color@9.4.0) - '@eslint/js': 9.12.0 - '@eslint/plugin-kit': 0.2.0 - '@humanfs/node': 0.16.5 + '@eslint/js': 9.14.0 + '@eslint/plugin-kit': 0.2.2 + '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.3.1 + '@humanwhocodes/retry': 0.4.1 '@types/estree': 1.0.6 '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.5 debug: 4.3.7(supports-color@9.4.0) escape-string-regexp: 4.0.0 - eslint-scope: 8.1.0 - eslint-visitor-keys: 4.1.0 - espree: 10.2.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -3098,21 +3061,21 @@ packages: - supports-color dev: true - /espree@10.2.0: - resolution: {integrity: sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==} + /espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) - eslint-visitor-keys: 4.1.0 + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 dev: true /espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) eslint-visitor-keys: 3.4.3 dev: true @@ -3163,9 +3126,10 @@ packages: jest-matcher-utils: 29.7.0 jest-message-util: 29.7.0 jest-util: 29.7.0 + dev: false - /express@4.21.0(supports-color@9.4.0): - resolution: {integrity: sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==} + /express@4.21.1(supports-color@9.4.0): + resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==} engines: {node: '>= 0.10.0'} dependencies: accepts: 1.3.8 @@ -3173,7 +3137,7 @@ packages: body-parser: 1.20.3(supports-color@9.4.0) content-disposition: 0.5.4 content-type: 1.0.5 - cookie: 0.6.0 + cookie: 0.7.1 cookie-signature: 1.0.6 debug: 2.6.9(supports-color@9.4.0) depd: 2.0.0 @@ -3475,8 +3439,8 @@ packages: engines: {node: '>=18'} dev: true - /globals@15.10.0: - resolution: {integrity: sha512-tqFIbz83w4Y5TCbtgjZjApohbuh7K9BxGYFm7ifwDR240tvdb7P9x+/9VvUKlmkPoiknoJtanI8UOrqxS3a7lQ==} + /globals@15.12.0: + resolution: {integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==} engines: {node: '>=18'} dev: true @@ -3513,31 +3477,31 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true - /graphql-tag@2.12.6(graphql@16.8.1): + /graphql-tag@2.12.6(graphql@16.9.0): resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==} engines: {node: '>=10'} peerDependencies: graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 dependencies: - graphql: 16.8.1 - tslib: 2.7.0 + graphql: 16.9.0 + tslib: 2.8.1 dev: false - /graphql.macro@1.4.2(@babel/core@7.25.7)(graphql@16.8.1): + /graphql.macro@1.4.2(@babel/core@7.26.0)(graphql@16.9.0): resolution: {integrity: sha512-vcIaStPgS65gp5i1M3DSBimNVkyus0Z7k4VObWAyZS319tKlpX/TEIJSWTgOZU5k8dn4RRzGoS/elQhX2E6yBw==} dependencies: - '@babel/template': 7.25.7 - babel-literal-to-ast: 2.1.0(@babel/core@7.25.7) + '@babel/template': 7.25.9 + babel-literal-to-ast: 2.1.0(@babel/core@7.26.0) babel-plugin-macros: 2.8.0 - graphql-tag: 2.12.6(graphql@16.8.1) + graphql-tag: 2.12.6(graphql@16.9.0) transitivePeerDependencies: - '@babel/core' - graphql - supports-color dev: false - /graphql@16.8.1: - resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} + /graphql@16.9.0: + resolution: {integrity: sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} dev: false @@ -3552,10 +3516,6 @@ packages: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true - /has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -3616,7 +3576,7 @@ packages: entities: 4.5.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.34.1 + terser: 5.36.0 dev: true /http-errors@1.6.3: @@ -3926,8 +3886,9 @@ packages: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true - /iterator.prototype@1.1.2: - resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} + /iterator.prototype@1.1.3: + resolution: {integrity: sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==} + engines: {node: '>= 0.4'} dependencies: define-properties: 1.2.1 get-intrinsic: 1.2.4 @@ -3955,10 +3916,12 @@ packages: diff-sequences: 29.6.3 jest-get-type: 29.6.3 pretty-format: 29.7.0 + dev: false /jest-get-type@29.6.3: resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: false /jest-matcher-utils@29.7.0: resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} @@ -3968,12 +3931,13 @@ packages: jest-diff: 29.7.0 jest-get-type: 29.6.3 pretty-format: 29.7.0 + dev: false /jest-message-util@29.7.0: resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/code-frame': 7.25.7 + '@babel/code-frame': 7.26.2 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.3 chalk: 4.1.2 @@ -3982,6 +3946,7 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 + dev: false /jest-util@29.7.0: resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} @@ -3993,6 +3958,7 @@ packages: ci-info: 3.9.0 graceful-fs: 4.2.11 picomatch: 2.3.1 + dev: false /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -4010,12 +3976,12 @@ packages: xmlcreate: 2.0.4 dev: true - /jsdoc@4.0.3: - resolution: {integrity: sha512-Nu7Sf35kXJ1MWDZIMAuATRQTg1iIPdzh7tqJ6jjvaU/GfDf+qi5UV8zJR3Mo+/pYFvm8mzay4+6O5EWigaQBQw==} + /jsdoc@4.0.4: + resolution: {integrity: sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==} engines: {node: '>=12.0.0'} hasBin: true dependencies: - '@babel/parser': 7.25.7 + '@babel/parser': 7.26.2 '@jsdoc/salty': 0.2.8 '@types/markdown-it': 14.1.2 bluebird: 3.7.2 @@ -4208,7 +4174,7 @@ packages: /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - tslib: 2.7.0 + tslib: 2.8.1 dev: true /lru-cache@5.1.1: @@ -4349,7 +4315,7 @@ packages: hasBin: true dev: true - /mocha-junit-reporter@2.2.1(mocha@10.7.3)(supports-color@9.4.0): + /mocha-junit-reporter@2.2.1(mocha@10.8.2)(supports-color@9.4.0): resolution: {integrity: sha512-iDn2tlKHn8Vh8o4nCzcUVW4q7iXp7cC4EB78N0cDHIobLymyHNwe0XG8HEHHjc3hJlXm0Vy6zcrxaIhnI2fWmw==} peerDependencies: mocha: '>=2.2.5' @@ -4357,15 +4323,15 @@ packages: debug: 4.3.7(supports-color@9.4.0) md5: 2.3.0 mkdirp: 3.0.1 - mocha: 10.7.3 + mocha: 10.8.2 strip-ansi: 6.0.1 xml: 1.0.1 transitivePeerDependencies: - supports-color dev: true - /mocha@10.7.3: - resolution: {integrity: sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==} + /mocha@10.8.2: + resolution: {integrity: sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==} engines: {node: '>= 14.0.0'} hasBin: true dependencies: @@ -4428,7 +4394,7 @@ packages: resolution: {integrity: sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==} dependencies: '@sinonjs/commons': 3.0.1 - '@sinonjs/fake-timers': 13.0.2 + '@sinonjs/fake-timers': 13.0.5 '@sinonjs/text-encoding': 0.7.3 just-extend: 6.2.0 path-to-regexp: 8.2.0 @@ -4438,7 +4404,7 @@ packages: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 - tslib: 2.7.0 + tslib: 2.8.1 dev: true /node-releases@2.0.18: @@ -4544,7 +4510,7 @@ packages: '@wry/caches': 1.0.1 '@wry/context': 0.7.4 '@wry/trie': 0.4.3 - tslib: 2.7.0 + tslib: 2.8.1 dev: false /optionator@0.9.4: @@ -4614,7 +4580,7 @@ packages: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} dependencies: dot-case: 3.0.4 - tslib: 2.7.0 + tslib: 2.8.1 dev: true /parent-module@1.0.1: @@ -4635,7 +4601,7 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.25.7 + '@babel/code-frame': 7.26.2 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -4655,7 +4621,7 @@ packages: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} dependencies: no-case: 3.0.4 - tslib: 2.7.0 + tslib: 2.8.1 dev: true /path-browserify@1.0.1: @@ -4702,8 +4668,8 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - /picocolors@1.1.0: - resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + /picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -4769,9 +4735,10 @@ packages: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 react-is: 18.3.1 + dev: false - /pretty-ms@9.0.0: - resolution: {integrity: sha512-E9e9HJ9R9NasGOgPaPE8VMeiPKAyWR5jcFpNnwIejslIhWqdqOrb2wShBsncMPUb+BcCd2OPYfh7p2W6oemTng==} + /pretty-ms@9.1.0: + resolution: {integrity: sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw==} engines: {node: '>=18'} dependencies: parse-ms: 4.0.0 @@ -4841,13 +4808,13 @@ packages: unpipe: 1.0.0 dev: true - /react-dom@18.2.0(react@18.2.0): - resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + /react-dom@18.3.1(react@18.3.1): + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: - react: ^18.2.0 + react: ^18.3.1 dependencies: loose-envify: 1.4.0 - react: 18.2.0 + react: 18.3.1 scheduler: 0.23.2 /react-is@16.13.1: @@ -4859,12 +4826,13 @@ packages: /react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + dev: false /react-lifecycles-compat@3.0.4: resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} dev: false - /react-modal@3.16.1(react-dom@18.2.0)(react@18.2.0): + /react-modal@3.16.1(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==} engines: {node: '>=8'} peerDependencies: @@ -4873,51 +4841,51 @@ packages: dependencies: exenv: 1.2.2 prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) react-lifecycles-compat: 3.0.4 warning: 4.0.3 dev: false - /react-router-dom@6.22.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==} + /react-router-dom@6.27.0(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' dependencies: - '@remix-run/router': 1.15.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-router: 6.22.3(react@18.2.0) + '@remix-run/router': 1.20.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.27.0(react@18.3.1) dev: false - /react-router@6.22.3(react@18.2.0): - resolution: {integrity: sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==} + /react-router@6.27.0(react@18.3.1): + resolution: {integrity: sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' dependencies: - '@remix-run/router': 1.15.3 - react: 18.2.0 + '@remix-run/router': 1.20.0 + react: 18.3.1 dev: false - /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): + /react-transition-group@4.4.5(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: react: '>=16.6.0' react-dom: '>=16.6.0' dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.26.0 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: false - /react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + /react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} dependencies: loose-envify: 1.4.0 @@ -4979,7 +4947,7 @@ packages: engines: {node: '>=8'} dev: true - /rehackt@0.1.0(@types/react@18.2.72)(react@18.2.0): + /rehackt@0.1.0(@types/react@18.2.72)(react@18.3.1): resolution: {integrity: sha512-7kRDOuLHB87D/JESKxQoRwv4DzbIdwkAGQ7p6QKGdVlY1IZheUnVhlk/4UZlNUVxdAXpyxikE3URsG067ybVzw==} peerDependencies: '@types/react': '*' @@ -4991,7 +4959,7 @@ packages: optional: true dependencies: '@types/react': 18.2.72 - react: 18.2.0 + react: 18.3.1 dev: false /relateurl@0.2.7: @@ -5232,7 +5200,7 @@ packages: resolution: {integrity: sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==} dependencies: '@sinonjs/commons': 3.0.1 - '@sinonjs/fake-timers': 13.0.2 + '@sinonjs/fake-timers': 13.0.5 '@sinonjs/samsam': 8.0.2 diff: 7.0.0 nise: 6.1.1 @@ -5289,6 +5257,7 @@ packages: engines: {node: '>=10'} dependencies: escape-string-regexp: 2.0.0 + dev: false /standard-engine@15.1.0: resolution: {integrity: sha512-VHysfoyxFu/ukT+9v49d4BRXIokFRZuH3z1VRxzFArZdjSCFpro6rEIU3ji7e4AoAtuSfKBkiOmsrDqKW5ZSRw==} @@ -5414,12 +5383,6 @@ packages: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} dev: false - /supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - dependencies: - has-flag: 3.0.0 - /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -5452,7 +5415,7 @@ packages: engines: {node: ^14.18.0 || >=16.0.0} dependencies: '@pkgr/core': 0.1.1 - tslib: 2.7.0 + tslib: 2.8.1 dev: true /tapable@2.2.1: @@ -5468,13 +5431,13 @@ packages: rimraf: 2.6.3 dev: false - /terser@5.34.1: - resolution: {integrity: sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==} + /terser@5.36.0: + resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==} engines: {node: '>=10'} hasBin: true dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.12.1 + acorn: 8.14.0 commander: 2.20.3 source-map-support: 0.5.21 dev: true @@ -5488,10 +5451,6 @@ packages: engines: {node: '>=14.14'} dev: false - /to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -5507,29 +5466,29 @@ packages: resolution: {integrity: sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==} engines: {node: '>=8'} dependencies: - tslib: 2.7.0 + tslib: 2.8.1 dev: false - /ts-standard@12.0.2(typescript@5.4.5): + /ts-standard@12.0.2(typescript@5.6.3): resolution: {integrity: sha512-XX2wrB9fKKTfBj4yD3ABm9iShzZcS2iWcPK8XzlBvuL20+wMiLgiz/k5tXgZwTaYq5wRhbks1Y9PelhujF/9ag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true peerDependencies: typescript: '*' dependencies: - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@5.4.5) - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.4.5) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 - eslint-config-standard-jsx: 11.0.0(eslint-plugin-react@7.37.1)(eslint@8.57.1) - eslint-config-standard-with-typescript: 23.0.0(@typescript-eslint/eslint-plugin@5.62.0)(eslint-plugin-import@2.31.0)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.6.0)(eslint@8.57.1)(typescript@5.4.5) + eslint-config-standard-jsx: 11.0.0(eslint-plugin-react@7.37.2)(eslint@8.57.1) + eslint-config-standard-with-typescript: 23.0.0(@typescript-eslint/eslint-plugin@5.62.0)(eslint-plugin-import@2.31.0)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.6.0)(eslint@8.57.1)(typescript@5.6.3) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1) eslint-plugin-n: 15.7.0(eslint@8.57.1) eslint-plugin-promise: 6.6.0(eslint@8.57.1) - eslint-plugin-react: 7.37.1(eslint@8.57.1) + eslint-plugin-react: 7.37.2(eslint@8.57.1) minimist: 1.2.8 pkg-conf: 4.0.0 standard-engine: 15.1.0 - typescript: 5.4.5 + typescript: 5.6.3 transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -5549,17 +5508,17 @@ packages: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true - /tslib@2.7.0: - resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + /tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - /tsutils@3.21.0(typescript@5.4.5): + /tsutils@3.21.0(typescript@5.6.3): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.4.5 + typescript: 5.6.3 dev: true /type-check@0.4.0: @@ -5645,8 +5604,8 @@ packages: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} dev: true - /typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + /typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true dev: true @@ -5670,6 +5629,7 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: false /universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} @@ -5681,15 +5641,15 @@ packages: engines: {node: '>= 0.8'} dev: true - /update-browserslist-db@1.1.1(browserslist@4.24.0): + /update-browserslist-db@1.1.1(browserslist@4.24.2): resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: 4.24.0 + browserslist: 4.24.2 escalade: 3.2.0 - picocolors: 1.1.0 + picocolors: 1.1.1 /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} diff --git a/py/BUILD.bazel b/py/BUILD.bazel index e2ba6dd2019f1..beba3d5684c7c 100644 --- a/py/BUILD.bazel +++ b/py/BUILD.bazel @@ -62,13 +62,13 @@ compile_pip_requirements( ], ) -SE_VERSION = "4.26.0.dev202409202351" +SE_VERSION = "4.27.0.dev202410311942" BROWSER_VERSIONS = [ "v85", "v128", "v129", - "v127", + "v130", ] TEST_DEPS = [ @@ -228,7 +228,6 @@ pkg_files( name = "selenium-sdist-pkg", srcs = [ "CHANGES", - "MANIFEST.in", "README.rst", "pyproject.toml", "setup.py", @@ -336,7 +335,6 @@ py_library( ], data = [ "pyproject.toml", - "setup.cfg", "test/selenium/webdriver/common/test_file.txt", "test/selenium/webdriver/common/test_file2.txt", ":webextensions-selenium-example-unsigned-zip", @@ -597,9 +595,7 @@ py_test_suite( name = "test-safari", size = "large", srcs = glob([ - "test/selenium/webdriver/common/**/*.py", "test/selenium/webdriver/safari/**/*.py", - "test/selenium/webdriver/support/**/*.py", ]), args = [ "--instafail", diff --git a/py/CHANGES b/py/CHANGES index a4e6fd42cb226..b7dabbf9d6b9b 100644 --- a/py/CHANGES +++ b/py/CHANGES @@ -1,3 +1,23 @@ +Selenium 4.26.1 +* DeprecationWarning raised in default webdriver init (#14690) +* Remote connection use timeout from ClientConfig (#14692) +* Add backward compatibility for AppiumConnection (#14696) + +Selenium 4.26.0 +* Add CDP for Chrome 130 and remove 127 +* Added more internal logging for CDP (#14668) +* Set consistent polling across java and python for `WebDriverWait` methods (#14626) +* webkitgtk: log_path -> log_output (#14618) +* Implement configurable configuration class for the http client (#13286) +* Better compatibility with Appium-python (#14587) +* Avoid waiting indefinitely on a frozen chromedriver process (#14578) +* Allow logging diagnose in safari driver (#14606) +* Remote connection throws response status code when data is empty (#14601) +* Remove deprecated parameter from EdgeService (#14563) +* Allow driver path to be set using ENV variables (#14528) +* Remove un-needed print (#14562) +* Fix a bug in `bidi/session.py` by removing mutable object as default value for function argument (#14286) + Selenium 4.25.0 * Add CDP for Chrome 129 and remove 126 * fix type errors for `service.py`, `cdp.py`, `webelement.py` and `remote_connection.py` (#14448) diff --git a/py/MANIFEST.in b/py/MANIFEST.in deleted file mode 100644 index b46446d38ec50..0000000000000 --- a/py/MANIFEST.in +++ /dev/null @@ -1,24 +0,0 @@ -prune * -recursive-include selenium/webdriver *.py -recursive-include selenium/webdriver/common *.py -recursive-include selenium/webdriver/common/actions *.py -recursive-include selenium/webdriver/common/html5 *.py -recursive-include selenium/common *.py -recursive-include selenium/webdriver/chromium *.py -recursive-include selenium/webdriver/chrome *.py -recursive-include selenium/webdriver/phantomjs *.py -recursive-include selenium/webdriver/firefox *.py *.xpi *.json -recursive-include selenium/webdriver/ie *.py -recursive-include selenium/webdriver/edge *.py -recursive-include selenium/webdriver/remote *.py *.js -recursive-include selenium/webdriver/support *.py -include selenium/selenium.py -include selenium/__init__.py -include selenium/py.typed -include selenium/webdriver/common/linux/selenium-manager -include selenium/webdriver/common/macos/selenium-manager -include selenium/webdriver/common/windows/selenium-manager.exe -include CHANGES -include README.rst -include LICENSE -recursive-include selenium.egg-info * diff --git a/py/docs/README.rst b/py/docs/README.rst new file mode 100644 index 0000000000000..8b58a410e00ff --- /dev/null +++ b/py/docs/README.rst @@ -0,0 +1,71 @@ +========================== +About Python Documentation +========================== + +This directory, ``py/docs``, is the source for the API Reference Documentation +and basic Python documentation as well as the main README for the GitHub and +PyPI package. + + +How to build docs +================= + +One can build the Python documentation without doing a full bazel build. The +following instructions will build the setup a virtual environment and installs tox, clones the +selenium repo and then runs ``tox -c py/tox.ini`` building the Python documentation. + +.. code-block:: console + + virtualenv test-py38-env + source test-py38-env/bin/activate + pip install tox + git clone git@github.com:SeleniumHQ/selenium.git + cd selenium/ + # uncomment and switch to your development branch if needed + #git switch -c feature-branch origin/feature-branch + tox -c py/tox.ini + +Works in similar manner as the larger selenium bazel doing just the python documentation portion. + +What is happening under the covers of tox, sphinx +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tox is essentially a build tool for Python. Here it setups its own virtualenv and installs the +documentation packages (sphinx and jinja2) as well as the required selenium python +dependencies. Then tox runs the sphinx generate autodoc stub files and then builds the docs. + +Sphinx is .. well a much larger topic then what we could cover here. Most important to say +here is that the docs are using the "classic" theme and than the majority of the api documentation +is autogenerated. There is plenty of information available and currently the documentation is +fairly small and not complex. So some basic understanding of restructed text and the Sphinx tool chain +should be sufficient to contribute and develop the Python docs. + +To clean up the build / tox cache +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Although there is a Sphinx Makefile option to clean up using the tox environment above one can +manually clean the build by deleting the build directory on the root (selenium/build using the +default directory on git clone). Noting that both Sphinx and tox cache parts of their build and +recognize changes to speed up their respective builds. But at times one wants to start fresh +deleting the aformentioned directory will clean the Sphinx cache or removing selenium/py/.tox +directory for cleaning the tox environment. + + +Known documentation issues +========================== +The API Reference primarily builds from the source code. But currently the initial template stating +which modules to document is hard coded within py/docs/source/api.rst. So if modules are added or +removed then the generated docs will be inaccurate. It would be preferred that the API docs generate +soley from the code if possible. This is being tracked in `#14178 `_ + +We are working through the Sphinx build Warnings and Errors trying to clean up botht the syntax and +the build. + +Contributing to Python docs +=========================== +First it is recommended that you read the main `CONTRIBUTING.md `_. + +Some steps for contibuting to the Python documentation .. + +- Check out changes locally using instruction above. +- Try to resolve any warnings/issues. + - If too arduous either ask for help or add to list of known issue. +- If this process is updated please update this doc as well to help the next person. \ No newline at end of file diff --git a/py/docs/source/conf.py b/py/docs/source/conf.py index d0f2f6b947180..1d5148a0508fa 100644 --- a/py/docs/source/conf.py +++ b/py/docs/source/conf.py @@ -56,9 +56,9 @@ # built documents. # # The short X.Y version. -version = '4.26' +version = '4.27' # The full version, including alpha/beta/rc tags. -release = '4.26.0.dev202409202351' +release = '4.27.0.dev202410311942' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/py/mypy.ini b/py/mypy.ini deleted file mode 100644 index 9ccefe45dd10c..0000000000000 --- a/py/mypy.ini +++ /dev/null @@ -1,39 +0,0 @@ -; The aim in future here is we would be able to turn (most) of these flags on, however the typing technical -; debt is quite colossal right now. For now we should maybe get everything working with the config here -; then look at going after partially or completely untyped defs as a phase-2. -[mypy] -files = selenium -; warn about per-module sections in the config file that do not match any files processed. -warn_unused_configs = True -; disallows subclassing of typing.Any. -disallow_subclassing_any = False -; disallow usage of generic types that do not specify explicit type parameters. -disallow_any_generics = False -; disallow calling functions without type annotations from functions that have type annotations. -disallow_untyped_calls = False -; disallow defining functions without type annotations or with incomplete annotations. -disallow_untyped_defs = False -; disallow defining functions with incomplete type annotations. -disallow_incomplete_defs = False -; type-checks the interior of functions without type annotations. -check_untyped_defs = False -; reports an error whenever a function with type annotations is decorated with a decorator without annotations. -disallow_untyped_decorators = False -; changes the treatment of arguments with a default value of None by not implicitly making their type `typing.Optional`. -no_implicit_optional = False -; warns about casting an expression to it's inferred type. -warn_redundant_casts = True -; warns about unneeded `# type: ignore` comments. -warn_unused_ignores = True -; warns when returning a value with typing.Any from a function with a non typing.Any return type. -warn_return_any = False -; Shows a warning when encountering any code inferred to be unreachable after performing type analysis. -warn_unreachable = False - -[mypy-trio_websocket] -; suppress error messages about imports that cannot be resolved. -ignore_missing_imports = True - -[mypy-_winreg] -; suppress error messages about imports that cannot be resolved. -ignore_missing_imports = True diff --git a/py/pyproject.toml b/py/pyproject.toml index c17be72be8eb3..eecffc1f3c3c8 100644 --- a/py/pyproject.toml +++ b/py/pyproject.toml @@ -2,6 +2,28 @@ requires = ["setuptools", "setuptools-rust"] build-backend = "setuptools.build_meta" +[tool.setuptools.packages.find] +include = ["selenium*"] +exclude = ["test*"] +namespaces = false +# include-package-data is `true` by default in pyproject.toml + +[tool.setuptools.package-data] +selenium_package = [ + "*.py", + "*.rst", + "*.json", + "*.xpi", + "*.js", + "py.typed", + "prune*", + "selenium.egg-info*", + "selenium-manager", + "selenium-manager.exe", + "CHANGES", + "LICENSE" +] + [tool.pytest.ini_options] console_output_style = "progress" faulthandler_timeout = 60 @@ -19,3 +41,61 @@ markers = [ ] python_files = ["test_*.py", "*_test.py"] testpaths = ["test"] + +# mypy global options +[tool.mypy] +# The aim in future here is we would be able to turn (most) of these flags on, however the typing technical +# debt is quite colossal right now. For now we should maybe get everything working with the config here +# then look at going after partially or completely untyped defs as a phase-2. +files = "selenium" +# warn about per-module sections in the config file that do not match any files processed. +warn_unused_configs = true +# disallows subclassing of typing.Any. +disallow_subclassing_any = false +# disallow usage of generic types that do not specify explicit type parameters. +disallow_any_generics = false +# disallow calling functions without type annotations from functions that have type annotations. +disallow_untyped_calls = false +# disallow defining functions without type annotations or with incomplete annotations. +disallow_untyped_defs = false +# disallow defining functions with incomplete type annotations. +disallow_incomplete_defs = false +# type-checks the interior of functions without type annotations. +check_untyped_defs = false +# reports an error whenever a function with type annotations is decorated with a decorator without annotations. +disallow_untyped_decorators = false +# changes the treatment of arguments with a default value of None by not implicitly making their type `typing.Optional`. +no_implicit_optional = false +# warns about casting an expression to it's inferred type. +warn_redundant_casts = true +# warns about unneeded `# type: ignore` comments. +warn_unused_ignores = true +# warns when returning a value with typing.Any from a function with a non typing.Any return type. +warn_return_any = false +# Shows a warning when encountering any code inferred to be unreachable after performing type analysis. +warn_unreachable = false + +# mypy module specific options +[[tool.mypy.trio_websocket]] +# suppress error messages about imports that cannot be resolved. +ignore_missing_imports = true + +[[tool.mypy._winreg]] +# suppress error messages about imports that cannot be resolved. +ignore_missing_imports = true + +[tool.isort] +# isort is a common python tool for keeping imports nicely formatted. +# Automatically keep imports alphabetically sorted, on single lines in +# PEP recommended sections (https://peps.python.org/pep-0008/#imports) +# files or individual lines can be ignored via `# isort:skip|# isort:skip_file`. +profile = "black" +py_version=38 +force_single_line = true + +[tool.black] +line-length = 120 +target-version = ['py38'] + +[tool.docformatter] +recursive = true diff --git a/py/requirements.txt b/py/requirements.txt index 7520086bbd7f0..38085a1181db8 100644 --- a/py/requirements.txt +++ b/py/requirements.txt @@ -3,7 +3,7 @@ attrs==23.2.0 certifi==2023.11.17 cffi==1.16.0 cryptography==42.0.8 -debugpy==1.8.5 +debugpy==1.8.7 h11==0.14.0 idna==3.7 importlib-metadata==6.8.0 diff --git a/py/selenium/__init__.py b/py/selenium/__init__.py index e4c5adeea398b..e7ab5cc0b075e 100644 --- a/py/selenium/__init__.py +++ b/py/selenium/__init__.py @@ -16,4 +16,4 @@ # under the License. -__version__ = "4.26.0.dev202409202351" +__version__ = "4.27.0.dev202410311942" diff --git a/py/selenium/common/__init__.py b/py/selenium/common/__init__.py index 2ef6204d37070..88269940125d9 100644 --- a/py/selenium/common/__init__.py +++ b/py/selenium/common/__init__.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. +from .exceptions import DetachedShadowRootException from .exceptions import ElementClickInterceptedException from .exceptions import ElementNotInteractableException from .exceptions import ElementNotSelectableException @@ -83,4 +84,5 @@ "InvalidSessionIdException", "SessionNotCreatedException", "UnknownMethodException", + "DetachedShadowRootException", ] diff --git a/py/selenium/common/exceptions.py b/py/selenium/common/exceptions.py index 931eb307f809f..d1d7efdd1f0bd 100644 --- a/py/selenium/common/exceptions.py +++ b/py/selenium/common/exceptions.py @@ -285,3 +285,7 @@ def __init__( with_support = f"{msg}; {SUPPORT_MSG} {ERROR_URL}/driver_location" super().__init__(with_support, screen, stacktrace) + + +class DetachedShadowRootException(WebDriverException): + """Raised when referenced shadow root is no longer attached to the DOM.""" diff --git a/py/selenium/webdriver/__init__.py b/py/selenium/webdriver/__init__.py index e61f321c187e0..b64471644e380 100644 --- a/py/selenium/webdriver/__init__.py +++ b/py/selenium/webdriver/__init__.py @@ -44,7 +44,7 @@ from .wpewebkit.service import Service as WPEWebKitService # noqa from .wpewebkit.webdriver import WebDriver as WPEWebKit # noqa -__version__ = "4.26.0.dev202409202351" +__version__ = "4.27.0.dev202410311942" # We need an explicit __all__ because the above won't otherwise be exported. __all__ = [ diff --git a/py/selenium/webdriver/chrome/remote_connection.py b/py/selenium/webdriver/chrome/remote_connection.py index d20ac581f36d7..1aa34dbfa4b31 100644 --- a/py/selenium/webdriver/chrome/remote_connection.py +++ b/py/selenium/webdriver/chrome/remote_connection.py @@ -14,10 +14,12 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -import typing + +from typing import Optional from selenium.webdriver import DesiredCapabilities from selenium.webdriver.chromium.remote_connection import ChromiumRemoteConnection +from selenium.webdriver.remote.client_config import ClientConfig class ChromeRemoteConnection(ChromiumRemoteConnection): @@ -27,7 +29,8 @@ def __init__( self, remote_server_addr: str, keep_alive: bool = True, - ignore_proxy: typing.Optional[bool] = False, + ignore_proxy: Optional[bool] = False, + client_config: Optional[ClientConfig] = None, ) -> None: super().__init__( remote_server_addr=remote_server_addr, @@ -35,4 +38,5 @@ def __init__( browser_name=ChromeRemoteConnection.browser_name, keep_alive=keep_alive, ignore_proxy=ignore_proxy, + client_config=client_config, ) diff --git a/py/selenium/webdriver/chromium/remote_connection.py b/py/selenium/webdriver/chromium/remote_connection.py index 29d33499111cf..ea532cba8ddcd 100644 --- a/py/selenium/webdriver/chromium/remote_connection.py +++ b/py/selenium/webdriver/chromium/remote_connection.py @@ -14,7 +14,9 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +from typing import Optional +from selenium.webdriver.remote.client_config import ClientConfig from selenium.webdriver.remote.remote_connection import RemoteConnection @@ -25,9 +27,16 @@ def __init__( vendor_prefix: str, browser_name: str, keep_alive: bool = True, - ignore_proxy: bool = False, + ignore_proxy: Optional[bool] = False, + client_config: Optional[ClientConfig] = None, ) -> None: - super().__init__(remote_server_addr, keep_alive, ignore_proxy) + client_config = client_config or ClientConfig( + remote_server_addr=remote_server_addr, keep_alive=keep_alive, timeout=120 + ) + super().__init__( + ignore_proxy=ignore_proxy, + client_config=client_config, + ) self.browser_name = browser_name commands = self._remote_commands(vendor_prefix) for key, value in commands.items(): diff --git a/py/selenium/webdriver/chromium/webdriver.py b/py/selenium/webdriver/chromium/webdriver.py index d7bf5c706a453..af563f41672a8 100644 --- a/py/selenium/webdriver/chromium/webdriver.py +++ b/py/selenium/webdriver/chromium/webdriver.py @@ -77,9 +77,9 @@ def launch_app(self, id): def get_network_conditions(self): """Gets Chromium network emulation settings. - :Returns: A dict. For example: {'latency': 4, - 'download_throughput': 2, 'upload_throughput': 2, 'offline': - False} + :Returns: + A dict. + For example: {'latency': 4, 'download_throughput': 2, 'upload_throughput': 2, 'offline': False} """ return self.execute("getNetworkConditions")["value"] diff --git a/py/selenium/webdriver/common/bidi/cdp.py b/py/selenium/webdriver/common/bidi/cdp.py index c4cb0feeedf40..c9ed47825e4da 100644 --- a/py/selenium/webdriver/common/bidi/cdp.py +++ b/py/selenium/webdriver/common/bidi/cdp.py @@ -211,13 +211,19 @@ async def execute(self, cmd: typing.Generator[dict, T, typing.Any]) -> T: if self.session_id: request["sessionId"] = self.session_id request_str = json.dumps(request) + if logger.isEnabledFor(logging.DEBUG): + logger.debug(f"Sending CDP message: {cmd_id} {cmd_event}: {request_str}") try: await self.ws.send_message(request_str) except WsConnectionClosed as wcc: raise CdpConnectionClosed(wcc.reason) from None await cmd_event.wait() response = self.inflight_result.pop(cmd_id) + if logger.isEnabledFor(logging.DEBUG): + logger.debug(f"Received CDP message: {response}") if isinstance(response, Exception): + if logger.isEnabledFor(logging.DEBUG): + logger.debug(f"Exception raised by {cmd_event} message: {type(response).__name__}") raise response return response diff --git a/py/selenium/webdriver/common/by.py b/py/selenium/webdriver/common/by.py index 56a3f96d6fbb8..65a74649f070e 100644 --- a/py/selenium/webdriver/common/by.py +++ b/py/selenium/webdriver/common/by.py @@ -16,7 +16,9 @@ # under the License. """The By implementation.""" +from typing import Dict from typing import Literal +from typing import Optional class By: @@ -31,5 +33,19 @@ class By: CLASS_NAME = "class name" CSS_SELECTOR = "css selector" + _custom_finders: Dict[str, str] = {} + + @classmethod + def register_custom_finder(cls, name: str, strategy: str) -> None: + cls._custom_finders[name] = strategy + + @classmethod + def get_finder(cls, name: str) -> Optional[str]: + return cls._custom_finders.get(name) or getattr(cls, name.upper(), None) + + @classmethod + def clear_custom_finders(cls) -> None: + cls._custom_finders.clear() + ByType = Literal["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"] diff --git a/py/selenium/webdriver/common/fedcm/account.py b/py/selenium/webdriver/common/fedcm/account.py new file mode 100644 index 0000000000000..6b8c20b12c781 --- /dev/null +++ b/py/selenium/webdriver/common/fedcm/account.py @@ -0,0 +1,71 @@ +# 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. + +from enum import Enum +from typing import Optional + + +class LoginState(Enum): + SIGN_IN = "SignIn" + SIGN_UP = "SignUp" + + +class Account: + """Represents an account displayed in a FedCM account list. + + See: https://w3c-fedid.github.io/FedCM/#dictdef-identityprovideraccount + https://w3c-fedid.github.io/FedCM/#webdriver-accountlist + """ + + def __init__(self, account_data): + self._account_data = account_data + + @property + def account_id(self) -> Optional[str]: + return self._account_data.get("accountId") + + @property + def email(self) -> Optional[str]: + return self._account_data.get("email") + + @property + def name(self) -> Optional[str]: + return self._account_data.get("name") + + @property + def given_name(self) -> Optional[str]: + return self._account_data.get("givenName") + + @property + def picture_url(self) -> Optional[str]: + return self._account_data.get("pictureUrl") + + @property + def idp_config_url(self) -> Optional[str]: + return self._account_data.get("idpConfigUrl") + + @property + def terms_of_service_url(self) -> Optional[str]: + return self._account_data.get("termsOfServiceUrl") + + @property + def privacy_policy_url(self) -> Optional[str]: + return self._account_data.get("privacyPolicyUrl") + + @property + def login_state(self) -> Optional[str]: + return self._account_data.get("loginState") diff --git a/py/selenium/webdriver/common/fedcm/dialog.py b/py/selenium/webdriver/common/fedcm/dialog.py new file mode 100644 index 0000000000000..fce069d00d767 --- /dev/null +++ b/py/selenium/webdriver/common/fedcm/dialog.py @@ -0,0 +1,64 @@ +# 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. + +from typing import List +from typing import Optional + +from .account import Account + + +class Dialog: + """Represents a FedCM dialog that can be interacted with.""" + + DIALOG_TYPE_ACCOUNT_LIST = "AccountChooser" + DIALOG_TYPE_AUTO_REAUTH = "AutoReauthn" + + def __init__(self, driver) -> None: + self._driver = driver + + @property + def type(self) -> Optional[str]: + """Gets the type of the dialog currently being shown.""" + return self._driver.fedcm.dialog_type + + @property + def title(self) -> str: + """Gets the title of the dialog.""" + return self._driver.fedcm.title + + @property + def subtitle(self) -> Optional[str]: + """Gets the subtitle of the dialog.""" + result = self._driver.fedcm.subtitle + return result.get("subtitle") if result else None + + def get_accounts(self) -> List[Account]: + """Gets the list of accounts shown in the dialog.""" + accounts = self._driver.fedcm.account_list + return [Account(account) for account in accounts] + + def select_account(self, index: int) -> None: + """Selects an account from the dialog by index.""" + self._driver.fedcm.select_account(index) + + def accept(self) -> None: + """Clicks the continue button in the dialog.""" + self._driver.fedcm.accept() + + def dismiss(self) -> None: + """Cancels/dismisses the dialog.""" + self._driver.fedcm.dismiss() diff --git a/py/selenium/webdriver/common/options.py b/py/selenium/webdriver/common/options.py index e938191c79adb..b31fcc348ce18 100644 --- a/py/selenium/webdriver/common/options.py +++ b/py/selenium/webdriver/common/options.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. import typing +import warnings from abc import ABCMeta from abc import abstractmethod from enum import Enum @@ -490,6 +491,8 @@ def ignore_local_proxy_environment_variables(self) -> None: class ArgOptions(BaseOptions): BINARY_LOCATION_ERROR = "Binary Location Must be a String" + # FedCM capability key + FEDCM_CAPABILITY = "fedcm:accounts" def __init__(self) -> None: super().__init__() @@ -514,6 +517,15 @@ def add_argument(self, argument) -> None: def ignore_local_proxy_environment_variables(self) -> None: """By calling this you will ignore HTTP_PROXY and HTTPS_PROXY from being picked up and used.""" + warnings.warn( + "using ignore_local_proxy_environment_variables in Options has been deprecated, " + "instead, create a Proxy instance with ProxyType.DIRECT to ignore proxy settings, " + "pass the proxy instance into a ClientConfig constructor, " + "pass the client config instance into the Webdriver constructor", + DeprecationWarning, + stacklevel=2, + ) + super().ignore_local_proxy_environment_variables() def to_capabilities(self): diff --git a/py/selenium/webdriver/common/virtual_authenticator.py b/py/selenium/webdriver/common/virtual_authenticator.py index 290a808d6ae3b..cfc467307f923 100644 --- a/py/selenium/webdriver/common/virtual_authenticator.py +++ b/py/selenium/webdriver/common/virtual_authenticator.py @@ -91,11 +91,11 @@ def __init__( :Args: - credential_id (bytes): Unique base64 encoded string. - is_resident_credential (bool): Whether the credential is client-side discoverable. - rp_id (str): Relying party identifier. - user_handle (bytes): userHandle associated to the credential. Must be Base64 encoded string. Can be None. - private_key (bytes): Base64 encoded PKCS#8 private key. - sign_count (int): intital value for a signature counter. + - is_resident_credential (bool): Whether the credential is client-side discoverable. + - rp_id (str): Relying party identifier. + - user_handle (bytes): userHandle associated to the credential. Must be Base64 encoded string. Can be None. + - private_key (bytes): Base64 encoded PKCS#8 private key. + - sign_count (int): intital value for a signature counter. """ self._id = credential_id self._is_resident_credential = is_resident_credential diff --git a/py/selenium/webdriver/edge/remote_connection.py b/py/selenium/webdriver/edge/remote_connection.py index 5e4a3739ba0e8..8f74c9f52c5af 100644 --- a/py/selenium/webdriver/edge/remote_connection.py +++ b/py/selenium/webdriver/edge/remote_connection.py @@ -14,10 +14,12 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -import typing + +from typing import Optional from selenium.webdriver import DesiredCapabilities from selenium.webdriver.chromium.remote_connection import ChromiumRemoteConnection +from selenium.webdriver.remote.client_config import ClientConfig class EdgeRemoteConnection(ChromiumRemoteConnection): @@ -27,7 +29,8 @@ def __init__( self, remote_server_addr: str, keep_alive: bool = True, - ignore_proxy: typing.Optional[bool] = False, + ignore_proxy: Optional[bool] = False, + client_config: Optional[ClientConfig] = None, ) -> None: super().__init__( remote_server_addr=remote_server_addr, @@ -35,4 +38,5 @@ def __init__( browser_name=EdgeRemoteConnection.browser_name, keep_alive=keep_alive, ignore_proxy=ignore_proxy, + client_config=client_config, ) diff --git a/py/selenium/webdriver/firefox/remote_connection.py b/py/selenium/webdriver/firefox/remote_connection.py index 1147a6a6aaadd..a749cce37dc62 100644 --- a/py/selenium/webdriver/firefox/remote_connection.py +++ b/py/selenium/webdriver/firefox/remote_connection.py @@ -15,15 +15,30 @@ # specific language governing permissions and limitations # under the License. +from typing import Optional + from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.remote.client_config import ClientConfig from selenium.webdriver.remote.remote_connection import RemoteConnection class FirefoxRemoteConnection(RemoteConnection): browser_name = DesiredCapabilities.FIREFOX["browserName"] - def __init__(self, remote_server_addr, keep_alive=True, ignore_proxy=False) -> None: - super().__init__(remote_server_addr, keep_alive, ignore_proxy) + def __init__( + self, + remote_server_addr: str, + keep_alive: bool = True, + ignore_proxy: Optional[bool] = False, + client_config: Optional[ClientConfig] = None, + ) -> None: + client_config = client_config or ClientConfig( + remote_server_addr=remote_server_addr, keep_alive=keep_alive, timeout=120 + ) + super().__init__( + ignore_proxy=ignore_proxy, + client_config=client_config, + ) self._commands["GET_CONTEXT"] = ("GET", "/session/$sessionId/moz/context") self._commands["SET_CONTEXT"] = ("POST", "/session/$sessionId/moz/context") diff --git a/py/selenium/webdriver/ie/webdriver.py b/py/selenium/webdriver/ie/webdriver.py index 11c137e509fe7..4df2c989a5813 100644 --- a/py/selenium/webdriver/ie/webdriver.py +++ b/py/selenium/webdriver/ie/webdriver.py @@ -16,6 +16,7 @@ # under the License. from selenium.webdriver.common.driver_finder import DriverFinder +from selenium.webdriver.remote.client_config import ClientConfig from selenium.webdriver.remote.remote_connection import RemoteConnection from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver @@ -49,10 +50,10 @@ def __init__( self.service.path = self.service.env_path() or DriverFinder(self.service, options).get_driver_path() self.service.start() + client_config = ClientConfig(remote_server_addr=self.service.service_url, keep_alive=keep_alive, timeout=120) executor = RemoteConnection( - remote_server_addr=self.service.service_url, - keep_alive=keep_alive, ignore_proxy=options._ignore_local_proxy, + client_config=client_config, ) try: diff --git a/py/selenium/webdriver/remote/client_config.py b/py/selenium/webdriver/remote/client_config.py new file mode 100644 index 0000000000000..f571d0886df54 --- /dev/null +++ b/py/selenium/webdriver/remote/client_config.py @@ -0,0 +1,291 @@ +# 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. +import base64 +import os +import socket +from enum import Enum +from typing import Optional +from urllib import parse + +import certifi + +from selenium.webdriver.common.proxy import Proxy +from selenium.webdriver.common.proxy import ProxyType + + +class AuthType(Enum): + BASIC = "Basic" + BEARER = "Bearer" + X_API_KEY = "X-API-Key" + + +class ClientConfig: + def __init__( + self, + remote_server_addr: str, + keep_alive: Optional[bool] = True, + proxy: Optional[Proxy] = Proxy(raw={"proxyType": ProxyType.SYSTEM}), + ignore_certificates: Optional[bool] = False, + init_args_for_pool_manager: Optional[dict] = None, + timeout: Optional[int] = None, + ca_certs: Optional[str] = None, + username: Optional[str] = None, + password: Optional[str] = None, + auth_type: Optional[AuthType] = AuthType.BASIC, + token: Optional[str] = None, + user_agent: Optional[str] = None, + extra_headers: Optional[dict] = None, + ) -> None: + self.remote_server_addr = remote_server_addr + self.keep_alive = keep_alive + self.proxy = proxy + self.ignore_certificates = ignore_certificates + self.init_args_for_pool_manager = init_args_for_pool_manager or {} + self.timeout = timeout + self.username = username + self.password = password + self.auth_type = auth_type + self.token = token + self.user_agent = user_agent + self.extra_headers = extra_headers + + self.timeout = ( + ( + float(os.getenv("GLOBAL_DEFAULT_TIMEOUT", str(socket.getdefaulttimeout()))) + if os.getenv("GLOBAL_DEFAULT_TIMEOUT") is not None + else socket.getdefaulttimeout() + ) + if timeout is None + else timeout + ) + + self.ca_certs = ( + (os.getenv("REQUESTS_CA_BUNDLE") if "REQUESTS_CA_BUNDLE" in os.environ else certifi.where()) + if ca_certs is None + else ca_certs + ) + + @property + def remote_server_addr(self) -> str: + """:Returns: The address of the remote server.""" + return self._remote_server_addr + + @remote_server_addr.setter + def remote_server_addr(self, value: str) -> None: + """Provides the address of the remote server.""" + self._remote_server_addr = value + + @property + def keep_alive(self) -> bool: + """:Returns: The keep alive value.""" + return self._keep_alive + + @keep_alive.setter + def keep_alive(self, value: bool) -> None: + """Toggles the keep alive value. + + :Args: + - value: whether to keep the http connection alive + """ + self._keep_alive = value + + @property + def proxy(self) -> Proxy: + """:Returns: The proxy used for communicating to the driver/server.""" + return self._proxy + + @proxy.setter + def proxy(self, proxy: Proxy) -> None: + """Provides the information for communicating with the driver or + server. + For example: Proxy(raw={"proxyType": ProxyType.SYSTEM}) + + :Args: + - value: the proxy information to use to communicate with the driver or server + """ + self._proxy = proxy + + @property + def ignore_certificates(self) -> bool: + """:Returns: The ignore certificate check value.""" + return self._ignore_certificates + + @ignore_certificates.setter + def ignore_certificates(self, ignore_certificates: bool) -> None: + """Toggles the ignore certificate check. + + :Args: + - value: value of ignore certificate check + """ + self._ignore_certificates = ignore_certificates + + @property + def init_args_for_pool_manager(self) -> dict: + """:Returns: The dictionary of arguments will be appended while + initializing the pool manager.""" + return self._init_args_for_pool_manager + + @init_args_for_pool_manager.setter + def init_args_for_pool_manager(self, init_args_for_pool_manager: dict) -> None: + """Provides dictionary of arguments will be appended while initializing the pool manager. + For example: {"init_args_for_pool_manager": {"retries": 3, "block": True}} + + :Args: + - value: the dictionary of arguments will be appended while initializing the pool manager + """ + self._init_args_for_pool_manager = init_args_for_pool_manager + + @property + def timeout(self) -> int: + """:Returns: The timeout (in seconds) used for communicating to the + driver/server.""" + return self._timeout + + @timeout.setter + def timeout(self, timeout: int) -> None: + """Provides the timeout (in seconds) for communicating with the driver + or server. + + :Args: + - value: the timeout (in seconds) to use to communicate with the driver or server + """ + self._timeout = timeout + + def reset_timeout(self) -> None: + """Resets the timeout to the default value of socket.""" + self._timeout = socket.getdefaulttimeout() + + @property + def ca_certs(self) -> str: + """:Returns: The path to bundle of CA certificates.""" + return self._ca_certs + + @ca_certs.setter + def ca_certs(self, ca_certs: str) -> None: + """Provides the path to bundle of CA certificates for establishing + secure connections. + + :Args: + - value: the path to bundle of CA certificates for establishing secure connections + """ + self._ca_certs = ca_certs + + @property + def username(self) -> str: + """Returns the username used for basic authentication to the remote + server.""" + return self._username + + @username.setter + def username(self, value: str) -> None: + """Sets the username used for basic authentication to the remote + server.""" + self._username = value + + @property + def password(self) -> str: + """Returns the password used for basic authentication to the remote + server.""" + return self._password + + @password.setter + def password(self, value: str) -> None: + """Sets the password used for basic authentication to the remote + server.""" + self._password = value + + @property + def auth_type(self) -> AuthType: + """Returns the type of authentication to the remote server.""" + return self._auth_type + + @auth_type.setter + def auth_type(self, value: AuthType) -> None: + """Sets the type of authentication to the remote server if it is not + using basic with username and password. + + :Args: value - AuthType enum value. For others, please use `extra_headers` instead + """ + self._auth_type = value + + @property + def token(self) -> str: + """Returns the token used for authentication to the remote server.""" + return self._token + + @token.setter + def token(self, value: str) -> None: + """Sets the token used for authentication to the remote server if + auth_type is not basic.""" + self._token = value + + @property + def user_agent(self) -> str: + """Returns user agent to be added to the request headers.""" + return self._user_agent + + @user_agent.setter + def user_agent(self, value: str) -> None: + """Sets user agent to be added to the request headers.""" + self._user_agent = value + + @property + def extra_headers(self) -> dict: + """Returns extra headers to be added to the request.""" + return self._extra_headers + + @extra_headers.setter + def extra_headers(self, value: dict) -> None: + """Sets extra headers to be added to the request.""" + self._extra_headers = value + + def get_proxy_url(self) -> Optional[str]: + """Returns the proxy URL to use for the connection.""" + proxy_type = self.proxy.proxy_type + remote_add = parse.urlparse(self.remote_server_addr) + if proxy_type is ProxyType.DIRECT: + return None + if proxy_type is ProxyType.SYSTEM: + _no_proxy = os.environ.get("no_proxy", os.environ.get("NO_PROXY")) + if _no_proxy: + for entry in map(str.strip, _no_proxy.split(",")): + if entry == "*": + return None + n_url = parse.urlparse(entry) + if n_url.netloc and remote_add.netloc == n_url.netloc: + return None + if n_url.path in remote_add.netloc: + return None + return os.environ.get( + "https_proxy" if self.remote_server_addr.startswith("https://") else "http_proxy", + os.environ.get("HTTPS_PROXY" if self.remote_server_addr.startswith("https://") else "HTTP_PROXY"), + ) + if proxy_type is ProxyType.MANUAL: + return self.proxy.sslProxy if self.remote_server_addr.startswith("https://") else self.proxy.http_proxy + return None + + def get_auth_header(self) -> Optional[dict]: + """Returns the authorization to add to the request headers.""" + if self.auth_type is AuthType.BASIC and self.username and self.password: + credentials = f"{self.username}:{self.password}" + encoded_credentials = base64.b64encode(credentials.encode("utf-8")).decode("utf-8") + return {"Authorization": f"{AuthType.BASIC.value} {encoded_credentials}"} + if self.auth_type is AuthType.BEARER and self.token: + return {"Authorization": f"{AuthType.BEARER.value} {self.token}"} + if self.auth_type is AuthType.X_API_KEY and self.token: + return {f"{AuthType.X_API_KEY.value}": f"{self.token}"} + return None diff --git a/py/selenium/webdriver/remote/command.py b/py/selenium/webdriver/remote/command.py index 0c104c2a46ab2..b079ed5406f53 100644 --- a/py/selenium/webdriver/remote/command.py +++ b/py/selenium/webdriver/remote/command.py @@ -122,3 +122,13 @@ class Command: GET_DOWNLOADABLE_FILES: str = "getDownloadableFiles" DOWNLOAD_FILE: str = "downloadFile" DELETE_DOWNLOADABLE_FILES: str = "deleteDownloadableFiles" + + # Federated Credential Management (FedCM) + GET_FEDCM_TITLE: str = "getFedcmTitle" + GET_FEDCM_DIALOG_TYPE: str = "getFedcmDialogType" + GET_FEDCM_ACCOUNT_LIST: str = "getFedcmAccountList" + SELECT_FEDCM_ACCOUNT: str = "selectFedcmAccount" + CLICK_FEDCM_DIALOG_BUTTON: str = "clickFedcmDialogButton" + CANCEL_FEDCM_DIALOG: str = "cancelFedcmDialog" + SET_FEDCM_DELAY: str = "setFedcmDelay" + RESET_FEDCM_COOLDOWN: str = "resetFedcmCooldown" diff --git a/py/selenium/webdriver/remote/errorhandler.py b/py/selenium/webdriver/remote/errorhandler.py index e9b30fe51dd3f..d2df3b1b8e241 100644 --- a/py/selenium/webdriver/remote/errorhandler.py +++ b/py/selenium/webdriver/remote/errorhandler.py @@ -19,6 +19,7 @@ from typing import Dict from typing import Type +from selenium.common.exceptions import DetachedShadowRootException from selenium.common.exceptions import ElementClickInterceptedException from selenium.common.exceptions import ElementNotInteractableException from selenium.common.exceptions import ElementNotSelectableException @@ -88,6 +89,7 @@ class ExceptionMapping: UNABLE_TO_CAPTURE_SCREEN = ScreenshotException ELEMENT_CLICK_INTERCEPTED = ElementClickInterceptedException UNKNOWN_METHOD = UnknownMethodException + DETACHED_SHADOW_ROOT = DetachedShadowRootException class ErrorCode: @@ -131,6 +133,7 @@ class ErrorCode: UNABLE_TO_CAPTURE_SCREEN = [63, "unable to capture screen"] ELEMENT_CLICK_INTERCEPTED = [64, "element click intercepted"] UNKNOWN_METHOD = ["unknown method exception"] + DETACHED_SHADOW_ROOT = [65, "detached shadow root"] METHOD_NOT_ALLOWED = [405, "unsupported operation"] diff --git a/py/selenium/webdriver/remote/fedcm.py b/py/selenium/webdriver/remote/fedcm.py new file mode 100644 index 0000000000000..eb2331923c2d5 --- /dev/null +++ b/py/selenium/webdriver/remote/fedcm.py @@ -0,0 +1,70 @@ +# 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. + +from typing import List +from typing import Optional + +from .command import Command + + +class FedCM: + def __init__(self, driver) -> None: + self._driver = driver + + @property + def title(self) -> str: + """Gets the title of the dialog.""" + return self._driver.execute(Command.GET_FEDCM_TITLE)["value"].get("title") + + @property + def subtitle(self) -> Optional[str]: + """Gets the subtitle of the dialog.""" + return self._driver.execute(Command.GET_FEDCM_TITLE)["value"].get("subtitle") + + @property + def dialog_type(self) -> str: + """Gets the type of the dialog currently being shown.""" + return self._driver.execute(Command.GET_FEDCM_DIALOG_TYPE).get("value") + + @property + def account_list(self) -> List[dict]: + """Gets the list of accounts shown in the dialog.""" + return self._driver.execute(Command.GET_FEDCM_ACCOUNT_LIST).get("value") + + def select_account(self, index: int) -> None: + """Selects an account from the dialog by index.""" + self._driver.execute(Command.SELECT_FEDCM_ACCOUNT, {"accountIndex": index}) + + def accept(self) -> None: + """Clicks the continue button in the dialog.""" + self._driver.execute(Command.CLICK_FEDCM_DIALOG_BUTTON, {"dialogButton": "ConfirmIdpLoginContinue"}) + + def dismiss(self) -> None: + """Cancels/dismisses the FedCM dialog.""" + self._driver.execute(Command.CANCEL_FEDCM_DIALOG) + + def enable_delay(self) -> None: + """Re-enables the promise rejection delay for FedCM.""" + self._driver.execute(Command.SET_FEDCM_DELAY, {"enabled": True}) + + def disable_delay(self) -> None: + """Disables the promise rejection delay for FedCM.""" + self._driver.execute(Command.SET_FEDCM_DELAY, {"enabled": False}) + + def reset_cooldown(self) -> None: + """Resets the FedCM dialog cooldown, allowing immediate retriggers.""" + self._driver.execute(Command.RESET_FEDCM_COOLDOWN) diff --git a/py/selenium/webdriver/remote/locator_converter.py b/py/selenium/webdriver/remote/locator_converter.py new file mode 100644 index 0000000000000..b43da73ef47cd --- /dev/null +++ b/py/selenium/webdriver/remote/locator_converter.py @@ -0,0 +1,28 @@ +# 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. + + +class LocatorConverter: + def convert(self, by, value): + # Default conversion logic + if by == "id": + return "css selector", f'[id="{value}"]' + elif by == "class name": + return "css selector", f".{value}" + elif by == "name": + return "css selector", f'[name="{value}"]' + return by, value diff --git a/py/selenium/webdriver/remote/remote_connection.py b/py/selenium/webdriver/remote/remote_connection.py index e409d2b9c104b..1bc432084b6e2 100644 --- a/py/selenium/webdriver/remote/remote_connection.py +++ b/py/selenium/webdriver/remote/remote_connection.py @@ -16,19 +16,20 @@ # under the License. import logging -import os import platform -import socket import string +import warnings from base64 import b64encode +from typing import Optional from urllib import parse +from urllib.parse import urlparse -import certifi import urllib3 from selenium import __version__ from . import utils +from .client_config import ClientConfig from .command import Command from .errorhandler import ErrorCode @@ -125,6 +126,15 @@ Command.GET_DOWNLOADABLE_FILES: ("GET", "/session/$sessionId/se/files"), Command.DOWNLOAD_FILE: ("POST", "/session/$sessionId/se/files"), Command.DELETE_DOWNLOADABLE_FILES: ("DELETE", "/session/$sessionId/se/files"), + # Federated Credential Management (FedCM) + Command.GET_FEDCM_TITLE: ("GET", "/session/$sessionId/fedcm/gettitle"), + Command.GET_FEDCM_DIALOG_TYPE: ("GET", "/session/$sessionId/fedcm/getdialogtype"), + Command.GET_FEDCM_ACCOUNT_LIST: ("GET", "/session/$sessionId/fedcm/accountlist"), + Command.CLICK_FEDCM_DIALOG_BUTTON: ("POST", "/session/$sessionId/fedcm/clickdialogbutton"), + Command.CANCEL_FEDCM_DIALOG: ("POST", "/session/$sessionId/fedcm/canceldialog"), + Command.SELECT_FEDCM_ACCOUNT: ("POST", "/session/$sessionId/fedcm/selectaccount"), + Command.SET_FEDCM_DELAY: ("POST", "/session/$sessionId/fedcm/setdelayenabled"), + Command.RESET_FEDCM_COOLDOWN: ("POST", "/session/$sessionId/fedcm/resetcooldown"), } @@ -136,12 +146,27 @@ class RemoteConnection: """ browser_name = None + # Keep backward compatibility for AppiumConnection - https://github.com/SeleniumHQ/selenium/issues/14694 + import os + import socket + + import certifi + _timeout = ( float(os.getenv("GLOBAL_DEFAULT_TIMEOUT", str(socket.getdefaulttimeout()))) if os.getenv("GLOBAL_DEFAULT_TIMEOUT") is not None else socket.getdefaulttimeout() ) _ca_certs = os.getenv("REQUESTS_CA_BUNDLE") if "REQUESTS_CA_BUNDLE" in os.environ else certifi.where() + _client_config: ClientConfig = None + + system = platform.system().lower() + if system == "darwin": + system = "mac" + + # Class variables for headers + extra_headers = None + user_agent = f"selenium/{__version__} (python {system})" @classmethod def get_timeout(cls): @@ -150,7 +175,12 @@ def get_timeout(cls): Timeout value in seconds for all http requests made to the Remote Connection """ - return None if cls._timeout == socket._GLOBAL_DEFAULT_TIMEOUT else cls._timeout + warnings.warn( + "get_timeout() in RemoteConnection is deprecated, get timeout from ClientConfig instance instead", + DeprecationWarning, + stacklevel=2, + ) + return cls._client_config.timeout @classmethod def set_timeout(cls, timeout): @@ -159,12 +189,22 @@ def set_timeout(cls, timeout): :Args: - timeout - timeout value for http requests in seconds """ - cls._timeout = timeout + warnings.warn( + "set_timeout() in RemoteConnection is deprecated, set timeout to ClientConfig instance in constructor instead", + DeprecationWarning, + stacklevel=2, + ) + cls._client_config.timeout = timeout @classmethod def reset_timeout(cls): """Reset the http request timeout to socket._GLOBAL_DEFAULT_TIMEOUT.""" - cls._timeout = socket._GLOBAL_DEFAULT_TIMEOUT + warnings.warn( + "reset_timeout() in RemoteConnection is deprecated, use reset_timeout() in ClientConfig instance instead", + DeprecationWarning, + stacklevel=2, + ) + cls._client_config.reset_timeout() @classmethod def get_certificate_bundle_path(cls): @@ -174,7 +214,12 @@ def get_certificate_bundle_path(cls): command executor. Defaults to certifi.where() or REQUESTS_CA_BUNDLE env variable if set. """ - return cls._ca_certs + warnings.warn( + "get_certificate_bundle_path() in RemoteConnection is deprecated, get ca_certs from ClientConfig instance instead", + DeprecationWarning, + stacklevel=2, + ) + return cls._client_config.ca_certs @classmethod def set_certificate_bundle_path(cls, path): @@ -185,7 +230,12 @@ def set_certificate_bundle_path(cls, path): :Args: - path - path of a .pem encoded certificate chain. """ - cls._ca_certs = path + warnings.warn( + "set_certificate_bundle_path() in RemoteConnection is deprecated, set ca_certs to ClientConfig instance in constructor instead", + DeprecationWarning, + stacklevel=2, + ) + cls._client_config.ca_certs = path @classmethod def get_remote_connection_headers(cls, parsed_url, keep_alive=False): @@ -196,49 +246,50 @@ def get_remote_connection_headers(cls, parsed_url, keep_alive=False): - keep_alive (Boolean) - Is this a keep-alive connection (default: False) """ - system = platform.system().lower() - if system == "darwin": - system = "mac" - headers = { "Accept": "application/json", "Content-Type": "application/json;charset=UTF-8", - "User-Agent": f"selenium/{__version__} (python {system})", + "User-Agent": cls.user_agent, } if parsed_url.username: + warnings.warn( + "Embedding username and password in URL could be insecure, use ClientConfig instead", stacklevel=2 + ) base64string = b64encode(f"{parsed_url.username}:{parsed_url.password}".encode()) headers.update({"Authorization": f"Basic {base64string.decode()}"}) if keep_alive: headers.update({"Connection": "keep-alive"}) - return headers + if cls.extra_headers: + headers.update(cls.extra_headers) - def _get_proxy_url(self): - if self._url.startswith("https://"): - return os.environ.get("https_proxy", os.environ.get("HTTPS_PROXY")) - if self._url.startswith("http://"): - return os.environ.get("http_proxy", os.environ.get("HTTP_PROXY")) + return headers def _identify_http_proxy_auth(self): - url = self._proxy_url - url = url[url.find(":") + 3 :] - return "@" in url and len(url[: url.find("@")]) > 0 + parsed_url = urlparse(self._proxy_url) + if parsed_url.username and parsed_url.password: + return True def _separate_http_proxy_auth(self): - url = self._proxy_url - protocol = url[: url.find(":") + 3] - no_protocol = url[len(protocol) :] - auth = no_protocol[: no_protocol.find("@")] - proxy_without_auth = protocol + no_protocol[len(auth) + 1 :] + parsed_url = urlparse(self._proxy_url) + proxy_without_auth = f"{parsed_url.scheme}://{parsed_url.hostname}:{parsed_url.port}" + auth = f"{parsed_url.username}:{parsed_url.password}" return proxy_without_auth, auth def _get_connection_manager(self): - pool_manager_init_args = {"timeout": self.get_timeout()} - if self._ca_certs: + pool_manager_init_args = {"timeout": self._client_config.timeout} + pool_manager_init_args.update( + self._client_config.init_args_for_pool_manager.get("init_args_for_pool_manager", {}) + ) + + if self._client_config.ignore_certificates: + pool_manager_init_args["cert_reqs"] = "CERT_NONE" + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + elif self._client_config.ca_certs: pool_manager_init_args["cert_reqs"] = "CERT_REQUIRED" - pool_manager_init_args["ca_certs"] = self._ca_certs + pool_manager_init_args["ca_certs"] = self._client_config.ca_certs if self._proxy_url: if self._proxy_url.lower().startswith("sock"): @@ -252,34 +303,81 @@ def _get_connection_manager(self): return urllib3.PoolManager(**pool_manager_init_args) - def __init__(self, remote_server_addr: str, keep_alive: bool = False, ignore_proxy: bool = False): - self.keep_alive = keep_alive - self._url = remote_server_addr - - # Env var NO_PROXY will override this part of the code - _no_proxy = os.environ.get("no_proxy", os.environ.get("NO_PROXY")) - if _no_proxy: - for npu in _no_proxy.split(","): - npu = npu.strip() - if npu == "*": - ignore_proxy = True - break - n_url = parse.urlparse(npu) - remote_add = parse.urlparse(self._url) - if n_url.netloc: - if remote_add.netloc == n_url.netloc: - ignore_proxy = True - break - else: - if n_url.path in remote_add.netloc: - ignore_proxy = True - break - - self._proxy_url = self._get_proxy_url() if not ignore_proxy else None - if keep_alive: + def __init__( + self, + remote_server_addr: Optional[str] = None, + keep_alive: Optional[bool] = True, + ignore_proxy: Optional[bool] = False, + ignore_certificates: Optional[bool] = False, + init_args_for_pool_manager: Optional[dict] = None, + client_config: Optional[ClientConfig] = None, + ): + self._client_config = client_config or ClientConfig( + remote_server_addr=remote_server_addr, + keep_alive=keep_alive, + ignore_certificates=ignore_certificates, + init_args_for_pool_manager=init_args_for_pool_manager, + ) + + # Keep backward compatibility for AppiumConnection - https://github.com/SeleniumHQ/selenium/issues/14694 + RemoteConnection._timeout = self._client_config.timeout + RemoteConnection._ca_certs = self._client_config.ca_certs + RemoteConnection._client_config = self._client_config + RemoteConnection.extra_headers = self._client_config.extra_headers or RemoteConnection.extra_headers + RemoteConnection.user_agent = self._client_config.user_agent or RemoteConnection.user_agent + + if remote_server_addr: + warnings.warn( + "setting remote_server_addr in RemoteConnection() is deprecated, set in ClientConfig instance instead", + DeprecationWarning, + stacklevel=2, + ) + + if not keep_alive: + warnings.warn( + "setting keep_alive in RemoteConnection() is deprecated, set in ClientConfig instance instead", + DeprecationWarning, + stacklevel=2, + ) + + if ignore_certificates: + warnings.warn( + "setting ignore_certificates in RemoteConnection() is deprecated, set in ClientConfig instance instead", + DeprecationWarning, + stacklevel=2, + ) + + if init_args_for_pool_manager: + warnings.warn( + "setting init_args_for_pool_manager in RemoteConnection() is deprecated, set in ClientConfig instance instead", + DeprecationWarning, + stacklevel=2, + ) + + if ignore_proxy: + warnings.warn( + "setting ignore_proxy in RemoteConnection() is deprecated, set in ClientConfig instance instead", + DeprecationWarning, + stacklevel=2, + ) + self._proxy_url = None + else: + self._proxy_url = self._client_config.get_proxy_url() + + if self._client_config.keep_alive: self._conn = self._get_connection_manager() self._commands = remote_commands + extra_commands = {} + + def add_command(self, name, method, url): + """Register a new command.""" + self._commands[name] = (method, url) + + def get_command(self, name: str): + """Retrieve a command if it exists.""" + return self._commands.get(name) + def execute(self, command, params): """Send a command to the remote server. @@ -291,7 +389,7 @@ def execute(self, command, params): - params - A dictionary of named parameters to send with the command as its JSON payload. """ - command_info = self._commands[command] + command_info = self._commands.get(command) or self.extra_commands.get(command) assert command_info is not None, f"Unrecognised command {command}" path_string = command_info[1] path = string.Template(path_string).substitute(params) @@ -300,12 +398,12 @@ def execute(self, command, params): for word in substitute_params: del params[word] data = utils.dump_json(params) - url = f"{self._url}{path}" + url = f"{self._client_config.remote_server_addr}{path}" trimmed = self._trim_large_entries(params) LOGGER.debug("%s %s %s", command_info[0], url, str(trimmed)) return self._request(command_info[0], url, body=data) - def _request(self, method, url, body=None, timeout=120): + def _request(self, method, url, body=None): """Send an HTTP request to the remote server. :Args: @@ -317,18 +415,22 @@ def _request(self, method, url, body=None, timeout=120): A dictionary with the server's parsed JSON response. """ parsed_url = parse.urlparse(url) - headers = self.get_remote_connection_headers(parsed_url, self.keep_alive) - response = None + headers = self.get_remote_connection_headers(parsed_url, self._client_config.keep_alive) + auth_header = self._client_config.get_auth_header() + + if auth_header: + headers.update(auth_header) + if body and method not in ("POST", "PUT"): body = None - if self.keep_alive: - response = self._conn.request(method, url, body=body, headers=headers, timeout=timeout) + if self._client_config.keep_alive: + response = self._conn.request(method, url, body=body, headers=headers, timeout=self._client_config.timeout) statuscode = response.status else: conn = self._get_connection_manager() with conn as http: - response = http.request(method, url, body=body, headers=headers, timeout=timeout) + response = http.request(method, url, body=body, headers=headers, timeout=self._client_config.timeout) statuscode = response.status data = response.data.decode("UTF-8") LOGGER.debug("Remote response: status=%s | data=%s | headers=%s", response.status, data, response.headers) diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index 41c4645bdc686..c2dc89551d6ba 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -43,6 +43,7 @@ from selenium.common.exceptions import WebDriverException from selenium.webdriver.common.bidi.script import Script from selenium.webdriver.common.by import By +from selenium.webdriver.common.options import ArgOptions from selenium.webdriver.common.options import BaseOptions from selenium.webdriver.common.print_page_options import PrintOptions from selenium.webdriver.common.timeouts import Timeouts @@ -53,11 +54,15 @@ ) from selenium.webdriver.support.relative_locator import RelativeBy +from ..common.fedcm.dialog import Dialog from .bidi_connection import BidiConnection +from .client_config import ClientConfig from .command import Command from .errorhandler import ErrorHandler +from .fedcm import FedCM from .file_detector import FileDetector from .file_detector import LocalFileDetector +from .locator_converter import LocatorConverter from .mobile import Mobile from .remote_connection import RemoteConnection from .script_key import ScriptKey @@ -95,7 +100,17 @@ def _create_caps(caps): return {"capabilities": {"firstMatch": [{}], "alwaysMatch": always_match}} -def get_remote_connection(capabilities, command_executor, keep_alive, ignore_local_proxy=False): +def get_remote_connection( + capabilities: dict, + command_executor: Union[str, RemoteConnection], + keep_alive: bool, + ignore_local_proxy: bool, + client_config: Optional[ClientConfig] = None, +) -> RemoteConnection: + if isinstance(command_executor, str): + client_config = client_config or ClientConfig(remote_server_addr=command_executor) + client_config.remote_server_addr = command_executor + command_executor = RemoteConnection(client_config=client_config) from selenium.webdriver.chrome.remote_connection import ChromeRemoteConnection from selenium.webdriver.edge.remote_connection import EdgeRemoteConnection from selenium.webdriver.firefox.remote_connection import FirefoxRemoteConnection @@ -104,7 +119,12 @@ def get_remote_connection(capabilities, command_executor, keep_alive, ignore_loc candidates = [ChromeRemoteConnection, EdgeRemoteConnection, SafariRemoteConnection, FirefoxRemoteConnection] handler = next((c for c in candidates if c.browser_name == capabilities.get("browserName")), RemoteConnection) - return handler(command_executor, keep_alive=keep_alive, ignore_proxy=ignore_local_proxy) + return handler( + remote_server_addr=command_executor, + keep_alive=keep_alive, + ignore_proxy=ignore_local_proxy, + client_config=client_config, + ) def create_matches(options: List[BaseOptions]) -> Dict: @@ -171,6 +191,9 @@ def __init__( keep_alive: bool = True, file_detector: Optional[FileDetector] = None, options: Optional[Union[BaseOptions, List[BaseOptions]]] = None, + locator_converter: Optional[LocatorConverter] = None, + web_element_cls: Optional[type] = None, + client_config: Optional[ClientConfig] = None, ) -> None: """Create a new driver that will issue commands using the wire protocol. @@ -178,11 +201,14 @@ def __init__( :Args: - command_executor - Either a string representing URL of the remote server or a custom remote_connection.RemoteConnection object. Defaults to 'http://127.0.0.1:4444/wd/hub'. - - keep_alive - Whether to configure remote_connection.RemoteConnection to use + - keep_alive - (Deprecated) Whether to configure remote_connection.RemoteConnection to use HTTP keep-alive. Defaults to True. - file_detector - Pass custom file detector object during instantiation. If None, then default LocalFileDetector() will be used. - options - instance of a driver options.Options class + - locator_converter - Custom locator converter to use. Defaults to None. + - web_element_cls - Custom class to use for web elements. Defaults to WebElement. + - client_config - Custom client configuration to use. Defaults to None. """ if isinstance(options, list): @@ -198,6 +224,7 @@ def __init__( command_executor=command_executor, keep_alive=keep_alive, ignore_local_proxy=_ignore_local_proxy, + client_config=client_config, ) self._is_remote = True self.session_id = None @@ -207,9 +234,12 @@ def __init__( self._switch_to = SwitchTo(self) self._mobile = Mobile(self) self.file_detector = file_detector or LocalFileDetector() + self.locator_converter = locator_converter or LocatorConverter() + self._web_element_cls = web_element_cls or self._web_element_cls self._authenticator_id = None self.start_client() self.start_session(capabilities) + self._fedcm = FedCM(self) self._websocket_connection = None self._script = None @@ -729,22 +759,14 @@ def find_element(self, by=By.ID, value: Optional[str] = None) -> WebElement: :rtype: WebElement """ + by, value = self.locator_converter.convert(by, value) + if isinstance(by, RelativeBy): elements = self.find_elements(by=by, value=value) if not elements: raise NoSuchElementException(f"Cannot locate relative element with: {by.root}") return elements[0] - if by == By.ID: - by = By.CSS_SELECTOR - value = f'[id="{value}"]' - elif by == By.CLASS_NAME: - by = By.CSS_SELECTOR - value = f".{value}" - elif by == By.NAME: - by = By.CSS_SELECTOR - value = f'[name="{value}"]' - return self.execute(Command.FIND_ELEMENT, {"using": by, "value": value})["value"] def find_elements(self, by=By.ID, value: Optional[str] = None) -> List[WebElement]: @@ -757,22 +779,14 @@ def find_elements(self, by=By.ID, value: Optional[str] = None) -> List[WebElemen :rtype: list of WebElement """ + by, value = self.locator_converter.convert(by, value) + if isinstance(by, RelativeBy): _pkg = ".".join(__name__.split(".")[:-1]) raw_function = pkgutil.get_data(_pkg, "findElements.js").decode("utf8") find_element_js = f"/* findElements */return ({raw_function}).apply(null, arguments);" return self.execute_script(find_element_js, by.to_dict()) - if by == By.ID: - by = By.CSS_SELECTOR - value = f'[id="{value}"]' - elif by == By.CLASS_NAME: - by = By.CSS_SELECTOR - value = f".{value}" - elif by == By.NAME: - by = By.CSS_SELECTOR - value = f'[name="{value}"]' - # Return empty list if driver returns null # See https://github.com/SeleniumHQ/selenium/issues/4555 return self.execute(Command.FIND_ELEMENTS, {"using": by, "value": value})["value"] or [] @@ -1212,3 +1226,77 @@ def delete_downloadable_files(self) -> None: raise WebDriverException("You must enable downloads in order to work with downloadable files.") self.execute(Command.DELETE_DOWNLOADABLE_FILES) + + @property + def fedcm(self) -> FedCM: + """ + :Returns: + - FedCM: an object providing access to all Federated Credential Management (FedCM) dialog commands. + + :Usage: + :: + + title = driver.fedcm.title + subtitle = driver.fedcm.subtitle + dialog_type = driver.fedcm.dialog_type + accounts = driver.fedcm.account_list + driver.fedcm.select_account(0) + driver.fedcm.accept() + driver.fedcm.dismiss() + driver.fedcm.enable_delay() + driver.fedcm.disable_delay() + driver.fedcm.reset_cooldown() + """ + return self._fedcm + + @property + def supports_fedcm(self) -> bool: + """Returns whether the browser supports FedCM capabilities.""" + return self.capabilities.get(ArgOptions.FEDCM_CAPABILITY, False) + + def _require_fedcm_support(self): + """Raises an exception if FedCM is not supported.""" + if not self.supports_fedcm: + raise WebDriverException( + "This browser does not support Federated Credential Management. " + "Please ensure you're using a supported browser." + ) + + @property + def dialog(self): + """Returns the FedCM dialog object for interaction.""" + self._require_fedcm_support() + return Dialog(self) + + def fedcm_dialog(self, timeout=5, poll_frequency=0.5, ignored_exceptions=None): + """Waits for and returns the FedCM dialog. + + Args: + timeout: How long to wait for the dialog + poll_frequency: How frequently to poll + ignored_exceptions: Exceptions to ignore while waiting + + Returns: + The FedCM dialog object if found + + Raises: + TimeoutException if dialog doesn't appear + WebDriverException if FedCM not supported + """ + from selenium.common.exceptions import NoAlertPresentException + from selenium.webdriver.support.wait import WebDriverWait + + self._require_fedcm_support() + + if ignored_exceptions is None: + ignored_exceptions = (NoAlertPresentException,) + + def _check_fedcm(): + try: + dialog = Dialog(self) + return dialog if dialog.type else None + except NoAlertPresentException: + return None + + wait = WebDriverWait(self, timeout, poll_frequency=poll_frequency, ignored_exceptions=ignored_exceptions) + return wait.until(lambda _: _check_fedcm()) diff --git a/py/selenium/webdriver/remote/webelement.py b/py/selenium/webdriver/remote/webelement.py index ef60757294caa..e5a3adad0d7b4 100644 --- a/py/selenium/webdriver/remote/webelement.py +++ b/py/selenium/webdriver/remote/webelement.py @@ -173,6 +173,13 @@ def get_attribute(self, name) -> str | None: # Check if the "active" CSS class is applied to an element. is_active = "active" in target_element.get_attribute("class") """ + + warnings.warn( + "using WebElement.get_attribute() has been deprecated. Please use get_dom_attribute() instead.", + DeprecationWarning, + stacklevel=2, + ) + if getAttribute_js is None: _load_js() attribute_value = self.parent.execute_script( @@ -404,16 +411,7 @@ def find_element(self, by=By.ID, value=None) -> WebElement: :rtype: WebElement """ - if by == By.ID: - by = By.CSS_SELECTOR - value = f'[id="{value}"]' - elif by == By.CLASS_NAME: - by = By.CSS_SELECTOR - value = f".{value}" - elif by == By.NAME: - by = By.CSS_SELECTOR - value = f'[name="{value}"]' - + by, value = self._parent.locator_converter.convert(by, value) return self._execute(Command.FIND_CHILD_ELEMENT, {"using": by, "value": value})["value"] def find_elements(self, by=By.ID, value=None) -> List[WebElement]: @@ -426,16 +424,7 @@ def find_elements(self, by=By.ID, value=None) -> List[WebElement]: :rtype: list of WebElement """ - if by == By.ID: - by = By.CSS_SELECTOR - value = f'[id="{value}"]' - elif by == By.CLASS_NAME: - by = By.CSS_SELECTOR - value = f".{value}" - elif by == By.NAME: - by = By.CSS_SELECTOR - value = f'[name="{value}"]' - + by, value = self._parent.locator_converter.convert(by, value) return self._execute(Command.FIND_CHILD_ELEMENTS, {"using": by, "value": value})["value"] def __hash__(self) -> int: diff --git a/py/selenium/webdriver/safari/remote_connection.py b/py/selenium/webdriver/safari/remote_connection.py index a97f614a98585..cd8762da4ce92 100644 --- a/py/selenium/webdriver/safari/remote_connection.py +++ b/py/selenium/webdriver/safari/remote_connection.py @@ -15,15 +15,30 @@ # specific language governing permissions and limitations # under the License. +from typing import Optional + from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.remote.client_config import ClientConfig from selenium.webdriver.remote.remote_connection import RemoteConnection class SafariRemoteConnection(RemoteConnection): browser_name = DesiredCapabilities.SAFARI["browserName"] - def __init__(self, remote_server_addr: str, keep_alive: bool = True, ignore_proxy: bool = False) -> None: - super().__init__(remote_server_addr, keep_alive, ignore_proxy) + def __init__( + self, + remote_server_addr: str, + keep_alive: bool = True, + ignore_proxy: Optional[bool] = False, + client_config: Optional[ClientConfig] = None, + ) -> None: + client_config = client_config or ClientConfig( + remote_server_addr=remote_server_addr, keep_alive=keep_alive, timeout=120 + ) + super().__init__( + ignore_proxy=ignore_proxy, + client_config=client_config, + ) self._commands["GET_PERMISSIONS"] = ("GET", "/session/$sessionId/apple/permissions") self._commands["SET_PERMISSIONS"] = ("POST", "/session/$sessionId/apple/permissions") diff --git a/py/selenium/webdriver/support/relative_locator.py b/py/selenium/webdriver/support/relative_locator.py index 3b7766a7de3de..d1ccbc257ac3f 100644 --- a/py/selenium/webdriver/support/relative_locator.py +++ b/py/selenium/webdriver/support/relative_locator.py @@ -32,11 +32,12 @@ def with_tag_name(tag_name: str) -> "RelativeBy": Note: This method may be removed in future versions, please use `locate_with` instead. + :Args: - tag_name: the DOM tag of element to start searching. :Returns: - - RelativeBy - use this object to create filters within a - `find_elements` call. + - RelativeBy: use this object to create filters within a + `find_elements` call. """ if not tag_name: raise WebDriverException("tag_name can not be null") @@ -50,8 +51,8 @@ def locate_with(by: ByType, using: str) -> "RelativeBy": - by: The value from `By` passed in. - using: search term to find the element with. :Returns: - - RelativeBy - use this object to create filters within a - `find_elements` call. + - RelativeBy: use this object to create filters within a + `find_elements` call. """ assert by is not None, "Please pass in a by argument" assert using is not None, "Please pass in a using argument" diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index 949b47238d20e..35bd74a695eb8 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -99,9 +99,9 @@ def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = except self._ignored_exceptions as exc: screen = getattr(exc, "screen", None) stacktrace = getattr(exc, "stacktrace", None) - time.sleep(self._poll) if time.monotonic() > end_time: break + time.sleep(self._poll) raise TimeoutException(message, screen, stacktrace) def until_not(self, method: Callable[[D], T], message: str = "") -> Union[T, Literal[True]]: @@ -122,7 +122,7 @@ def until_not(self, method: Callable[[D], T], message: str = "") -> Union[T, Lit return value except self._ignored_exceptions: return True - time.sleep(self._poll) if time.monotonic() > end_time: break + time.sleep(self._poll) raise TimeoutException(message) diff --git a/py/selenium/webdriver/webkitgtk/service.py b/py/selenium/webdriver/webkitgtk/service.py index 92cea26c535f3..5f42d31486143 100644 --- a/py/selenium/webdriver/webkitgtk/service.py +++ b/py/selenium/webdriver/webkitgtk/service.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. import typing +import warnings from selenium.webdriver.common import service @@ -28,7 +29,7 @@ class Service(service.Service): :param executable_path: install path of the WebKitWebDriver executable, defaults to `WebKitWebDriver`. :param port: Port for the service to run on, defaults to 0 where the operating system will decide. :param service_args: (Optional) List of args to be passed to the subprocess when launching the executable. - :param log_path: (Optional) File path for the file to be opened and passed as the subprocess stdout/stderr handler. + :param log_output: (Optional) File path for the file to be opened and passed as the subprocess stdout/stderr handler. :param env: (Optional) Mapping of environment variables for the new process, defaults to `os.environ`. """ @@ -37,16 +38,20 @@ def __init__( executable_path: str = DEFAULT_EXECUTABLE_PATH, port: int = 0, log_path: typing.Optional[str] = None, + log_output: typing.Optional[str] = None, service_args: typing.Optional[typing.List[str]] = None, env: typing.Optional[typing.Mapping[str, str]] = None, **kwargs, ) -> None: self.service_args = service_args or [] - log_file = open(log_path, "wb") if log_path else None + if log_path is not None: + warnings.warn("log_path is deprecated, use log_output instead", DeprecationWarning, stacklevel=2) + log_path = open(log_path, "wb") + log_output = open(log_output, "wb") if log_output else None super().__init__( executable_path=executable_path, port=port, - log_file=log_file, + log_output=log_path or log_output, env=env, **kwargs, ) diff --git a/py/setup.cfg b/py/setup.cfg deleted file mode 100644 index 0cda7cace9e8c..0000000000000 --- a/py/setup.cfg +++ /dev/null @@ -1,11 +0,0 @@ -[flake8] -exclude = .tox,docs/source/conf.py,*venv -# Disable this once black is applied throughout & line length is better handled. -extend-ignore = E501, E203 -# This does nothing for now as E501 is ignored. -max-line-length = 120 - -[tool:pytest] -addopts = -ra -python_files = test_*.py *_tests.py -testpaths = test diff --git a/py/setup.py b/py/setup.py index 9cdd0712f7158..cd6b84b8e2c14 100755 --- a/py/setup.py +++ b/py/setup.py @@ -28,7 +28,7 @@ setup_args = { 'cmdclass': {'install': install}, 'name': 'selenium', - 'version': "4.26.0.dev202409202351", + 'version': "4.27.0.dev202410311942", 'license': 'Apache 2.0', 'description': 'Official Python bindings for Selenium WebDriver.', 'long_description': open(join(abspath(dirname(__file__)), "README.rst")).read(), diff --git a/py/test/selenium/webdriver/common/alerts_tests.py b/py/test/selenium/webdriver/common/alerts_tests.py index ea37e0e3d5371..9479df88fadf5 100644 --- a/py/test/selenium/webdriver/common/alerts_tests.py +++ b/py/test/selenium/webdriver/common/alerts_tests.py @@ -294,6 +294,7 @@ def test_alert_should_not_allow_additional_commands_if_dismissed(driver, pages): @pytest.mark.xfail_remote(reason="https://bugzilla.mozilla.org/show_bug.cgi?id=1279211") @pytest.mark.xfail_chrome @pytest.mark.xfail_edge +@pytest.mark.xfail_safari def test_unexpected_alert_present_exception_contains_alert_text(driver, pages): pages.load("alerts.html") driver.find_element(by=By.ID, value="alert").click() diff --git a/py/test/selenium/webdriver/common/api_example_tests.py b/py/test/selenium/webdriver/common/api_example_tests.py index 7b8a4cedee3d8..40b3d2b7a2f01 100644 --- a/py/test/selenium/webdriver/common/api_example_tests.py +++ b/py/test/selenium/webdriver/common/api_example_tests.py @@ -240,6 +240,7 @@ def test_is_element_displayed(driver, pages): @pytest.mark.xfail_chrome +@pytest.mark.xfail_safari def test_move_window_position(driver, pages): pages.load("blank.html") loc = driver.get_window_position() diff --git a/py/test/selenium/webdriver/common/bidi_tests.py b/py/test/selenium/webdriver/common/bidi_tests.py index cfa34a55a6cae..6872bc211f4ba 100644 --- a/py/test/selenium/webdriver/common/bidi_tests.py +++ b/py/test/selenium/webdriver/common/bidi_tests.py @@ -18,7 +18,6 @@ from selenium.webdriver.common.by import By from selenium.webdriver.common.log import Log -from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait @@ -76,8 +75,6 @@ async def test_collect_log_mutations(driver, pages): WebDriverWait(driver, 10).until( lambda d: d.find_element(By.ID, "revealed").value_of_css_property("display") != "none" ) - WebDriverWait(driver, 5).until(EC.visibility_of(driver.find_element(By.ID, "revealed"))) - assert event["attribute_name"] == "style" assert event["current_value"] == "" assert event["old_value"] == "display:none;" diff --git a/py/test/selenium/webdriver/common/driver_element_finding_tests.py b/py/test/selenium/webdriver/common/driver_element_finding_tests.py index 2578ac2f57861..205edb92e1f88 100644 --- a/py/test/selenium/webdriver/common/driver_element_finding_tests.py +++ b/py/test/selenium/webdriver/common/driver_element_finding_tests.py @@ -715,3 +715,21 @@ def test_should_not_be_able_to_find_an_element_on_a_blank_page(driver, pages): driver.get("about:blank") with pytest.raises(NoSuchElementException): driver.find_element(By.TAG_NAME, "a") + + +# custom finders tests + + +def test_register_and_get_custom_finder(): + By.register_custom_finder("custom", "custom strategy") + assert By.get_finder("custom") == "custom strategy" + + +def test_get_nonexistent_finder(): + assert By.get_finder("nonexistent") is None + + +def test_clear_custom_finders(): + By.register_custom_finder("custom", "custom strategy") + By.clear_custom_finders() + assert By.get_finder("custom") is None diff --git a/py/test/selenium/webdriver/common/fedcm_tests.py b/py/test/selenium/webdriver/common/fedcm_tests.py new file mode 100644 index 0000000000000..868fdd991e27e --- /dev/null +++ b/py/test/selenium/webdriver/common/fedcm_tests.py @@ -0,0 +1,138 @@ +# 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. + +import pytest + +from selenium.common.exceptions import NoAlertPresentException + + +@pytest.mark.xfail_safari(reason="FedCM not supported") +@pytest.mark.xfail_firefox(reason="FedCM not supported") +@pytest.mark.xfail_ie(reason="FedCM not supported") +@pytest.mark.xfail_remote(reason="FedCM not supported, since remote uses Firefox") +class TestFedCM: + @pytest.fixture(autouse=True) + def setup(self, driver, webserver): + driver.get(webserver.where_is("fedcm/fedcm.html", localhost=True)) + self.dialog = driver.dialog + + def test_no_dialog_title(driver): + with pytest.raises(NoAlertPresentException): + driver.dialog.title + + def test_no_dialog_subtitle(driver): + with pytest.raises(NoAlertPresentException): + driver.dialog.subtitle + + def test_no_dialog_type(driver): + with pytest.raises(NoAlertPresentException): + driver.dialog.type + + def test_no_dialog_get_accounts(driver): + with pytest.raises(NoAlertPresentException): + driver.dialog.get_accounts() + + def test_no_dialog_select_account(driver): + with pytest.raises(NoAlertPresentException): + driver.dialog.select_account(1) + + def test_no_dialog_cancel(driver): + with pytest.raises(NoAlertPresentException): + driver.dialog.dismiss() + + def test_no_dialog_click_continue(driver): + with pytest.raises(NoAlertPresentException): + driver.dialog.accept() + + def test_trigger_and_verify_dialog_title(self, driver): + driver.execute_script("triggerFedCm();") + dialog = driver.fedcm_dialog() + assert dialog.title == "Sign in to localhost with localhost" + + def test_trigger_and_verify_dialog_subtitle(self, driver): + driver.execute_script("triggerFedCm();") + dialog = driver.fedcm_dialog() + assert dialog.subtitle is None + + def test_trigger_and_verify_dialog_type(self, driver): + driver.execute_script("triggerFedCm();") + dialog = driver.fedcm_dialog() + assert dialog.type == "AccountChooser" + + def test_trigger_and_verify_account_list(self, driver): + driver.execute_script("triggerFedCm();") + dialog = driver.fedcm_dialog() + accounts = dialog.get_accounts() + assert len(accounts) > 0 + assert accounts[0].name == "John Doe" + + def test_select_account(self, driver): + driver.execute_script("triggerFedCm();") + dialog = driver.fedcm_dialog() + dialog.select_account(1) + driver.fedcm_dialog() # Wait for dialog to become interactable + # dialog.click_continue() + + def test_dialog_cancel(self, driver): + driver.execute_script("triggerFedCm();") + dialog = driver.fedcm_dialog() + dialog.dismiss() + with pytest.raises(NoAlertPresentException): + dialog.title + + def test_enable_fedcm_delay(self, driver): + driver.fedcm.enable_delay() + + def test_disable_fedcm_delay(self, driver): + driver.fedcm.disable_delay() + + def test_fedcm_cooldown_reset(self, driver): + driver.fedcm.reset_cooldown() + + def test_fedcm_no_dialog_type_present(self, driver): + with pytest.raises(NoAlertPresentException): + driver.fedcm.dialog_type + + def test_fedcm_no_title_present(self, driver): + with pytest.raises(NoAlertPresentException): + driver.fedcm.title + + def test_fedcm_no_subtitle_present(self, driver): + with pytest.raises(NoAlertPresentException): + driver.fedcm.subtitle + + def test_fedcm_no_account_list_present(self, driver): + with pytest.raises(NoAlertPresentException): + driver.fedcm.account_list() + + def test_fedcm_no_select_account_present(self, driver): + with pytest.raises(NoAlertPresentException): + driver.fedcm.select_account(1) + + def test_fedcm_no_cancel_dialog_present(self, driver): + with pytest.raises(NoAlertPresentException): + driver.fedcm.dismiss() + + def test_fedcm_no_click_continue_present(self, driver): + with pytest.raises(NoAlertPresentException): + driver.fedcm.accept() + + def test_verify_dialog_type_after_cooldown_reset(self, driver): + driver.fedcm.reset_cooldown() + driver.execute_script("triggerFedCm();") + dialog = driver.fedcm_dialog() + assert dialog.type == "AccountChooser" diff --git a/py/test/selenium/webdriver/common/interactions_tests.py b/py/test/selenium/webdriver/common/interactions_tests.py index 8c2ad03fc8d6f..7ffe0e9dc4e0b 100644 --- a/py/test/selenium/webdriver/common/interactions_tests.py +++ b/py/test/selenium/webdriver/common/interactions_tests.py @@ -253,6 +253,7 @@ def test_can_pause(driver, pages): @pytest.mark.xfail_firefox @pytest.mark.xfail_remote +@pytest.mark.xfail_safari def test_can_scroll_to_element(driver, pages): pages.load("scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html") iframe = driver.find_element(By.TAG_NAME, "iframe") @@ -266,6 +267,7 @@ def test_can_scroll_to_element(driver, pages): @pytest.mark.xfail_firefox @pytest.mark.xfail_remote +@pytest.mark.xfail_safari def test_can_scroll_from_element_by_amount(driver, pages): pages.load("scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html") iframe = driver.find_element(By.TAG_NAME, "iframe") @@ -280,6 +282,7 @@ def test_can_scroll_from_element_by_amount(driver, pages): @pytest.mark.xfail_firefox @pytest.mark.xfail_remote +@pytest.mark.xfail_safari def test_can_scroll_from_element_with_offset_by_amount(driver, pages): pages.load("scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html") footer = driver.find_element(By.TAG_NAME, "footer") @@ -314,6 +317,7 @@ def test_can_scroll_from_viewport_by_amount(driver, pages): assert _in_viewport(driver, footer) +@pytest.mark.xfail_safari def test_can_scroll_from_viewport_with_offset_by_amount(driver, pages): pages.load("scrolling_tests/frame_with_nested_scrolling_frame.html") scroll_origin = ScrollOrigin.from_viewport(10, 10) diff --git a/py/test/selenium/webdriver/common/interactions_with_device_tests.py b/py/test/selenium/webdriver/common/interactions_with_device_tests.py index 926b93c641e44..f82f3967a0776 100644 --- a/py/test/selenium/webdriver/common/interactions_with_device_tests.py +++ b/py/test/selenium/webdriver/common/interactions_with_device_tests.py @@ -247,6 +247,7 @@ def test_can_pause_with_pointer(driver, pages): @pytest.mark.xfail_firefox @pytest.mark.xfail_remote +@pytest.mark.xfail_safari def test_can_scroll_to_element_with_wheel(driver, pages): pages.load("scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html") iframe = driver.find_element(By.TAG_NAME, "iframe") @@ -262,6 +263,7 @@ def test_can_scroll_to_element_with_wheel(driver, pages): @pytest.mark.xfail_firefox @pytest.mark.xfail_remote +@pytest.mark.xfail_safari def test_can_scroll_from_element_by_amount_with_wheel(driver, pages): pages.load("scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html") iframe = driver.find_element(By.TAG_NAME, "iframe") @@ -278,6 +280,7 @@ def test_can_scroll_from_element_by_amount_with_wheel(driver, pages): @pytest.mark.xfail_firefox @pytest.mark.xfail_remote +@pytest.mark.xfail_safari def test_can_scroll_from_element_with_offset_by_amount_with_wheel(driver, pages): pages.load("scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html") footer = driver.find_element(By.TAG_NAME, "footer") @@ -320,6 +323,7 @@ def test_can_scroll_from_viewport_by_amount_with_wheel(driver, pages): @pytest.mark.xfail_firefox +@pytest.mark.xfail_safari def test_can_scroll_from_viewport_with_offset_by_amount_with_wheel(driver, pages): pages.load("scrolling_tests/frame_with_nested_scrolling_frame.html") scroll_origin = ScrollOrigin.from_viewport(10, 10) diff --git a/py/test/selenium/webdriver/common/position_and_size_tests.py b/py/test/selenium/webdriver/common/position_and_size_tests.py index a438c0f5e0c88..081c51358a517 100644 --- a/py/test/selenium/webdriver/common/position_and_size_tests.py +++ b/py/test/selenium/webdriver/common/position_and_size_tests.py @@ -53,6 +53,7 @@ def test_should_get_coordinates_of_an_invisible_element(driver, pages): _check_location(element.location, x=0, y=0) +@pytest.mark.xfail_safari def test_should_scroll_page_and_get_coordinates_of_an_element_that_is_out_of_view_port(driver, pages): pages.load("coordinates_tests/page_with_element_out_of_view.html") element = driver.find_element(By.ID, "box") @@ -89,6 +90,7 @@ def test_should_get_coordinates_of_an_element_in_anested_frame(driver, pages): _check_location(element.location, x=10, y=10) +@pytest.mark.xfail_safari def test_should_get_coordinates_of_an_element_with_fixed_position(driver, pages): pages.load("coordinates_tests/page_with_fixed_element.html") element = driver.find_element(By.ID, "fixed") diff --git a/py/test/selenium/webdriver/common/selenium_manager_tests.py b/py/test/selenium/webdriver/common/selenium_manager_tests.py index 8692644cee0c4..67239bf87a67d 100644 --- a/py/test/selenium/webdriver/common/selenium_manager_tests.py +++ b/py/test/selenium/webdriver/common/selenium_manager_tests.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. import json +import platform import sys from pathlib import Path from unittest import mock @@ -59,10 +60,14 @@ def test_uses_windows(monkeypatch): def test_uses_linux(monkeypatch): monkeypatch.setattr(sys, "platform", "linux") - binary = SeleniumManager()._get_binary() - project_root = Path(selenium.__file__).parent.parent - assert binary == project_root.joinpath("selenium/webdriver/common/linux/selenium-manager") + if platform.machine() == "arm64": + with pytest.raises(WebDriverException, match="Unsupported platform/architecture combination: linux/arm64"): + SeleniumManager()._get_binary() + else: + binary = SeleniumManager()._get_binary() + project_root = Path(selenium.__file__).parent.parent + assert binary == project_root.joinpath("selenium/webdriver/common/linux/selenium-manager") def test_uses_mac(monkeypatch): diff --git a/py/test/selenium/webdriver/common/typing_tests.py b/py/test/selenium/webdriver/common/typing_tests.py index 92741533c3de1..57e254fe902c1 100644 --- a/py/test/selenium/webdriver/common/typing_tests.py +++ b/py/test/selenium/webdriver/common/typing_tests.py @@ -19,6 +19,8 @@ from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait def test_should_fire_key_press_events(driver, pages): @@ -49,6 +51,7 @@ def test_should_type_lower_case_letters(driver, pages): pages.load("javascriptPage.html") keyReporter = driver.find_element(by=By.ID, value="keyReporter") keyReporter.send_keys("abc def") + WebDriverWait(driver, 2).until(EC.text_to_be_present_in_element_value((By.ID, "keyReporter"), "abc def")) assert keyReporter.get_attribute("value") == "abc def" @@ -98,6 +101,7 @@ def test_should_be_able_to_use_arrow_keys(driver, pages): pages.load("javascriptPage.html") keyReporter = driver.find_element(by=By.ID, value="keyReporter") keyReporter.send_keys("Tet", Keys.ARROW_LEFT, "s") + WebDriverWait(driver, 2).until(EC.text_to_be_present_in_element_value((By.ID, "keyReporter"), "Test")) assert keyReporter.get_attribute("value") == "Test" @@ -212,6 +216,7 @@ def test_lower_case_alpha_keys(driver, pages): element = driver.find_element(by=By.ID, value="keyReporter") lowerAlphas = "abcdefghijklmnopqrstuvwxyz" element.send_keys(lowerAlphas) + WebDriverWait(driver, 2).until(EC.text_to_be_present_in_element_value((By.ID, "keyReporter"), lowerAlphas)) assert element.get_attribute("value") == lowerAlphas @@ -235,7 +240,7 @@ def test_all_printable_keys(driver, pages): element = driver.find_element(by=By.ID, value="keyReporter") allPrintable = "!\"#$%&'()*+,-./0123456789:<=>?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ [\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" element.send_keys(allPrintable) - + WebDriverWait(driver, 2).until(EC.text_to_be_present_in_element_value((By.ID, "keyReporter"), allPrintable)) assert element.get_attribute("value") == allPrintable assert "up: 16" in result.text.strip() @@ -282,6 +287,7 @@ def test_special_space_keys(driver, pages): pages.load("javascriptPage.html") element = driver.find_element(by=By.ID, value="keyReporter") element.send_keys("abcd" + Keys.SPACE + "fgh" + Keys.SPACE + "ij") + WebDriverWait(driver, 2).until(EC.text_to_be_present_in_element_value((By.ID, "keyReporter"), "abcd fgh ij")) assert element.get_attribute("value") == "abcd fgh ij" diff --git a/py/test/selenium/webdriver/common/upload_tests.py b/py/test/selenium/webdriver/common/upload_tests.py index f5521abd50fd8..642085790e303 100644 --- a/py/test/selenium/webdriver/common/upload_tests.py +++ b/py/test/selenium/webdriver/common/upload_tests.py @@ -16,11 +16,14 @@ # under the License. import os +import textwrap import pytest from selenium.webdriver.common.by import By from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait @pytest.fixture @@ -28,32 +31,33 @@ def get_local_path(): current_dir = os.path.dirname(os.path.realpath(__file__)) def wrapped(filename): - return os.path.join(current_dir, filename) + full_path = os.path.join(current_dir, filename) + return textwrap.fill(full_path, width=512) return wrapped +@pytest.mark.xfail_safari def test_can_upload_file(driver, pages, get_local_path): pages.load("upload.html") driver.find_element(By.ID, "upload").send_keys(get_local_path("test_file.txt")) driver.find_element(By.ID, "go").click() driver.switch_to.frame(driver.find_element(By.ID, "upload_target")) - body = driver.find_element(By.CSS_SELECTOR, "body").text - assert "test_file.txt" in body + WebDriverWait(driver, 10).until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, "body"), "test_file.txt")) +@pytest.mark.xfail_safari def test_can_upload_two_files(driver, pages, get_local_path): pages.load("upload.html") two_file_paths = get_local_path("test_file.txt") + "\n" + get_local_path("test_file2.txt") driver.find_element(By.ID, "upload").send_keys(two_file_paths) driver.find_element(By.ID, "go").click() driver.switch_to.frame(driver.find_element(By.ID, "upload_target")) - body = driver.find_element(By.CSS_SELECTOR, "body").text - assert "test_file.txt" in body - assert "test_file2.txt" in body + WebDriverWait(driver, 10).until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, "body"), "test_file.txt")) + WebDriverWait(driver, 10).until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, "body"), "test_file2.txt")) @pytest.mark.xfail_firefox diff --git a/py/test/selenium/webdriver/common/w3c_interaction_tests.py b/py/test/selenium/webdriver/common/w3c_interaction_tests.py index d0a00ac26a2d4..5376265ede3bb 100644 --- a/py/test/selenium/webdriver/common/w3c_interaction_tests.py +++ b/py/test/selenium/webdriver/common/w3c_interaction_tests.py @@ -176,6 +176,7 @@ def test_dragging_element_with_mouse_fires_events(driver, pages): @pytest.mark.xfail_firefox @pytest.mark.xfail_remote +@pytest.mark.xfail_safari def test_pen_pointer_properties(driver, pages): pages.load("pointerActionsPage.html") @@ -223,6 +224,7 @@ def test_pen_pointer_properties(driver, pages): @pytest.mark.xfail_firefox @pytest.mark.xfail_remote +@pytest.mark.xfail_safari def test_touch_pointer_properties(driver, pages): pages.load("pointerActionsPage.html") pointerArea = driver.find_element(By.CSS_SELECTOR, "#pointerArea") diff --git a/py/test/selenium/webdriver/common/webdriverwait_tests.py b/py/test/selenium/webdriver/common/webdriverwait_tests.py index 14019d185e8b0..f332933e4241c 100644 --- a/py/test/selenium/webdriver/common/webdriverwait_tests.py +++ b/py/test/selenium/webdriver/common/webdriverwait_tests.py @@ -35,6 +35,7 @@ def throw_sere(driver): @pytest.mark.xfail_chrome(reason="https://bugs.chromium.org/p/chromedriver/issues/detail?id=4743") @pytest.mark.xfail_edge(reason="https://bugs.chromium.org/p/chromedriver/issues/detail?id=4743") +@pytest.mark.xfail_safari def test_should_fail_with_invalid_selector_exception(driver, pages): pages.load("dynamic.html") with pytest.raises(InvalidSelectorException): diff --git a/py/test/selenium/webdriver/common/webserver.py b/py/test/selenium/webdriver/common/webserver.py index 5a227c1722f6c..bd108dade8278 100644 --- a/py/test/selenium/webdriver/common/webserver.py +++ b/py/test/selenium/webdriver/common/webserver.py @@ -59,27 +59,43 @@ def updir(): class HtmlOnlyHandler(BaseHTTPRequestHandler): - """Http handler.""" + """Handler for HTML responses and JSON files.""" + + def _serve_page(self, page_number): + """Serve a dynamically generated HTML page.""" + html = f"""Page{page_number} + Page number {page_number} +

top

+ """ + return html.encode("utf-8") + + def _serve_file(self, file_path): + """Serve a file from the HTML root directory.""" + with open(file_path, encoding="latin-1") as f: + return f.read().encode("utf-8") + + def _send_response(self, content_type="text/html"): + """Send a response.""" + self.send_response(200) + self.send_header("Content-type", content_type) + self.end_headers() def do_GET(self): """GET method handler.""" try: path = self.path[1:].split("?")[0] - if path[:5] == "page/": - html = """Page{page_number} - Page number {page_number} -

top - """.format( - page_number=path[5:] - ) - html = html.encode("utf-8") + file_path = os.path.join(HTML_ROOT, path) + if path.startswith("page/"): + html = self._serve_page(path[5:]) + self._send_response("text/html") + self.wfile.write(html) + elif os.path.isfile(file_path): + content_type = "application/json" if file_path.endswith(".json") else "text/html" + content = self._serve_file(file_path) + self._send_response(content_type) + self.wfile.write(content) else: - with open(os.path.join(HTML_ROOT, path), encoding="latin-1") as f: - html = f.read().encode("utf-8") - self.send_response(200) - self.send_header("Content-type", "text/html") - self.end_headers() - self.wfile.write(html) + self.send_error(404, f"File Not Found: {path}") except OSError: self.send_error(404, f"File Not Found: {path}") @@ -87,34 +103,12 @@ def do_POST(self): """POST method handler.""" try: remaining_bytes = int(self.headers["content-length"]) - contents = "" - line = self.rfile.readline() - contents += line.decode("utf-8") - remaining_bytes -= len(line) - line = self.rfile.readline() - contents += line.decode("utf-8") - remaining_bytes -= len(line) - fn = re.findall(r'Content-Disposition.*name="upload"; filename="(.*)"', line.decode("utf-8")) - if not fn: - self.send_error(500, f"File not found. {contents}") + contents = self.rfile.read(remaining_bytes).decode("utf-8") + fn_match = re.search(r'Content-Disposition.*name="upload"; filename="(.*)"', contents) + if not fn_match: + self.send_error(500, f"File not found in content. {contents}") return - line = self.rfile.readline() - remaining_bytes -= len(line) - contents += line.decode("utf-8") - line = self.rfile.readline() - remaining_bytes -= len(line) - contents += line.decode("utf-8") - preline = self.rfile.readline() - remaining_bytes -= len(preline) - while remaining_bytes > 0: - line = self.rfile.readline() - remaining_bytes -= len(line) - contents += line.decode("utf-8") - - self.send_response(200) - self.send_header("Content-type", "text/html") - self.end_headers() - + self._send_response("text/html") self.wfile.write( f""" {contents} diff --git a/py/test/selenium/webdriver/common/window_switching_tests.py b/py/test/selenium/webdriver/common/window_switching_tests.py index 1e2359cd52b36..2883ab2c55f69 100644 --- a/py/test/selenium/webdriver/common/window_switching_tests.py +++ b/py/test/selenium/webdriver/common/window_switching_tests.py @@ -130,6 +130,7 @@ def test_should_throw_no_such_window_exception_on_any_element_operation_if_awind element.text +@pytest.mark.xfail_safari def test_clicking_on_abutton_that_closes_an_open_window_does_not_cause_the_browser_to_hang(driver, pages): pages.load("xhtmlTest.html") current = driver.current_window_handle diff --git a/py/test/selenium/webdriver/edge/__init__.py b/py/test/selenium/webdriver/edge/__init__.py new file mode 100644 index 0000000000000..a5b1e6f85a09e --- /dev/null +++ b/py/test/selenium/webdriver/edge/__init__.py @@ -0,0 +1,16 @@ +# 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. diff --git a/py/test/selenium/webdriver/edge/conftest.py b/py/test/selenium/webdriver/edge/conftest.py new file mode 100644 index 0000000000000..043b7df7e146f --- /dev/null +++ b/py/test/selenium/webdriver/edge/conftest.py @@ -0,0 +1,21 @@ +# 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. + + +def pytest_generate_tests(metafunc): + if "driver" in metafunc.fixturenames and metafunc.config.option.drivers: + metafunc.parametrize("driver", metafunc.config.option.drivers, indirect=True) diff --git a/py/test/selenium/webdriver/edge/edge_launcher_tests.py b/py/test/selenium/webdriver/edge/edge_launcher_tests.py new file mode 100644 index 0000000000000..f928054a2c725 --- /dev/null +++ b/py/test/selenium/webdriver/edge/edge_launcher_tests.py @@ -0,0 +1,34 @@ +# 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. + +import pytest + + +@pytest.mark.no_driver_after_test +def test_launch_and_close_browser(clean_driver, clean_service): + driver = clean_driver(service=clean_service) + driver.quit() + + +@pytest.mark.no_driver_after_test +def test_we_can_launch_multiple_edge_instances(clean_driver, clean_service): + driver1 = clean_driver(service=clean_service) + driver2 = clean_driver(service=clean_service) + driver3 = clean_driver(service=clean_service) + driver1.quit() + driver2.quit() + driver3.quit() diff --git a/py/test/selenium/webdriver/edge/edge_service_tests.py b/py/test/selenium/webdriver/edge/edge_service_tests.py new file mode 100644 index 0000000000000..43416faf76b70 --- /dev/null +++ b/py/test/selenium/webdriver/edge/edge_service_tests.py @@ -0,0 +1,123 @@ +# 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. +import os +import subprocess +import time + +import pytest + +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.edge.service import Service + + +@pytest.mark.xfail_edge(raises=WebDriverException) +@pytest.mark.no_driver_after_test +def test_uses_edgedriver_logging(clean_driver, driver_executable) -> None: + log_file = "msedgedriver.log" + service_args = ["--append-log"] + + service = Service( + log_output=log_file, + service_args=service_args, + executable_path=driver_executable, + ) + driver2 = None + try: + driver1 = clean_driver(service=service) + with open(log_file) as fp: + lines = len(fp.readlines()) + driver2 = clean_driver(service=service) + with open(log_file) as fp: + assert len(fp.readlines()) >= 2 * lines + finally: + driver1.quit() + if driver2: + driver2.quit() + os.remove(log_file) + + +@pytest.mark.no_driver_after_test +def test_log_output_as_filename(clean_driver, driver_executable) -> None: + log_file = "msedgedriver.log" + service = Service(log_output=log_file, executable_path=driver_executable) + try: + assert "--log-path=msedgedriver.log" in service.service_args + driver = clean_driver(service=service) + with open(log_file) as fp: + assert "Starting Microsoft Edge WebDriver" in fp.readline() + finally: + driver.quit() + os.remove(log_file) + + +@pytest.mark.no_driver_after_test +def test_log_output_as_file(clean_driver, driver_executable) -> None: + log_name = "msedgedriver.log" + log_file = open(log_name, "w", encoding="utf-8") + service = Service(log_output=log_file, executable_path=driver_executable) + try: + driver = clean_driver(service=service) + time.sleep(1) + with open(log_name) as fp: + assert "Starting Microsoft Edge WebDriver" in fp.readline() + finally: + driver.quit() + log_file.close() + os.remove(log_name) + + +@pytest.mark.no_driver_after_test +def test_log_output_as_stdout(clean_driver, capfd, driver_executable) -> None: + service = Service(log_output=subprocess.STDOUT, executable_path=driver_executable) + driver = clean_driver(service=service) + + out, err = capfd.readouterr() + assert "Starting Microsoft Edge WebDriver" in out + driver.quit() + + +@pytest.mark.no_driver_after_test +def test_log_output_null_default(driver, capfd) -> None: + out, err = capfd.readouterr() + assert "Starting Microsoft Edge WebDriver" not in out + driver.quit() + + +@pytest.fixture +def service(): + return Service() + + +@pytest.mark.usefixtures("service") +class TestEdgeDriverService: + service_path = "/path/to/msedgedriver" + + @pytest.fixture(autouse=True) + def setup_and_teardown(self): + os.environ["SE_EDGEDRIVER"] = self.service_path + yield + os.environ.pop("SE_EDGEDRIVER", None) + + def test_uses_path_from_env_variable(self, service): + assert "msedgedriver" in service.path + + def test_updates_path_after_setting_env_variable(self, service): + new_path = "/foo/bar" + os.environ["SE_EDGEDRIVER"] = new_path + service.executable_path = self.service_path # Simulating the update + + assert "msedgedriver" in service.executable_path diff --git a/py/test/selenium/webdriver/marionette/mn_options_tests.py b/py/test/selenium/webdriver/marionette/mn_options_tests.py index 1e9200836aa67..cbf939df2ca05 100644 --- a/py/test/selenium/webdriver/marionette/mn_options_tests.py +++ b/py/test/selenium/webdriver/marionette/mn_options_tests.py @@ -41,20 +41,20 @@ def test_we_can_pass_options(self, driver, pages): class TestUnit: def test_ctor(self): opts = Options() - assert opts._binary is None - assert not opts._preferences + assert opts.binary_location == "" + assert opts._preferences == {"remote.active-protocols": 3} assert opts._profile is None assert not opts._arguments assert isinstance(opts.log, Log) def test_binary(self): opts = Options() - assert opts.binary is None + assert opts.binary_location == "" other_binary = FirefoxBinary() - assert other_binary != opts.binary + assert other_binary != opts.binary_location opts.binary = other_binary - assert other_binary == opts.binary + assert other_binary._start_cmd == opts.binary_location path = "/path/to/binary" opts.binary = path @@ -63,16 +63,16 @@ def test_binary(self): def test_prefs(self): opts = Options() - assert len(opts.preferences) == 0 + assert len(opts.preferences) == 1 assert isinstance(opts.preferences, dict) opts.set_preference("spam", "ham") - assert len(opts.preferences) == 1 - opts.set_preference("eggs", True) assert len(opts.preferences) == 2 + opts.set_preference("eggs", True) + assert len(opts.preferences) == 3 opts.set_preference("spam", "spam") - assert len(opts.preferences) == 2 - assert opts.preferences == {"spam": "spam", "eggs": True} + assert len(opts.preferences) == 3 + assert opts.preferences == {"eggs": True, "remote.active-protocols": 3, "spam": "spam"} def test_profile(self, tmpdir_factory): opts = Options() @@ -99,7 +99,12 @@ def test_arguments(self): def test_to_capabilities(self): opts = Options() firefox_caps = DesiredCapabilities.FIREFOX.copy() - firefox_caps.update({"pageLoadStrategy": PageLoadStrategy.normal}) + firefox_caps.update( + { + "pageLoadStrategy": PageLoadStrategy.normal, + "moz:firefoxOptions": {"prefs": {"remote.active-protocols": 3}}, + } + ) assert opts.to_capabilities() == firefox_caps profile = FirefoxProfile() diff --git a/py/test/selenium/webdriver/remote/custom_element_tests.py b/py/test/selenium/webdriver/remote/custom_element_tests.py new file mode 100644 index 0000000000000..3fccb52ad3119 --- /dev/null +++ b/py/test/selenium/webdriver/remote/custom_element_tests.py @@ -0,0 +1,50 @@ +# 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. +from selenium.webdriver.common.by import By +from selenium.webdriver.remote.webelement import WebElement + + +# Custom element class +class MyCustomElement(WebElement): + def custom_method(self): + return "Custom element method" + + +def test_find_element_with_custom_class(driver, pages): + """Test to ensure custom element class is used for a single element.""" + driver._web_element_cls = MyCustomElement + pages.load("simpleTest.html") + element = driver.find_element(By.TAG_NAME, "body") + assert isinstance(element, MyCustomElement) + assert element.custom_method() == "Custom element method" + + +def test_find_elements_with_custom_class(driver, pages): + """Test to ensure custom element class is used for multiple elements.""" + driver._web_element_cls = MyCustomElement + pages.load("simpleTest.html") + elements = driver.find_elements(By.TAG_NAME, "div") + assert all(isinstance(el, MyCustomElement) for el in elements) + assert all(el.custom_method() == "Custom element method" for el in elements) + + +def test_default_element_class(driver, pages): + """Test to ensure default WebElement class is used.""" + pages.load("simpleTest.html") + element = driver.find_element(By.TAG_NAME, "body") + assert isinstance(element, WebElement) + assert not hasattr(element, "custom_method") diff --git a/py/test/selenium/webdriver/remote/remote_custom_locator_tests.py b/py/test/selenium/webdriver/remote/remote_custom_locator_tests.py new file mode 100644 index 0000000000000..e235f2ee2e999 --- /dev/null +++ b/py/test/selenium/webdriver/remote/remote_custom_locator_tests.py @@ -0,0 +1,40 @@ +# 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. +from selenium.webdriver.remote.locator_converter import LocatorConverter + + +class CustomLocatorConverter(LocatorConverter): + def convert(self, by, value): + # Custom conversion logic + if by == "custom": + return "css selector", f'[custom-attr="{value}"]' + return super().convert(by, value) + + +def test_find_element_with_custom_locator(driver): + driver.get("data:text/html,

Test
") + element = driver.find_element("custom", "example") + assert element is not None + assert element.text == "Test" + + +def test_find_elements_with_custom_locator(driver): + driver.get("data:text/html,
Test1
Test2
") + elements = driver.find_elements("custom", "example") + assert len(elements) == 2 + assert elements[0].text == "Test1" + assert elements[1].text == "Test2" diff --git a/py/test/selenium/webdriver/safari/__init__.py b/py/test/selenium/webdriver/safari/__init__.py new file mode 100644 index 0000000000000..a5b1e6f85a09e --- /dev/null +++ b/py/test/selenium/webdriver/safari/__init__.py @@ -0,0 +1,16 @@ +# 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. diff --git a/py/test/selenium/webdriver/safari/launcher_tests.py b/py/test/selenium/webdriver/safari/launcher_tests.py index 1d08a226d2fcd..77a57c313aa2e 100644 --- a/py/test/selenium/webdriver/safari/launcher_tests.py +++ b/py/test/selenium/webdriver/safari/launcher_tests.py @@ -52,6 +52,7 @@ def test_launch(self, driver): assert driver.capabilities["browserName"] == "safari" +@pytest.mark.skip(reason="Need to be updated") def test_launch_safari_with_legacy_flag(mocker, driver_class): import subprocess diff --git a/py/test/selenium/webdriver/safari/safari_service_tests.py b/py/test/selenium/webdriver/safari/safari_service_tests.py new file mode 100644 index 0000000000000..494cbe6be6143 --- /dev/null +++ b/py/test/selenium/webdriver/safari/safari_service_tests.py @@ -0,0 +1,59 @@ +# 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. + +import os + +import pytest + +from selenium.webdriver.safari.service import Service + + +@pytest.fixture +def service(): + return Service() + + +@pytest.mark.usefixtures("service") +class TestSafariDriverService: + service_path = "/path/to/safaridriver" + + @pytest.fixture(autouse=True) + def setup_and_teardown(self): + os.environ["SE_SAFARIDRIVER"] = self.service_path + yield + os.environ.pop("SE_SAFARIDRIVER", None) + + def test_uses_path_from_env_variable(self, service): + assert "safaridriver" in service.path + + def test_updates_path_after_setting_env_variable(self, service): + new_path = "/foo/bar" + os.environ["SE_SAFARIDRIVER"] = new_path + service.executable_path = self.service_path # Simulating the update + + assert "safaridriver" in service.executable_path + + +def test_enable_logging(): + enable_logging = True + service = Service(enable_logging=enable_logging) + assert "--diagnose" in service.service_args + + +def test_service_url(): + service = Service(port=1313) + assert service.service_url == "http://localhost:1313" diff --git a/py/test/unit/selenium/webdriver/common/fedcm/account_tests.py b/py/test/unit/selenium/webdriver/common/fedcm/account_tests.py new file mode 100644 index 0000000000000..419a44afad949 --- /dev/null +++ b/py/test/unit/selenium/webdriver/common/fedcm/account_tests.py @@ -0,0 +1,44 @@ +# 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. + +from selenium.webdriver.common.fedcm.account import Account + + +def test_account_properties(): + account_data = { + "accountId": "12341234", + "email": "test@email.com", + "name": "Real Name", + "givenName": "Test Name", + "pictureUrl": "picture-url", + "idpConfigUrl": "idp-config-url", + "loginState": "login-state", + "termsOfServiceUrl": "terms-of-service-url", + "privacyPolicyUrl": "privacy-policy-url", + } + + account = Account(account_data) + + assert account.account_id == "12341234" + assert account.email == "test@email.com" + assert account.name == "Real Name" + assert account.given_name == "Test Name" + assert account.picture_url == "picture-url" + assert account.idp_config_url == "idp-config-url" + assert account.login_state == "login-state" + assert account.terms_of_service_url == "terms-of-service-url" + assert account.privacy_policy_url == "privacy-policy-url" diff --git a/py/test/unit/selenium/webdriver/common/fedcm/dialog_tests.py b/py/test/unit/selenium/webdriver/common/fedcm/dialog_tests.py new file mode 100644 index 0000000000000..30224b723a83a --- /dev/null +++ b/py/test/unit/selenium/webdriver/common/fedcm/dialog_tests.py @@ -0,0 +1,88 @@ +# 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. + +from unittest.mock import Mock +from unittest.mock import patch + +import pytest + +from selenium.webdriver.common.fedcm.dialog import Dialog + + +@pytest.fixture +def mock_driver(): + return Mock() + + +@pytest.fixture +def fedcm(mock_driver): + fedcm = Mock() + mock_driver.fedcm = fedcm + return fedcm + + +@pytest.fixture +def dialog(mock_driver, fedcm): + return Dialog(mock_driver) + + +def test_click_continue(dialog, fedcm): + dialog.accept() + fedcm.accept.assert_called_once() + + +def test_cancel(dialog, fedcm): + dialog.dismiss() + fedcm.dismiss.assert_called_once() + + +def test_select_account(dialog, fedcm): + dialog.select_account(1) + fedcm.select_account.assert_called_once_with(1) + + +def test_type(dialog, fedcm): + fedcm.dialog_type = "AccountChooser" + assert dialog.type == "AccountChooser" + + +def test_title(dialog, fedcm): + fedcm.title = "Sign in" + assert dialog.title == "Sign in" + + +def test_subtitle(dialog, fedcm): + fedcm.subtitle = {"subtitle": "Choose an account"} + assert dialog.subtitle == "Choose an account" + + +def test_get_accounts(dialog, fedcm): + accounts_data = [ + {"name": "Account1", "email": "account1@example.com"}, + {"name": "Account2", "email": "account2@example.com"}, + ] + fedcm.account_list = accounts_data + + with patch("selenium.webdriver.common.fedcm.account.Account") as MockAccount: + MockAccount.return_value = Mock() # Mock the Account instance + accounts = dialog.get_accounts() + + assert len(accounts) == 2 + assert accounts[0].name == "Account1" + assert accounts[0].email == "account1@example.com" + assert accounts[1].name == "Account2" + assert accounts[1].email == "account2@example.com" diff --git a/py/test/unit/selenium/webdriver/remote/error_handler_tests.py b/py/test/unit/selenium/webdriver/remote/error_handler_tests.py index 809d64193387a..9f3098550efc8 100644 --- a/py/test/unit/selenium/webdriver/remote/error_handler_tests.py +++ b/py/test/unit/selenium/webdriver/remote/error_handler_tests.py @@ -242,6 +242,12 @@ def test_raises_exception_for_method_not_allowed(handler, code): handler.check_response({"status": code, "value": "foo"}) +@pytest.mark.parametrize("code", ErrorCode.DETACHED_SHADOW_ROOT) +def test_raises_exception_for_detached_shadow_root(handler, code): + with pytest.raises(exceptions.DetachedShadowRootException): + handler.check_response({"status": code, "value": "foo"}) + + @pytest.mark.parametrize("key", ["stackTrace", "stacktrace"]) def test_relays_exception_stacktrace(handler, key): import json diff --git a/py/test/unit/selenium/webdriver/remote/remote_connection_tests.py b/py/test/unit/selenium/webdriver/remote/remote_connection_tests.py index 2c798365d85f5..fb7e865c68b04 100644 --- a/py/test/unit/selenium/webdriver/remote/remote_connection_tests.py +++ b/py/test/unit/selenium/webdriver/remote/remote_connection_tests.py @@ -15,15 +15,48 @@ # specific language governing permissions and limitations # under the License. +import os +from unittest.mock import patch from urllib import parse import pytest import urllib3 +from urllib3.util import Retry +from urllib3.util import Timeout from selenium import __version__ +from selenium.webdriver import Proxy +from selenium.webdriver.common.proxy import ProxyType +from selenium.webdriver.remote.client_config import AuthType +from selenium.webdriver.remote.remote_connection import ClientConfig from selenium.webdriver.remote.remote_connection import RemoteConnection +@pytest.fixture +def remote_connection(): + """Fixture to create a RemoteConnection instance.""" + return RemoteConnection("http://localhost:4444") + + +def test_add_command(remote_connection): + """Test adding a custom command to the connection.""" + remote_connection.add_command("CUSTOM_COMMAND", "PUT", "/session/$sessionId/custom") + assert remote_connection.get_command("CUSTOM_COMMAND") == ("PUT", "/session/$sessionId/custom") + + +@patch("selenium.webdriver.remote.remote_connection.RemoteConnection._request") +def test_execute_custom_command(mock_request, remote_connection): + """Test executing a custom command through the connection.""" + remote_connection.add_command("CUSTOM_COMMAND", "PUT", "/session/$sessionId/custom") + mock_request.return_value = {"status": 200, "value": "OK"} + + params = {"sessionId": "12345"} + response = remote_connection.execute("CUSTOM_COMMAND", params) + + mock_request.assert_called_once_with("PUT", "http://localhost:4444/session/12345/custom", body="{}") + assert response == {"status": 200, "value": "OK"} + + def test_get_remote_connection_headers_defaults(): url = "http://remote" headers = RemoteConnection.get_remote_connection_headers(parse.urlparse(url)) @@ -32,13 +65,18 @@ def test_get_remote_connection_headers_defaults(): assert headers.get("Accept") == "application/json" assert headers.get("Content-Type") == "application/json;charset=UTF-8" assert headers.get("User-Agent").startswith(f"selenium/{__version__} (python ") - assert headers.get("User-Agent").split(" ")[-1] in {"windows)", "mac)", "linux)"} + assert headers.get("User-Agent").split(" ")[-1] in {"windows)", "mac)", "linux)", "mac", "windows", "linux"} def test_get_remote_connection_headers_adds_auth_header_if_pass(): url = "http://user:pass@remote" - headers = RemoteConnection.get_remote_connection_headers(parse.urlparse(url)) + with pytest.warns(None) as record: + headers = RemoteConnection.get_remote_connection_headers(parse.urlparse(url)) assert headers.get("Authorization") == "Basic dXNlcjpwYXNz" + assert ( + record[0].message.args[0] + == "Embedding username and password in URL could be insecure, use ClientConfig instead" + ) def test_get_remote_connection_headers_adds_keep_alive_if_requested(): @@ -50,26 +88,109 @@ def test_get_remote_connection_headers_adds_keep_alive_if_requested(): def test_get_proxy_url_http(mock_proxy_settings): proxy = "http://http_proxy.com:8080" remote_connection = RemoteConnection("http://remote", keep_alive=False) - proxy_url = remote_connection._get_proxy_url() + proxy_url = remote_connection._client_config.get_proxy_url() assert proxy_url == proxy +def test_get_auth_header_if_client_config_pass_basic_auth(): + custom_config = ClientConfig( + remote_server_addr="http://remote", keep_alive=True, username="user", password="pass", auth_type=AuthType.BASIC + ) + remote_connection = RemoteConnection(custom_config.remote_server_addr, client_config=custom_config) + headers = remote_connection._client_config.get_auth_header() + assert headers.get("Authorization") == "Basic dXNlcjpwYXNz" + + +def test_get_auth_header_if_client_config_pass_bearer_token(): + custom_config = ClientConfig( + remote_server_addr="http://remote", keep_alive=True, auth_type=AuthType.BEARER, token="dXNlcjpwYXNz" + ) + remote_connection = RemoteConnection(custom_config.remote_server_addr, client_config=custom_config) + headers = remote_connection._client_config.get_auth_header() + assert headers.get("Authorization") == "Bearer dXNlcjpwYXNz" + + +def test_get_auth_header_if_client_config_pass_x_api_key(): + custom_config = ClientConfig( + remote_server_addr="http://remote", keep_alive=True, auth_type=AuthType.X_API_KEY, token="abcdefgh123456789" + ) + remote_connection = RemoteConnection(custom_config.remote_server_addr, client_config=custom_config) + headers = remote_connection._client_config.get_auth_header() + assert headers.get("X-API-Key") == "abcdefgh123456789" + + def test_get_proxy_url_https(mock_proxy_settings): proxy = "http://https_proxy.com:8080" remote_connection = RemoteConnection("https://remote", keep_alive=False) - proxy_url = remote_connection._get_proxy_url() + proxy_url = remote_connection._client_config.get_proxy_url() assert proxy_url == proxy +def test_get_proxy_url_https_via_client_config(): + client_config = ClientConfig( + remote_server_addr="https://localhost:4444", + proxy=Proxy({"proxyType": ProxyType.MANUAL, "sslProxy": "https://admin:admin@http_proxy.com:8080"}), + ) + remote_connection = RemoteConnection(client_config=client_config) + conn = remote_connection._get_connection_manager() + assert isinstance(conn, urllib3.ProxyManager) + conn.proxy_url = "https://http_proxy.com:8080" + conn.connection_pool_kw["proxy_headers"] = urllib3.make_headers(proxy_basic_auth="admin:admin") + + +def test_get_proxy_url_http_via_client_config(): + client_config = ClientConfig( + remote_server_addr="http://localhost:4444", + proxy=Proxy( + { + "proxyType": ProxyType.MANUAL, + "httpProxy": "http://admin:admin@http_proxy.com:8080", + "sslProxy": "https://admin:admin@http_proxy.com:8080", + } + ), + ) + remote_connection = RemoteConnection(client_config=client_config) + conn = remote_connection._get_connection_manager() + assert isinstance(conn, urllib3.ProxyManager) + conn.proxy_url = "http://http_proxy.com:8080" + conn.connection_pool_kw["proxy_headers"] = urllib3.make_headers(proxy_basic_auth="admin:admin") + + +def test_get_proxy_direct_via_client_config(): + client_config = ClientConfig( + remote_server_addr="http://localhost:4444", proxy=Proxy({"proxyType": ProxyType.DIRECT}) + ) + remote_connection = RemoteConnection(client_config=client_config) + conn = remote_connection._get_connection_manager() + assert isinstance(conn, urllib3.PoolManager) + proxy_url = remote_connection._client_config.get_proxy_url() + assert proxy_url is None + + +def test_get_proxy_system_matches_no_proxy_via_client_config(): + os.environ["HTTP_PROXY"] = "http://admin:admin@system_proxy.com:8080" + os.environ["NO_PROXY"] = "localhost,127.0.0.1" + client_config = ClientConfig( + remote_server_addr="http://localhost:4444", proxy=Proxy({"proxyType": ProxyType.SYSTEM}) + ) + remote_connection = RemoteConnection(client_config=client_config) + conn = remote_connection._get_connection_manager() + assert isinstance(conn, urllib3.PoolManager) + proxy_url = remote_connection._client_config.get_proxy_url() + assert proxy_url is None + os.environ.pop("HTTP_PROXY") + os.environ.pop("NO_PROXY") + + def test_get_proxy_url_none(mock_proxy_settings_missing): remote_connection = RemoteConnection("https://remote", keep_alive=False) - proxy_url = remote_connection._get_proxy_url() + proxy_url = remote_connection._client_config.get_proxy_url() assert proxy_url is None def test_get_proxy_url_http_auth(mock_proxy_auth_settings): remote_connection = RemoteConnection("http://remote", keep_alive=False) - proxy_url = remote_connection._get_proxy_url() + proxy_url = remote_connection._client_config.get_proxy_url() raw_proxy_url, basic_auth_string = remote_connection._separate_http_proxy_auth() assert proxy_url == "http://user:password@http_proxy.com:8080" assert raw_proxy_url == "http://http_proxy.com:8080" @@ -78,7 +199,7 @@ def test_get_proxy_url_http_auth(mock_proxy_auth_settings): def test_get_proxy_url_https_auth(mock_proxy_auth_settings): remote_connection = RemoteConnection("https://remote", keep_alive=False) - proxy_url = remote_connection._get_proxy_url() + proxy_url = remote_connection._client_config.get_proxy_url() raw_proxy_url, basic_auth_string = remote_connection._separate_http_proxy_auth() assert proxy_url == "https://user:password@https_proxy.com:8080" assert raw_proxy_url == "https://https_proxy.com:8080" @@ -91,9 +212,10 @@ def test_get_connection_manager_without_proxy(mock_proxy_settings_missing): assert isinstance(conn, urllib3.PoolManager) -def test_get_connection_manager_for_certs_and_timeout(monkeypatch): - monkeypatch.setattr(RemoteConnection, "get_timeout", lambda _: 10) # Class state; leaks into subsequent tests. +def test_get_connection_manager_for_certs_and_timeout(): remote_connection = RemoteConnection("http://remote", keep_alive=False) + remote_connection.set_timeout(10) + assert remote_connection.get_timeout() == 10 conn = remote_connection._get_connection_manager() assert conn.connection_pool_kw["timeout"] == 10 assert conn.connection_pool_kw["cert_reqs"] == "CERT_REQUIRED" @@ -239,3 +361,171 @@ def mock_no_proxy_settings(monkeypatch): monkeypatch.setenv("http_proxy", http_proxy) monkeypatch.setenv("no_proxy", "65.253.214.253,localhost,127.0.0.1,*zyz.xx,::1") monkeypatch.setenv("NO_PROXY", "65.253.214.253,localhost,127.0.0.1,*zyz.xx,::1,127.0.0.0/8") + + +@patch("selenium.webdriver.remote.remote_connection.RemoteConnection.get_remote_connection_headers") +def test_override_user_agent_in_headers(mock_get_remote_connection_headers, remote_connection): + RemoteConnection.user_agent = "custom-agent/1.0 (python 3.8)" + + mock_get_remote_connection_headers.return_value = { + "Accept": "application/json", + "Content-Type": "application/json;charset=UTF-8", + "User-Agent": "custom-agent/1.0 (python 3.8)", + } + + headers = RemoteConnection.get_remote_connection_headers(parse.urlparse("http://remote")) + + assert headers.get("User-Agent") == "custom-agent/1.0 (python 3.8)" + assert headers.get("Accept") == "application/json" + assert headers.get("Content-Type") == "application/json;charset=UTF-8" + + +@patch("selenium.webdriver.remote.remote_connection.RemoteConnection.get_remote_connection_headers") +def test_override_user_agent_via_client_config(mock_get_remote_connection_headers): + client_config = ClientConfig( + remote_server_addr="http://localhost:4444", + user_agent="custom-agent/1.0 (python 3.8)", + extra_headers={"Content-Type": "application/xml;charset=UTF-8"}, + ) + remote_connection = RemoteConnection(client_config=client_config) + + mock_get_remote_connection_headers.return_value = { + "Accept": "application/json", + "Content-Type": "application/xml;charset=UTF-8", + "User-Agent": "custom-agent/1.0 (python 3.8)", + } + + headers = remote_connection.get_remote_connection_headers(parse.urlparse("http://localhost:4444")) + + assert headers.get("User-Agent") == "custom-agent/1.0 (python 3.8)" + assert headers.get("Accept") == "application/json" + assert headers.get("Content-Type") == "application/xml;charset=UTF-8" + + +@patch("selenium.webdriver.remote.remote_connection.RemoteConnection._request") +def test_register_extra_headers(mock_request, remote_connection): + RemoteConnection.extra_headers = {"Foo": "bar"} + + mock_request.return_value = {"status": 200, "value": "OK"} + remote_connection.execute("newSession", {}) + + mock_request.assert_called_once_with("POST", "http://localhost:4444/session", body="{}") + headers = RemoteConnection.get_remote_connection_headers(parse.urlparse("http://localhost:4444"), False) + assert headers["Foo"] == "bar" + + +@patch("selenium.webdriver.remote.remote_connection.RemoteConnection._request") +def test_register_extra_headers_via_client_config(mock_request): + client_config = ClientConfig( + remote_server_addr="http://localhost:4444", + extra_headers={ + "Authorization": "AWS4-HMAC-SHA256", + "Credential": "abc/20200618/us-east-1/execute-api/aws4_request", + }, + ) + remote_connection = RemoteConnection(client_config=client_config) + + mock_request.return_value = {"status": 200, "value": "OK"} + remote_connection.execute("newSession", {}) + + mock_request.assert_called_once_with("POST", "http://localhost:4444/session", body="{}") + headers = remote_connection.get_remote_connection_headers(parse.urlparse("http://localhost:4444"), False) + assert headers["Authorization"] == "AWS4-HMAC-SHA256" + assert headers["Credential"] == "abc/20200618/us-east-1/execute-api/aws4_request" + + +def test_backwards_compatibility_with_appium_connection(): + # Keep backward compatibility for AppiumConnection - https://github.com/SeleniumHQ/selenium/issues/14694 + client_config = ClientConfig(remote_server_addr="http://remote", ca_certs="/path/to/cacert.pem", timeout=300) + remote_connection = RemoteConnection(client_config=client_config) + assert remote_connection._ca_certs == "/path/to/cacert.pem" + assert remote_connection._timeout == 300 + assert remote_connection._client_config == client_config + remote_connection.set_timeout(120) + assert remote_connection.get_timeout() == 120 + remote_connection.set_certificate_bundle_path("/path/to/cacert2.pem") + assert remote_connection.get_certificate_bundle_path() == "/path/to/cacert2.pem" + + +def test_get_connection_manager_with_timeout_from_client_config(): + remote_connection = RemoteConnection(remote_server_addr="http://remote", keep_alive=False) + remote_connection.set_timeout(10) + conn = remote_connection._get_connection_manager() + assert remote_connection.get_timeout() == 10 + assert conn.connection_pool_kw["timeout"] == 10 + assert isinstance(conn, urllib3.PoolManager) + + +def test_connection_manager_with_timeout_via_client_config(): + client_config = ClientConfig("http://remote", timeout=300) + remote_connection = RemoteConnection(client_config=client_config) + conn = remote_connection._get_connection_manager() + assert conn.connection_pool_kw["timeout"] == 300 + assert isinstance(conn, urllib3.PoolManager) + + +def test_get_connection_manager_with_ca_certs(): + remote_connection = RemoteConnection(remote_server_addr="http://remote") + remote_connection.set_certificate_bundle_path("/path/to/cacert.pem") + conn = remote_connection._get_connection_manager() + assert conn.connection_pool_kw["timeout"] is None + assert conn.connection_pool_kw["cert_reqs"] == "CERT_REQUIRED" + assert conn.connection_pool_kw["ca_certs"] == "/path/to/cacert.pem" + assert isinstance(conn, urllib3.PoolManager) + + +def test_connection_manager_with_ca_certs_via_client_config(): + client_config = ClientConfig(remote_server_addr="http://remote", ca_certs="/path/to/cacert.pem") + remote_connection = RemoteConnection(client_config=client_config) + conn = remote_connection._get_connection_manager() + assert conn.connection_pool_kw["timeout"] is None + assert conn.connection_pool_kw["cert_reqs"] == "CERT_REQUIRED" + assert conn.connection_pool_kw["ca_certs"] == "/path/to/cacert.pem" + assert isinstance(conn, urllib3.PoolManager) + + +def test_get_connection_manager_ignores_certificates(): + remote_connection = RemoteConnection(remote_server_addr="http://remote", keep_alive=False, ignore_certificates=True) + remote_connection.set_timeout(10) + conn = remote_connection._get_connection_manager() + assert conn.connection_pool_kw["timeout"] == 10 + assert conn.connection_pool_kw["cert_reqs"] == "CERT_NONE" + assert isinstance(conn, urllib3.PoolManager) + + remote_connection.reset_timeout() + assert remote_connection.get_timeout() is None + + +def test_connection_manager_ignores_certificates_via_client_config(): + client_config = ClientConfig(remote_server_addr="http://remote", ignore_certificates=True, timeout=10) + remote_connection = RemoteConnection(client_config=client_config) + conn = remote_connection._get_connection_manager() + assert isinstance(conn, urllib3.PoolManager) + assert conn.connection_pool_kw["timeout"] == 10 + assert conn.connection_pool_kw["cert_reqs"] == "CERT_NONE" + + +def test_get_connection_manager_with_custom_args(): + custom_args = {"init_args_for_pool_manager": {"retries": 3, "block": True}} + + remote_connection = RemoteConnection( + remote_server_addr="http://remote", keep_alive=False, init_args_for_pool_manager=custom_args + ) + conn = remote_connection._get_connection_manager() + assert isinstance(conn, urllib3.PoolManager) + assert conn.connection_pool_kw["retries"] == 3 + assert conn.connection_pool_kw["block"] is True + + +def test_connection_manager_with_custom_args_via_client_config(): + retries = Retry(connect=2, read=2, redirect=2) + timeout = Timeout(connect=300, read=3600) + client_config = ClientConfig( + remote_server_addr="http://localhost:4444", + init_args_for_pool_manager={"init_args_for_pool_manager": {"retries": retries, "timeout": timeout}}, + ) + remote_connection = RemoteConnection(client_config=client_config) + conn = remote_connection._get_connection_manager() + assert isinstance(conn, urllib3.PoolManager) + assert conn.connection_pool_kw["retries"] == retries + assert conn.connection_pool_kw["timeout"] == timeout diff --git a/py/tox.ini b/py/tox.ini index f454af1ee3347..3ba7d4be20ccc 100644 --- a/py/tox.ini +++ b/py/tox.ini @@ -25,17 +25,6 @@ deps = trio-typing==0.7.0 commands = mypy --install-types {posargs} - -[isort] -; isort is a common python tool for keeping imports nicely formatted. -; Automatically keep imports alphabetically sorted, on single lines in -; PEP recommended sections (https://peps.python.org/pep-0008/#imports) -; files or individual lines can be ignored via `# isort:skip|# isort:skip_file`. -profile = black -py_version=38 -force_single_line = True - - [testenv:linting-ci] ; checks linting for CI with stricter exiting when failing. skip_install = true @@ -69,3 +58,10 @@ commands = black selenium/ test/ conftest.py -l 120 flake8 selenium/ test/ --min-python-version=3.8 docformatter --in-place -r selenium/ + +[flake8] +exclude = .tox,docs/source/conf.py,*venv +# Disable this once black is applied throughout & line length is better handled. +extend-ignore = E501, E203 +# This does nothing for now as E501 is ignored. +max-line-length = 120 diff --git a/rb/.rubocop.yml b/rb/.rubocop.yml index 14142a7db62a8..0e1f819a1d856 100644 --- a/rb/.rubocop.yml +++ b/rb/.rubocop.yml @@ -4,7 +4,7 @@ require: - rubocop-rspec AllCops: - TargetRubyVersion: 3.0 + TargetRubyVersion: 3.1 NewCops: enable Exclude: - !ruby/regexp /lib\/selenium\/devtools\/v\d+/ diff --git a/rb/.ruby-version b/rb/.ruby-version index 818bd47abfc91..9cec7165ab0a0 100644 --- a/rb/.ruby-version +++ b/rb/.ruby-version @@ -1 +1 @@ -3.0.6 +3.1.6 diff --git a/rb/CHANGES b/rb/CHANGES index 2711e0ada7c2d..00357fb0c0c52 100644 --- a/rb/CHANGES +++ b/rb/CHANGES @@ -1,3 +1,10 @@ +4.26.0 (2024-10-28) +========================= +* Add CDP for Chrome 130 and remove 127 +* Add missing RBS methods (#14621) +* Update Ruby BiDi script structs to match spec +* Add RBS type support for BiDi related classes (#14611) + 4.25.0 (2024-09-19) ========================= * Add CDP for Chrome 129 and remove 126 diff --git a/rb/Gemfile.lock b/rb/Gemfile.lock index 446dbdb16d988..830962b3bc08b 100644 --- a/rb/Gemfile.lock +++ b/rb/Gemfile.lock @@ -1,9 +1,9 @@ PATH remote: . specs: - selenium-devtools (0.129.0) + selenium-devtools (0.130.0) selenium-webdriver (~> 4.2) - selenium-webdriver (4.26.0.nightly) + selenium-webdriver (4.27.0.nightly) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) @@ -13,8 +13,9 @@ PATH GEM remote: https://rubygems.org/ specs: - activesupport (7.2.1) + activesupport (7.2.2) base64 + benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) @@ -28,6 +29,7 @@ GEM public_suffix (>= 2.0.2, < 7.0) ast (2.4.2) base64 (0.2.0) + benchmark (0.4.0) bigdecimal (3.1.8) bigdecimal (3.1.8-java) concurrent-ruby (1.3.4) @@ -45,7 +47,7 @@ GEM ffi (1.17.0) ffi (1.17.0-java) ffi (1.17.0-x86_64-darwin) - fileutils (1.7.2) + fileutils (1.7.3) git (1.19.1) addressable (~> 2.8) rchardet (~> 1.8) @@ -54,12 +56,12 @@ GEM concurrent-ruby (~> 1.0) io-console (0.7.2) io-console (0.7.2-java) - irb (1.14.0) + irb (1.14.1) rdoc (>= 4.0.0) reline (>= 0.4.2) jar-dependencies (0.4.1) - json (2.7.2) - json (2.7.2-java) + json (2.8.1) + json (2.8.1-java) language_server-protocol (3.17.0.3) listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) @@ -67,45 +69,45 @@ GEM logger (1.6.1) minitest (5.25.1) parallel (1.26.3) - parser (3.3.5.0) + parser (3.3.6.0) ast (~> 2.4.1) racc - psych (5.1.2) + psych (5.2.0) stringio - psych (5.1.2-java) + psych (5.2.0-java) jar-dependencies (>= 0.1.7) public_suffix (6.0.1) racc (1.8.1) racc (1.8.1-java) - rack (2.2.9) + rack (2.2.10) rainbow (3.1.1) rake (13.2.1) rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rbs (3.5.3) + rbs (3.6.1) logger rchardet (1.8.0) rdoc (6.7.0) psych (>= 4.0.0) regexp_parser (2.9.2) - reline (0.5.10) + reline (0.5.11) io-console (~> 0.5) - rexml (3.3.7) + rexml (3.3.9) rspec (3.13.0) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.1) + rspec-core (3.13.2) rspec-support (~> 3.13.0) rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.1) + rspec-mocks (3.13.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.1) - rubocop (1.66.1) + rubocop (1.68.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -115,7 +117,7 @@ GEM rubocop-ast (>= 1.32.2, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.32.3) + rubocop-ast (1.34.1) parser (>= 3.3.1.0) rubocop-capybara (2.21.0) rubocop (~> 1.41) @@ -151,7 +153,7 @@ GEM securerandom (>= 0.1) strscan (>= 1.0.0) terminal-table (>= 2, < 4) - stringio (3.1.1) + stringio (3.1.2) strscan (3.1.0) strscan (3.1.0-java) terminal-table (3.0.2) @@ -159,11 +161,11 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.6.0) - webmock (3.23.1) + webmock (3.24.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.8.1) + webrick (1.9.0) websocket (1.2.11) yard (0.9.37) @@ -196,4 +198,4 @@ DEPENDENCIES yard (~> 0.9.11, >= 0.9.36) BUNDLED WITH - 2.5.14 + 2.5.6 diff --git a/rb/README.md b/rb/README.md index 651b47dc6828a..aacb9db114b10 100644 --- a/rb/README.md +++ b/rb/README.md @@ -1,6 +1,6 @@ # selenium-webdriver -This gem provides Ruby bindings for Selenium and supports MRI >= 3.0. +This gem provides Ruby bindings for Selenium and supports MRI >= 3.1. ## Install diff --git a/rb/lib/selenium/devtools/BUILD.bazel b/rb/lib/selenium/devtools/BUILD.bazel index 8aa9b187bdae8..c974adf9ba317 100644 --- a/rb/lib/selenium/devtools/BUILD.bazel +++ b/rb/lib/selenium/devtools/BUILD.bazel @@ -7,7 +7,7 @@ CDP_VERSIONS = [ "v85", "v128", "v129", - "v127", + "v130", ] rb_library( diff --git a/rb/lib/selenium/devtools/version.rb b/rb/lib/selenium/devtools/version.rb index be6d000d557c4..d75521bb415aa 100644 --- a/rb/lib/selenium/devtools/version.rb +++ b/rb/lib/selenium/devtools/version.rb @@ -19,6 +19,6 @@ module Selenium module DevTools - VERSION = '0.129.0' + VERSION = '0.130.0' end # DevTools end # Selenium diff --git a/rb/lib/selenium/server.rb b/rb/lib/selenium/server.rb index 7d031f26af07d..ad63ca25eb936 100644 --- a/rb/lib/selenium/server.rb +++ b/rb/lib/selenium/server.rb @@ -122,15 +122,15 @@ def available_assets end end - def net_http_start(address, &block) + def net_http_start(address, &) http_proxy = ENV.fetch('http_proxy', nil) || ENV.fetch('HTTP_PROXY', nil) if http_proxy http_proxy = "http://#{http_proxy}" unless http_proxy.start_with?('http://') uri = URI.parse(http_proxy) - Net::HTTP.start(address, nil, uri.host, uri.port, &block) + Net::HTTP.start(address, nil, uri.host, uri.port, &) else - Net::HTTP.start(address, use_ssl: true, &block) + Net::HTTP.start(address, use_ssl: true, &) end end diff --git a/rb/lib/selenium/webdriver/bidi.rb b/rb/lib/selenium/webdriver/bidi.rb index 0beb1d024578a..214091791fc45 100644 --- a/rb/lib/selenium/webdriver/bidi.rb +++ b/rb/lib/selenium/webdriver/bidi.rb @@ -25,6 +25,7 @@ class BiDi autoload :LogHandler, 'selenium/webdriver/bidi/log_handler' autoload :BrowsingContext, 'selenium/webdriver/bidi/browsing_context' autoload :Struct, 'selenium/webdriver/bidi/struct' + autoload :Network, 'selenium/webdriver/bidi/network' def initialize(url:) @ws = WebSocketConnection.new(url: url) @@ -38,8 +39,8 @@ def callbacks @ws.callbacks end - def add_callback(event, &block) - @ws.add_callback(event, &block) + def add_callback(event, &) + @ws.add_callback(event, &) end def remove_callback(event, id) diff --git a/rb/lib/selenium/webdriver/bidi/log_handler.rb b/rb/lib/selenium/webdriver/bidi/log_handler.rb index 5054a28ed416f..29b3d4dcab0d9 100644 --- a/rb/lib/selenium/webdriver/bidi/log_handler.rb +++ b/rb/lib/selenium/webdriver/bidi/log_handler.rb @@ -21,8 +21,8 @@ module Selenium module WebDriver class BiDi class LogHandler - ConsoleLogEntry = BiDi::Struct.new(:level, :text, :timestamp, :method, :args, :type) - JavaScriptLogEntry = BiDi::Struct.new(:level, :text, :timestamp, :stack_trace, :type) + ConsoleLogEntry = BiDi::Struct.new(:level, :text, :timestamp, :stack_trace, :type, :source, :method, :args) + JavaScriptLogEntry = BiDi::Struct.new(:level, :text, :timestamp, :stack_trace, :type, :source) def initialize(bidi) @bidi = bidi @@ -30,6 +30,7 @@ def initialize(bidi) end # @return [int] id of the handler + # steep:ignore:start def add_message_handler(type) subscribe_log_entry unless @log_entry_subscribed @bidi.add_callback('log.entryAdded') do |params| @@ -39,6 +40,7 @@ def add_message_handler(type) end end end + # steep:ignore:end # @param [int] id of the handler previously added def remove_message_handler(id) diff --git a/rb/lib/selenium/webdriver/bidi/log_inspector.rb b/rb/lib/selenium/webdriver/bidi/log_inspector.rb index 133666cec0f01..4c7ff02100a72 100644 --- a/rb/lib/selenium/webdriver/bidi/log_inspector.rb +++ b/rb/lib/selenium/webdriver/bidi/log_inspector.rb @@ -79,7 +79,7 @@ def on_javascript_exception(&block) end end - def on_log(filter_by = nil, &block) + def on_log(filter_by = nil, &) unless filter_by.nil? check_valid_filter(filter_by) @@ -89,14 +89,14 @@ def on_log(filter_by = nil, &block) return end - on(:entry_added, &block) + on(:entry_added, &) end private - def on(event, &block) + def on(event, &) event = EVENTS[event] if event.is_a?(Symbol) - @bidi.add_callback("log.#{event}", &block) + @bidi.add_callback("log.#{event}", &) end def check_valid_filter(filter_by) diff --git a/rb/lib/selenium/webdriver/bidi/network.rb b/rb/lib/selenium/webdriver/bidi/network.rb new file mode 100644 index 0000000000000..a6cdef3c344f6 --- /dev/null +++ b/rb/lib/selenium/webdriver/bidi/network.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +# 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. + +module Selenium + module WebDriver + class BiDi + class Network + EVENTS = { + before_request: 'network.beforeRequestSent', + response_started: 'network.responseStarted', + response_completed: 'network.responseCompleted', + auth_required: 'network.authRequired', + FETCH_ERROR: 'network.fetchError' + }.freeze + + PHASES = { + before_request: 'beforeRequestSent', + response_started: 'responseStarted', + auth_required: 'authRequired' + }.freeze + + def initialize(bidi) + @bidi = bidi + end + + def add_intercept(phases: [], contexts: nil, url_patterns: nil) + @bidi.send_cmd('network.addIntercept', phases: phases, contexts: contexts, urlPatterns: url_patterns) + end + + def remove_intercept(intercept) + @bidi.send_cmd('network.removeIntercept', intercept: intercept) + end + + def continue_with_auth(request_id, username, password) + @bidi.send_cmd( + 'network.continueWithAuth', + 'request' => request_id, + 'action' => 'provideCredentials', + 'credentials' => { + 'type' => 'password', + 'username' => username, + 'password' => password + } + ) + end + + def on(event, &) + event = EVENTS[event] if event.is_a?(Symbol) + @bidi.add_callback(event, &) + end + end # Network + end # BiDi + end # WebDriver +end # Selenium diff --git a/rb/lib/selenium/webdriver/bidi/struct.rb b/rb/lib/selenium/webdriver/bidi/struct.rb index 38d3e08e85221..1e54477818e84 100644 --- a/rb/lib/selenium/webdriver/bidi/struct.rb +++ b/rb/lib/selenium/webdriver/bidi/struct.rb @@ -37,8 +37,6 @@ def camel_to_snake(camel_str) end end end - end - - # BiDi + end # BiDi end # WebDriver end # Selenium diff --git a/rb/lib/selenium/webdriver/common.rb b/rb/lib/selenium/webdriver/common.rb index 0a778214f38d0..136a83fef0f0c 100644 --- a/rb/lib/selenium/webdriver/common.rb +++ b/rb/lib/selenium/webdriver/common.rb @@ -102,3 +102,4 @@ require 'selenium/webdriver/common/script' require 'selenium/webdriver/common/fedcm/account' require 'selenium/webdriver/common/fedcm/dialog' +require 'selenium/webdriver/common/network' diff --git a/rb/lib/selenium/webdriver/common/child_process.rb b/rb/lib/selenium/webdriver/common/child_process.rb index c63b8fa3b7c02..41fd206616da6 100644 --- a/rb/lib/selenium/webdriver/common/child_process.rb +++ b/rb/lib/selenium/webdriver/common/child_process.rb @@ -64,16 +64,10 @@ def stop(timeout = 3) return unless @pid return if exited? - WebDriver.logger.debug("Sending TERM to process: #{@pid}", id: :process) - terminate(@pid) - poll_for_exit(timeout) - - WebDriver.logger.debug(" -> stopped #{@pid}", id: :process) - rescue TimeoutError, Errno::EINVAL - WebDriver.logger.debug(" -> sending KILL to process: #{@pid}", id: :process) - kill(@pid) - wait - WebDriver.logger.debug(" -> killed #{@pid}", id: :process) + terminate_and_wait_else_kill(timeout) + rescue Errno::ECHILD, Errno::ESRCH => e + # Process exited earlier than terminate/kill could catch + WebDriver.logger.debug(" -> process: #{@pid} does not exist (#{e.class.name})", id: :process) end def alive? @@ -91,6 +85,9 @@ def exited? WebDriver.logger.debug(" -> exit code is #{exit_code.inspect}", id: :process) !!exit_code + rescue Errno::ECHILD, Errno::ESRCH + WebDriver.logger.debug(" -> process: #{@pid} already finished", id: :process) + true end def poll_for_exit(timeout) @@ -110,20 +107,29 @@ def wait private + def terminate_and_wait_else_kill(timeout) + WebDriver.logger.debug("Sending TERM to process: #{@pid}", id: :process) + terminate(@pid) + poll_for_exit(timeout) + + WebDriver.logger.debug(" -> stopped #{@pid}", id: :process) + rescue TimeoutError, Errno::EINVAL + WebDriver.logger.debug(" -> sending KILL to process: #{@pid}", id: :process) + kill(@pid) + wait + WebDriver.logger.debug(" -> killed #{@pid}", id: :process) + end + def terminate(pid) Process.kill(SIGTERM, pid) end def kill(pid) Process.kill(SIGKILL, pid) - rescue Errno::ECHILD, Errno::ESRCH - # already dead end def waitpid2(pid, flags = 0) Process.waitpid2(pid, flags) - rescue Errno::ECHILD - # already dead end end # ChildProcess end # WebDriver diff --git a/rb/lib/selenium/webdriver/common/driver.rb b/rb/lib/selenium/webdriver/common/driver.rb index 643d477ef381d..9fe9b43444832 100644 --- a/rb/lib/selenium/webdriver/common/driver.rb +++ b/rb/lib/selenium/webdriver/common/driver.rb @@ -264,6 +264,15 @@ def add_virtual_authenticator(options) bridge.add_virtual_authenticator(options) end + # + # @return [Network] + # @see Network + # + + def network + @network ||= WebDriver::Network.new(bridge) + end + #-------------------------------- sugar -------------------------------- # diff --git a/rb/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb b/rb/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb index 0cedd98ba3b52..3339426157d91 100644 --- a/rb/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +++ b/rb/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb @@ -59,9 +59,9 @@ module HasNetworkInterception # @yieldparam [Proc] continue block which proceeds with the request and optionally yields response # - def intercept(&block) + def intercept(&) @interceptor ||= DevTools::NetworkInterceptor.new(devtools) - @interceptor.intercept(&block) + @interceptor.intercept(&) end end # HasNetworkInterception end # DriverExtensions diff --git a/rb/lib/selenium/webdriver/common/error.rb b/rb/lib/selenium/webdriver/common/error.rb index deece7d2e3b0f..ed452cb7377df 100644 --- a/rb/lib/selenium/webdriver/common/error.rb +++ b/rb/lib/selenium/webdriver/common/error.rb @@ -50,9 +50,11 @@ def initialize(msg = '') super(URLS[class_name] ? "#{msg}; #{SUPPORT_MSG} #{URLS[class_name]}" : msg) end + # steep:ignore:start def class_name - self.class.name&.split('::')&.last&.to_sym + self.class.name.split('::')&.last&.to_sym end + # steep:ignore:end end # diff --git a/rb/lib/selenium/webdriver/common/fedcm/account.rb b/rb/lib/selenium/webdriver/common/fedcm/account.rb index edefcd918e5c1..29f5974e198b6 100644 --- a/rb/lib/selenium/webdriver/common/fedcm/account.rb +++ b/rb/lib/selenium/webdriver/common/fedcm/account.rb @@ -30,9 +30,7 @@ class Account attr_reader :account_id, :email, :name, :given_name, :picture_url, :idp_config_url, :login_state, :terms_of_service_url, :privacy_policy_url - # Initializes a new account with the provided attributes. - # - # @param [Hash] + # steep:ignore:start def initialize(**args) @account_id = args['accountId'] @email = args['email'] @@ -44,6 +42,7 @@ def initialize(**args) @terms_of_service_url = args['termsOfServiceUrl'] @privacy_policy_url = args['privacyPolicyUrl'] end + # steep:ignore:end end # Account end # FedCM end # WebDriver diff --git a/rb/lib/selenium/webdriver/common/logger.rb b/rb/lib/selenium/webdriver/common/logger.rb index 318d4e3110ab5..810b87a8e3c31 100644 --- a/rb/lib/selenium/webdriver/common/logger.rb +++ b/rb/lib/selenium/webdriver/common/logger.rb @@ -193,7 +193,7 @@ def create_logger(name, level:) def discard_or_log(level, message, id) id = Array(id) - return if (@ignored & id).any? + return if @ignored.intersect?(id) return if @allowed.any? && (@allowed & id).none? return if ::Logger::Severity.const_get(level.upcase) < @logger.level diff --git a/rb/lib/selenium/webdriver/common/network.rb b/rb/lib/selenium/webdriver/common/network.rb new file mode 100644 index 0000000000000..c78980b954bff --- /dev/null +++ b/rb/lib/selenium/webdriver/common/network.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +# 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. + +module Selenium + module WebDriver + class Network + attr_reader :auth_callbacks + + def initialize(bridge) + @network = BiDi::Network.new(bridge.bidi) + @auth_callbacks = {} + end + + def add_authentication_handler(username, password) + intercept = @network.add_intercept(phases: [BiDi::Network::PHASES[:auth_required]]) + auth_id = @network.on(:auth_required) do |event| + request_id = event['requestId'] + @network.continue_with_auth(request_id, username, password) + end + @auth_callbacks[auth_id] = intercept + + auth_id + end + + def remove_authentication_handler(id) + intercept = @auth_callbacks[id] + @network.remove_intercept(intercept['intercept']) + @auth_callbacks.delete(id) + end + + def clear_authentication_handlers + @auth_callbacks.each_key { |id| remove_authentication_handler(id) } + end + end # Network + end # WebDriver +end # Selenium diff --git a/rb/lib/selenium/webdriver/common/script.rb b/rb/lib/selenium/webdriver/common/script.rb index 4b58b1bbea2c7..a637b75f6bda1 100644 --- a/rb/lib/selenium/webdriver/common/script.rb +++ b/rb/lib/selenium/webdriver/common/script.rb @@ -25,13 +25,13 @@ def initialize(bridge) end # @return [int] id of the handler - def add_console_message_handler(&block) - @log_handler.add_message_handler('console', &block) + def add_console_message_handler(&) + @log_handler.add_message_handler('console', &) end # @return [int] id of the handler - def add_javascript_error_handler(&block) - @log_handler.add_message_handler('javascript', &block) + def add_javascript_error_handler(&) + @log_handler.add_message_handler('javascript', &) end # @param [int] id of the handler previously added diff --git a/rb/lib/selenium/webdriver/common/service_manager.rb b/rb/lib/selenium/webdriver/common/service_manager.rb index f3e623f7b9532..c93e2d688c03e 100644 --- a/rb/lib/selenium/webdriver/common/service_manager.rb +++ b/rb/lib/selenium/webdriver/common/service_manager.rb @@ -113,6 +113,7 @@ def stop_process def stop_server connect_to_server do |http| headers = WebDriver::Remote::Http::Common::DEFAULT_HEADERS.dup + WebDriver.logger.debug('Sending shutdown request to server', id: :driver_service) http.get('/shutdown', headers) end end diff --git a/rb/lib/selenium/webdriver/common/target_locator.rb b/rb/lib/selenium/webdriver/common/target_locator.rb index 9300d414d6d74..378fa7ea00c28 100644 --- a/rb/lib/selenium/webdriver/common/target_locator.rb +++ b/rb/lib/selenium/webdriver/common/target_locator.rb @@ -50,6 +50,7 @@ def parent_frame # @param type either :tab or :window # + # steep:ignore:start def new_window(type = :window) raise ArgumentError, "Valid types are :tab and :window, received: #{type.inspect}" unless %i[window tab].include?(type) @@ -70,6 +71,7 @@ def new_window(type = :window) window(handle) end end + # steep:ignore:end # # switch to the given window handle diff --git a/rb/lib/selenium/webdriver/remote/bridge.rb b/rb/lib/selenium/webdriver/remote/bridge.rb index a26bb4bd1fe22..1678065010108 100644 --- a/rb/lib/selenium/webdriver/remote/bridge.rb +++ b/rb/lib/selenium/webdriver/remote/bridge.rb @@ -35,10 +35,10 @@ class << self attr_reader :extra_commands attr_writer :element_class, :locator_converter - def add_command(name, verb, url, &block) + def add_command(name, verb, url, &) @extra_commands ||= {} @extra_commands[name] = [verb, url] - define_method(name, &block) + define_method(name, &) end def locator_converter diff --git a/rb/lib/selenium/webdriver/remote/http/common.rb b/rb/lib/selenium/webdriver/remote/http/common.rb index 423dc5d7ece14..2f287e2703132 100644 --- a/rb/lib/selenium/webdriver/remote/http/common.rb +++ b/rb/lib/selenium/webdriver/remote/http/common.rb @@ -48,6 +48,7 @@ def close # hook for subclasses - will be called on Driver#quit end + # steep:ignore:start def call(verb, url, command_hash) url = server_url.merge(url) unless url.is_a?(URI) headers = common_headers.dup @@ -66,6 +67,7 @@ def call(verb, url, command_hash) request verb, url, headers, payload end + # steep:ignore:end private diff --git a/rb/lib/selenium/webdriver/support/guards.rb b/rb/lib/selenium/webdriver/support/guards.rb index ad5e1f2c9efd8..f56850106396d 100644 --- a/rb/lib/selenium/webdriver/support/guards.rb +++ b/rb/lib/selenium/webdriver/support/guards.rb @@ -37,8 +37,8 @@ def initialize(example, bug_tracker: '', conditions: nil) @messages = {} end - def add_condition(name, condition = nil, &blk) - @guard_conditions << GuardCondition.new(name, condition, &blk) + def add_condition(name, condition = nil, &) + @guard_conditions << GuardCondition.new(name, condition, &) end def add_message(name, message) diff --git a/rb/lib/selenium/webdriver/version.rb b/rb/lib/selenium/webdriver/version.rb index e99cd42fa460c..3a8d951f55562 100644 --- a/rb/lib/selenium/webdriver/version.rb +++ b/rb/lib/selenium/webdriver/version.rb @@ -19,6 +19,6 @@ module Selenium module WebDriver - VERSION = '4.26.0.nightly' + VERSION = '4.27.0.nightly' end # WebDriver end # Selenium diff --git a/rb/selenium-devtools.gemspec b/rb/selenium-devtools.gemspec index 455966163f019..709efc6bcdb2c 100644 --- a/rb/selenium-devtools.gemspec +++ b/rb/selenium-devtools.gemspec @@ -27,7 +27,7 @@ Gem::Specification.new do |s| } s.required_rubygems_version = Gem::Requirement.new('> 1.3.1') if s.respond_to? :required_rubygems_version= - s.required_ruby_version = Gem::Requirement.new('>= 3.0') + s.required_ruby_version = Gem::Requirement.new('>= 3.1') s.files = [ 'LICENSE', diff --git a/rb/selenium-webdriver.gemspec b/rb/selenium-webdriver.gemspec index 64a92dcb358fc..b6c09f273c326 100644 --- a/rb/selenium-webdriver.gemspec +++ b/rb/selenium-webdriver.gemspec @@ -28,7 +28,7 @@ Gem::Specification.new do |s| } s.required_rubygems_version = Gem::Requirement.new('> 1.3.1') if s.respond_to? :required_rubygems_version= - s.required_ruby_version = Gem::Requirement.new('>= 3.0') + s.required_ruby_version = Gem::Requirement.new('>= 3.1') s.files = [ 'CHANGES', diff --git a/rb/sig/lib/selenium/webdriver/bidi.rbs b/rb/sig/lib/selenium/webdriver/bidi.rbs index 109b658bca24b..314dc70fdd5f3 100644 --- a/rb/sig/lib/selenium/webdriver/bidi.rbs +++ b/rb/sig/lib/selenium/webdriver/bidi.rbs @@ -7,13 +7,17 @@ module Selenium def initialize: (url: String) -> void + def add_callback: (String event) { () -> void } -> Integer + def close: () -> nil def callbacks: () -> Hash[untyped, untyped] + def remove_callback: (String event, Integer id) -> Error::WebDriverError? + def session: () -> Session - def send_cmd: (untyped method, **untyped params) -> untyped + def send_cmd: (String method, **untyped params) -> untyped def error_message: (Hash[String,String] message) -> String end diff --git a/rb/sig/lib/selenium/webdriver/bidi/log_handler.rbs b/rb/sig/lib/selenium/webdriver/bidi/log_handler.rbs new file mode 100644 index 0000000000000..2506df056f819 --- /dev/null +++ b/rb/sig/lib/selenium/webdriver/bidi/log_handler.rbs @@ -0,0 +1,27 @@ +module Selenium + module WebDriver + class BiDi + class LogHandler + @bidi: BiDi + + @log_entry_subscribed: bool + + ConsoleLogEntry: Struct + + JavaScriptLogEntry: Struct + + def initialize: (BiDi bidi) -> void + + def add_message_handler: (String type) ?{ (untyped) -> untyped } -> Integer + + def remove_message_handler: (Integer id) -> bool? + + private + + def subscribe_log_entry: () -> bool + + def unsubscribe_log_entry: () -> bool + end + end + end +end diff --git a/rb/sig/lib/selenium/webdriver/bidi/network.rbs b/rb/sig/lib/selenium/webdriver/bidi/network.rbs new file mode 100644 index 0000000000000..96dcdc47d89ed --- /dev/null +++ b/rb/sig/lib/selenium/webdriver/bidi/network.rbs @@ -0,0 +1,23 @@ +module Selenium + module WebDriver + class BiDi + class Network + @bidi: BiDi + + EVENTS: Hash[Symbol, String] + + PHASES: Hash[Symbol, String] + + def initialize: (BiDi bidi) -> void + + def add_intercept: (?phases: Array[String], ?contexts: BrowsingContext?, ?url_patterns: untyped?) -> Hash[String, String] + + def remove_intercept: (String intercept) -> untyped + + def continue_with_auth: (String request_id, String username, String password) -> untyped + + def on: (Symbol event) { (?) -> untyped } -> untyped + end + end + end +end diff --git a/rb/sig/lib/selenium/webdriver/bidi/struct.rbs b/rb/sig/lib/selenium/webdriver/bidi/struct.rbs new file mode 100644 index 0000000000000..ff4ab92c762e5 --- /dev/null +++ b/rb/sig/lib/selenium/webdriver/bidi/struct.rbs @@ -0,0 +1,11 @@ +module Selenium + module WebDriver + class BiDi + class Struct < ::Struct[untyped] + def self.new: (*untyped args) ?{ (self) [self: self] -> untyped } -> void + + def self.camel_to_snake: (String camel_str) -> String + end + end + end +end diff --git a/rb/sig/lib/selenium/webdriver/common/driver.rbs b/rb/sig/lib/selenium/webdriver/common/driver.rbs index 0bc94a74a8008..8a95acc35109a 100644 --- a/rb/sig/lib/selenium/webdriver/common/driver.rbs +++ b/rb/sig/lib/selenium/webdriver/common/driver.rbs @@ -17,6 +17,8 @@ module Selenium def inspect: () -> String + def network: -> Network + def status: () -> Hash[untyped, untyped] def navigate: () -> Navigation diff --git a/rb/sig/lib/selenium/webdriver/common/driver_finder.rbs b/rb/sig/lib/selenium/webdriver/common/driver_finder.rbs index d012a5ec38f3c..fc17ee073d3ee 100644 --- a/rb/sig/lib/selenium/webdriver/common/driver_finder.rbs +++ b/rb/sig/lib/selenium/webdriver/common/driver_finder.rbs @@ -1,7 +1,26 @@ module Selenium module WebDriver class DriverFinder + @options: untyped + + @paths: untyped + @service: untyped + + def initialize: (untyped options,untyped service) -> void + def self.path: (untyped options, untyped klass) -> untyped + + def browser_path: -> untyped + + def browser_path?: -> untyped + + def driver_path: -> untyped + + private + + def paths: -> untyped + + def to_args: -> Array[String] end end end diff --git a/rb/sig/lib/selenium/webdriver/common/error.rbs b/rb/sig/lib/selenium/webdriver/common/error.rbs index da8be68ee9f8c..224ee6ee29cee 100644 --- a/rb/sig/lib/selenium/webdriver/common/error.rbs +++ b/rb/sig/lib/selenium/webdriver/common/error.rbs @@ -7,7 +7,7 @@ module Selenium ERROR_URL: String - URLS: Hash[Symbol, String] + URLS: Hash[Symbol?, String] class WebDriverError < StandardError def initialize: (?String | Array[String] msg) -> void diff --git a/rb/sig/lib/selenium/webdriver/common/logger.rbs b/rb/sig/lib/selenium/webdriver/common/logger.rbs index 0b8f39a33ca76..69a167e4b9276 100644 --- a/rb/sig/lib/selenium/webdriver/common/logger.rbs +++ b/rb/sig/lib/selenium/webdriver/common/logger.rbs @@ -28,7 +28,7 @@ module Selenium def warn: (String message, ?id: Symbol | Array[Symbol] id) ?{ () -> void } -> void - def deprecate: (String old, ?String? new, ?id: Symbol | Array[Symbol] id, ?reference: String reference) { () -> void } -> (void) + def deprecate: (String old, ?String? new, ?id: Symbol | Array[Symbol] id, ?reference: String reference) ?{ () -> void } -> untyped def debug?: () -> bool @@ -36,7 +36,7 @@ module Selenium def create_logger: (String name, level: Symbol level) -> ::Logger - def discard_or_log: (Symbol level, String message, (Symbol | Array[::Symbol]) | [(Symbol | Array[Symbol])] id) ?{ () -> void } -> (void) + def discard_or_log: (Symbol level, String message, (Symbol | Array[::Symbol]) | [(Symbol | Array[Symbol])] id) ?{ () -> void } -> untyped end end end diff --git a/rb/sig/lib/selenium/webdriver/common/network.rbs b/rb/sig/lib/selenium/webdriver/common/network.rbs new file mode 100644 index 0000000000000..edd306ee3990a --- /dev/null +++ b/rb/sig/lib/selenium/webdriver/common/network.rbs @@ -0,0 +1,17 @@ +module Selenium + module WebDriver + class Network + @network: BiDi::Network + + attr_reader auth_callbacks: Hash[String, String] + + def initialize: (Remote::Bridge bridge) -> void + + def add_authentication_handler: (String username, String password) -> String + + def clear_authentication_handlers: -> Hash[nil, nil] + + def remove_authentication_handler: (String id) -> nil + end + end +end diff --git a/rb/sig/lib/selenium/webdriver/common/options.rbs b/rb/sig/lib/selenium/webdriver/common/options.rbs index adf1b2bb473c7..d2fe0ccd30b20 100644 --- a/rb/sig/lib/selenium/webdriver/common/options.rbs +++ b/rb/sig/lib/selenium/webdriver/common/options.rbs @@ -1,7 +1,7 @@ module Selenium module WebDriver class Options - @options: Hash[String | Symbol, String | Numeric | bool?] + @options: Hash[untyped, untyped] W3C_OPTIONS: Array[Symbol] diff --git a/rb/sig/lib/selenium/webdriver/common/search_context.rbs b/rb/sig/lib/selenium/webdriver/common/search_context.rbs index 5f8f227156cb6..c240e68d5ce5b 100644 --- a/rb/sig/lib/selenium/webdriver/common/search_context.rbs +++ b/rb/sig/lib/selenium/webdriver/common/search_context.rbs @@ -5,6 +5,10 @@ module Selenium FINDERS: untyped + attr_accessor self.extra_finders: untyped + + def self.finders: -> untyped + def find_element: (*untyped args) -> untyped def find_elements: (*untyped args) -> untyped diff --git a/rb/sig/lib/selenium/webdriver/common/selenium_manager.rbs b/rb/sig/lib/selenium/webdriver/common/selenium_manager.rbs index f4f0c58f00d81..c73ec88f61487 100644 --- a/rb/sig/lib/selenium/webdriver/common/selenium_manager.rbs +++ b/rb/sig/lib/selenium/webdriver/common/selenium_manager.rbs @@ -12,7 +12,7 @@ module Selenium def self.bin_path: () -> String - def self.binary_paths: (Array[String] arguments) -> Hash[untyped, Array[String]] + def self.binary_paths: (*String arguments) -> Hash[untyped, Array[String]] private diff --git a/rb/sig/lib/selenium/webdriver/common/service.rbs b/rb/sig/lib/selenium/webdriver/common/service.rbs index e07af9ac3e1ce..da5a793919e55 100644 --- a/rb/sig/lib/selenium/webdriver/common/service.rbs +++ b/rb/sig/lib/selenium/webdriver/common/service.rbs @@ -5,6 +5,8 @@ module Selenium DEFAULT_PORT: untyped + DRIVER_PATH_ENV_KEY: String + self.@driver_path: untyped @executable_path: untyped @@ -47,12 +49,14 @@ module Selenium attr_accessor args: untyped - def env_path: -> String + def env_path: -> String? alias extra_args args def initialize: (?path: untyped?, ?port: untyped?, ?log: untyped?, ?args: untyped?) -> void + def find_driver_path: -> untyped + def launch: () -> untyped def shutdown_supported: () -> untyped diff --git a/rb/sig/lib/selenium/webdriver/common/websocket_connection.rbs b/rb/sig/lib/selenium/webdriver/common/websocket_connection.rbs index 411f09d91e948..98ac289f081ed 100644 --- a/rb/sig/lib/selenium/webdriver/common/websocket_connection.rbs +++ b/rb/sig/lib/selenium/webdriver/common/websocket_connection.rbs @@ -35,10 +35,14 @@ module Selenium def initialize: (url: untyped) -> void + def add_callback: (untyped event) { () -> void } -> untyped + def close: () -> untyped def callbacks: () -> untyped + def remove_callback: (untyped event, untyped id) -> untyped + def send_cmd: (**untyped payload) -> untyped private diff --git a/rb/sig/lib/selenium/webdriver/fedcm/account.rbs b/rb/sig/lib/selenium/webdriver/fedcm/account.rbs index 72ae9bf8b5884..57859a9f1ca06 100644 --- a/rb/sig/lib/selenium/webdriver/fedcm/account.rbs +++ b/rb/sig/lib/selenium/webdriver/fedcm/account.rbs @@ -1,51 +1,31 @@ module Selenium module WebDriver module FedCM - # Represents an account displayed in a FedCm account list. - # See: https://w3c-fedid.github.io/FedCM/#dictdef-identityprovideraccount - # https://w3c-fedid.github.io/FedCM/#webdriver-accountlist class Account @account_id: String - @email: String - @name: String - @given_name: String - @picture_url: String - @idp_config_url: String - @login_state: String - @terms_of_service_url: String - @privacy_policy_url: String LOGIN_STATE_SIGNIN: String - LOGIN_STATE_SIGNUP: String attr_reader account_id: String - attr_reader email: String - attr_reader name: String - attr_reader given_name: String - attr_reader picture_url: String - attr_reader idp_config_url: String - attr_reader login_state: String - attr_reader terms_of_service_url: String - attr_reader privacy_policy_url: String - def initialize: (**untyped args) -> void + def initialize: (**String args) -> void end end end diff --git a/rb/sig/lib/selenium/webdriver/fedcm/dialog.rbs b/rb/sig/lib/selenium/webdriver/fedcm/dialog.rbs index c16ef91d6c930..98e4ff81a283e 100644 --- a/rb/sig/lib/selenium/webdriver/fedcm/dialog.rbs +++ b/rb/sig/lib/selenium/webdriver/fedcm/dialog.rbs @@ -7,6 +7,8 @@ module Selenium @bridge: Remote::Bridge + def initialize: (Remote::Bridge bridge) -> void + def accounts: -> Array[Account] def cancel: -> Remote::Response? diff --git a/rb/sig/lib/selenium/webdriver/firefox/options.rbs b/rb/sig/lib/selenium/webdriver/firefox/options.rbs index d4087e414024c..4f3b12a6487b8 100644 --- a/rb/sig/lib/selenium/webdriver/firefox/options.rbs +++ b/rb/sig/lib/selenium/webdriver/firefox/options.rbs @@ -6,6 +6,8 @@ module Selenium @profile: untyped + @options: Hash[untyped, untyped] + attr_accessor debugger_address: untyped KEY: String @@ -28,7 +30,7 @@ module Selenium def log_level=: (untyped level) -> untyped - def enable_android: (?package: ::String, ?serial_number: untyped?, ?activity: untyped?, ?intent_arguments: untyped?) -> untyped + def enable_android: (?package: String, ?serial_number: untyped?, ?activity: untyped?, ?intent_arguments: untyped?) -> untyped private diff --git a/rb/sig/lib/selenium/webdriver/remote/bidi_bridge.rbs b/rb/sig/lib/selenium/webdriver/remote/bidi_bridge.rbs new file mode 100644 index 0000000000000..bb0c77cc36863 --- /dev/null +++ b/rb/sig/lib/selenium/webdriver/remote/bidi_bridge.rbs @@ -0,0 +1,17 @@ +module Selenium + module WebDriver + module Remote + class BiDiBridge < Bridge + @bidi: untyped + + attr_reader bidi: untyped + + def create_session: (Hash[Symbol, String] capabilities) -> BiDi + + def quit: () -> nil + + def close: () -> untyped + end + end + end +end diff --git a/rb/sig/lib/selenium/webdriver/remote/bridge.rbs b/rb/sig/lib/selenium/webdriver/remote/bridge.rbs index 4e06cbd61ed7e..8da1a8dac805d 100644 --- a/rb/sig/lib/selenium/webdriver/remote/bridge.rbs +++ b/rb/sig/lib/selenium/webdriver/remote/bridge.rbs @@ -32,6 +32,8 @@ module Selenium def initialize: (url: String | URI, ?http_client: untyped?) -> void + def bidi: -> WebDriver::Error::WebDriverError + def cancel_fedcm_dialog: -> nil def click_fedcm_dialog_button: -> nil @@ -46,7 +48,7 @@ module Selenium extend WebDriver::Safari::Features - def fedcm_account_list: -> [FedCM::Account] + def fedcm_account_list: -> Array[Hash[untyped, untyped]] def fedcm_dialog_type: -> String @@ -56,7 +58,7 @@ module Selenium def reset_fedcm_cooldown: -> nil - def select_fedcm_account: -> nil + def select_fedcm_account: (Integer index) -> nil def session_id: () -> untyped @@ -248,7 +250,7 @@ module Selenium private - def execute: (untyped command, ?::Hash[untyped, untyped] opts, ?untyped? command_hash) -> WebDriver::Remote::Response + def execute: (untyped command, ?::Hash[untyped, untyped] opts, ?untyped? command_hash) -> String def escaper: () -> untyped diff --git a/rb/sig/lib/selenium/webdriver/remote/bridge/locator_converter.rbs b/rb/sig/lib/selenium/webdriver/remote/bridge/locator_converter.rbs new file mode 100644 index 0000000000000..3e2b15b82b84a --- /dev/null +++ b/rb/sig/lib/selenium/webdriver/remote/bridge/locator_converter.rbs @@ -0,0 +1,19 @@ +module Selenium + module WebDriver + module Remote + class Bridge + class LocatorConverter + ESCAPE_CSS_REGEXP: Regexp + + UNICODE_CODE_POINT: Integer + + def convert: (String | Symbol how, String what) -> Array[String] + + private + + def escape_css: (String string) -> String + end + end + end + end +end diff --git a/rb/sig/lib/selenium/webdriver/remote/http/common.rbs b/rb/sig/lib/selenium/webdriver/remote/http/common.rbs index 3e1e2ecc26be3..dfafbbf8456bf 100644 --- a/rb/sig/lib/selenium/webdriver/remote/http/common.rbs +++ b/rb/sig/lib/selenium/webdriver/remote/http/common.rbs @@ -9,21 +9,31 @@ module Selenium DEFAULT_HEADERS: Hash[String, untyped] - attr_writer server_url: untyped + @common_headers: Hash[String, untyped] + + attr_accessor self.extra_headers: Hash[String, untyped] + + attr_writer self.user_agent: String + + def self.user_agent: -> String + + attr_writer server_url: String def quit_errors: () -> Array[untyped] - def close: () -> untyped + def close: () -> nil def call: (untyped verb, untyped url, untyped command_hash) -> untyped private - def server_url: () -> untyped + def common_headers: -> Hash[String, untyped] + + def server_url: () -> String def request: (*untyped) -> untyped - def create_response: (untyped code, untyped body, untyped content_type) -> untyped + def create_response: (Integer code, Hash[String, untyped] body, String content_type) -> Remote::Response end end end diff --git a/rb/sig/lib/selenium/webdriver/remote/response.rbs b/rb/sig/lib/selenium/webdriver/remote/response.rbs index 29c806ff74283..3afa65955917b 100644 --- a/rb/sig/lib/selenium/webdriver/remote/response.rbs +++ b/rb/sig/lib/selenium/webdriver/remote/response.rbs @@ -2,15 +2,15 @@ module Selenium module WebDriver module Remote class Response - @code: String + @code: Integer @payload: Hash[untyped, untyped] - attr_reader code: String + attr_reader code: Integer attr_reader payload: Hash[untyped, untyped] - def initialize: (String code, ?Hash[untyped, untyped]? payload) -> void + def initialize: (Integer code, ?Hash[untyped, untyped]? payload) -> void def error: () -> Error::WebDriverError? diff --git a/rb/sig/lib/selenium/webdriver/support/guards/guard.rbs b/rb/sig/lib/selenium/webdriver/support/guards/guard.rbs index d5e312b9ee43f..70c13b3cb0312 100644 --- a/rb/sig/lib/selenium/webdriver/support/guards/guard.rbs +++ b/rb/sig/lib/selenium/webdriver/support/guards/guard.rbs @@ -15,6 +15,7 @@ module Selenium attr_reader guarded: untyped + attr_reader tracker: String attr_reader type: untyped attr_reader messages: untyped diff --git a/rb/sig/selenium/web_driver/script.rbs b/rb/sig/selenium/web_driver/script.rbs index f2dc066174df0..0bcdcf8318cc3 100644 --- a/rb/sig/selenium/web_driver/script.rbs +++ b/rb/sig/selenium/web_driver/script.rbs @@ -4,17 +4,17 @@ module Selenium @bidi: BiDi @log_entry_subscribed: bool - def add_console_message_handler: -> untyped + @log_handler: BiDi::LogHandler - def add_javascript_error_handler: -> untyped + def initialize: (Remote::BiDiBridge bidi) -> void - def remove_console_message_handler: -> untyped + def add_console_message_handler: ?{ (untyped) -> untyped } -> Integer - alias remove_javascript_error_handler remove_console_message_handler + def add_javascript_error_handler: ?{ (untyped) -> untyped } -> Integer - private + def remove_console_message_handler: (Integer id) -> bool? - def subscribe_log_entry: -> untyped + alias remove_javascript_error_handler remove_console_message_handler end end end diff --git a/rb/spec/integration/selenium/webdriver/bidi/network_spec.rb b/rb/spec/integration/selenium/webdriver/bidi/network_spec.rb new file mode 100644 index 0000000000000..7aef0ba9856aa --- /dev/null +++ b/rb/spec/integration/selenium/webdriver/bidi/network_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +# 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. + + +require_relative '../spec_helper' + +module Selenium + module WebDriver + class BiDi + describe Network, exclusive: {bidi: true, reason: 'only executed when bidi is enabled'}, + only: {browser: %i[chrome edge firefox]} do + it 'adds an intercept' do + reset_driver!(web_socket_url: true) do |driver| + network = described_class.new(driver.bidi) + intercept = network.add_intercept(phases: [described_class::PHASES[:before_request]]) + expect(intercept).not_to be_nil + end + end + + it 'removes an intercept' do + reset_driver!(web_socket_url: true) do |driver| + network = described_class.new(driver.bidi) + intercept = network.add_intercept(phases: [described_class::PHASES[:before_request]]) + expect(network.remove_intercept(intercept['intercept'])).to be_empty + end + end + + it 'continues with auth' do + username = SpecSupport::RackServer::TestApp::BASIC_AUTH_CREDENTIALS.first + password = SpecSupport::RackServer::TestApp::BASIC_AUTH_CREDENTIALS.last + reset_driver!(web_socket_url: true) do |driver| + network = described_class.new(driver.bidi) + network.add_intercept(phases: [described_class::PHASES[:auth_required]]) + network.on(:auth_required) do |event| + request_id = event['requestId'] + network.continue_with_auth(request_id, username, password) + end + + driver.navigate.to url_for('basicAuth') + expect(driver.find_element(tag_name: 'h1').text).to eq('authorized') + end + end + end + end + end +end diff --git a/rb/spec/integration/selenium/webdriver/bidi/script_spec.rb b/rb/spec/integration/selenium/webdriver/bidi/script_spec.rb index eb660f9b4d763..578659befa1f5 100644 --- a/rb/spec/integration/selenium/webdriver/bidi/script_spec.rb +++ b/rb/spec/integration/selenium/webdriver/bidi/script_spec.rb @@ -25,6 +25,20 @@ module WebDriver only: {browser: %i[chrome edge firefox]} do after { |example| reset_driver!(example: example) } + # Helper to match the expected pattern of `script.StackFrame` objects. + # https://w3c.github.io/webdriver-bidi/#type-script-StackFrame + # + # Pass in any fields you want to check more specific values for, e.g: + # a_stack_frame('functionName' => 'someFunction') + def a_stack_frame(**options) + include({ + 'columnNumber' => an_instance_of(Integer), + 'functionName' => an_instance_of(String), + 'lineNumber' => an_instance_of(Integer), + 'url' => an_instance_of(String) + }.merge(options)) + end + it 'errors when bidi not enabled' do reset_driver!(web_socket_url: false) do |driver| msg = /BiDi must be enabled by setting #web_socket_url to true in options class/ @@ -45,10 +59,27 @@ module WebDriver expect(log_entries.size).to eq(1) log_entry = log_entries.first expect(log_entry).to be_a BiDi::LogHandler::ConsoleLogEntry + expect(log_entry.type).to eq 'console' expect(log_entry.level).to eq 'info' expect(log_entry.method).to eq 'log' expect(log_entry.text).to eq 'Hello, world!' - expect(log_entry.type).to eq 'console' + expect(log_entry.args).to eq [ + {'type' => 'string', 'value' => 'Hello, world!'} + ] + expect(log_entry.timestamp).to be_an_integer + expect(log_entry.source).to match( + 'context' => an_instance_of(String), + 'realm' => an_instance_of(String) + ) + # Stack traces on console messages are optional. + expect(log_entry.stack_trace).to be_nil.or match( + # Some browsers include stack traces from parts of the runtime, so we + # just check the first frames that come from user code. + 'callFrames' => start_with( + a_stack_frame('functionName' => 'helloWorld'), + a_stack_frame('functionName' => 'onclick') + ) + ) end it 'logs multiple console messages' do @@ -97,10 +128,22 @@ module WebDriver expect(log_entries.size).to eq(1) log_entry = log_entries.first expect(log_entry).to be_a BiDi::LogHandler::JavaScriptLogEntry - expect(log_entry.level).to eq 'error' expect(log_entry.type).to eq 'javascript' + expect(log_entry.level).to eq 'error' expect(log_entry.text).to eq 'Error: Not working' - expect(log_entry.stack_trace).not_to be_empty + expect(log_entry.timestamp).to be_an_integer + expect(log_entry.source).to match( + 'context' => an_instance_of(String), + 'realm' => an_instance_of(String) + ) + expect(log_entry.stack_trace).to match( + # Some browsers include stack traces from parts of the runtime, so we + # just check the first frames that come from user code. + 'callFrames' => start_with( + a_stack_frame('functionName' => 'createError'), + a_stack_frame('functionName' => 'onclick') + ) + ) end it 'errors removing non-existent handler' do diff --git a/rb/spec/integration/selenium/webdriver/network_spec.rb b/rb/spec/integration/selenium/webdriver/network_spec.rb new file mode 100644 index 0000000000000..11776a4e2c73f --- /dev/null +++ b/rb/spec/integration/selenium/webdriver/network_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +# 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. + + +require_relative 'spec_helper' + +module Selenium + module WebDriver + describe Network, exclusive: {bidi: true, reason: 'only executed when bidi is enabled'}, + only: {browser: %i[chrome edge firefox]} do + let(:username) { SpecSupport::RackServer::TestApp::BASIC_AUTH_CREDENTIALS.first } + let(:password) { SpecSupport::RackServer::TestApp::BASIC_AUTH_CREDENTIALS.last } + + it 'adds an auth handler' do + reset_driver!(web_socket_url: true) do |driver| + network = described_class.new(driver) + network.add_authentication_handler(username, password) + expect(network.auth_callbacks.count).to be 1 + end + end + + it 'removes an auth handler' do + reset_driver!(web_socket_url: true) do |driver| + network = described_class.new(driver) + id = network.add_authentication_handler(username, password) + network.remove_authentication_handler(id) + expect(network.auth_callbacks.count).to be 0 + end + end + + it 'clears all auth handlers' do + reset_driver!(web_socket_url: true) do |driver| + network = described_class.new(driver) + network.add_authentication_handler(username, password) + network.add_authentication_handler(username, password) + network.clear_authentication_handlers + expect(network.auth_callbacks.count).to be 0 + end + end + end + end +end diff --git a/rb/spec/integration/selenium/webdriver/remote/driver_spec.rb b/rb/spec/integration/selenium/webdriver/remote/driver_spec.rb index 36038859cd303..671aa1720fef3 100644 --- a/rb/spec/integration/selenium/webdriver/remote/driver_spec.rb +++ b/rb/spec/integration/selenium/webdriver/remote/driver_spec.rb @@ -83,10 +83,12 @@ module Remote it 'errors when not set', {except: {browser: :firefox, reason: 'grid always sets true and firefox returns it'}, exclude: {browser: :safari, reason: 'grid hangs'}} do - expect { - driver.downloadable_files - }.to raise_exception(Error::WebDriverError, - 'You must enable downloads in order to work with downloadable files.') + reset_driver!(enable_downloads: false) do |driver| + expect { + driver.downloadable_files + }.to raise_exception(Error::WebDriverError, + 'You must enable downloads in order to work with downloadable files.') + end end private diff --git a/rust/CHANGELOG.md b/rust/CHANGELOG.md index b56d9cf5b207f..12049809f5d18 100644 --- a/rust/CHANGELOG.md +++ b/rust/CHANGELOG.md @@ -1,3 +1,7 @@ +0.4.26 +====== +* Selenium Manager checks invalid browser version (#14511) + 0.4.25 ====== diff --git a/rust/Cargo.Bazel.lock b/rust/Cargo.Bazel.lock index 187142b30214a..aa489691c16ae 100644 --- a/rust/Cargo.Bazel.lock +++ b/rust/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "1b307b787c95f27b60af367955d57dfd4b21ff233bf6e156a87950f4c610430e", + "checksum": "94895b25f9b1d0a76ec78d588887353422bc623faf9ef986467b199d2a966765", "crates": { "addr2line 0.21.0": { "name": "addr2line", @@ -572,14 +572,14 @@ ], "license_file": "LICENSE-APACHE" }, - "anyhow 1.0.89": { + "anyhow 1.0.91": { "name": "anyhow", - "version": "1.0.89", + "version": "1.0.91", "package_url": "https://github.com/dtolnay/anyhow", "repository": { "Http": { - "url": "https://static.crates.io/crates/anyhow/1.0.89/download", - "sha256": "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + "url": "https://static.crates.io/crates/anyhow/1.0.91/download", + "sha256": "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" } }, "targets": [ @@ -623,7 +623,7 @@ "deps": { "common": [ { - "id": "anyhow 1.0.89", + "id": "anyhow 1.0.91", "target": "build_script_build" }, { @@ -634,7 +634,7 @@ "selects": {} }, "edition": "2018", - "version": "1.0.89" + "version": "1.0.91" }, "build_script_attrs": { "compile_data_glob": [ @@ -2034,16 +2034,79 @@ "id": "jobserver 0.1.31", "target": "jobserver" }, - { - "id": "libc 0.2.160", - "target": "libc" - }, { "id": "shlex 1.3.0", "target": "shlex" } ], - "selects": {} + "selects": { + "aarch64-apple-darwin": [ + { + "id": "libc 0.2.160", + "target": "libc" + } + ], + "aarch64-unknown-linux-gnu": [ + { + "id": "libc 0.2.160", + "target": "libc" + } + ], + "aarch64-unknown-nixos-gnu": [ + { + "id": "libc 0.2.160", + "target": "libc" + } + ], + "arm-unknown-linux-gnueabi": [ + { + "id": "libc 0.2.160", + "target": "libc" + } + ], + "i686-unknown-linux-gnu": [ + { + "id": "libc 0.2.160", + "target": "libc" + } + ], + "powerpc-unknown-linux-gnu": [ + { + "id": "libc 0.2.160", + "target": "libc" + } + ], + "s390x-unknown-linux-gnu": [ + { + "id": "libc 0.2.160", + "target": "libc" + } + ], + "x86_64-apple-darwin": [ + { + "id": "libc 0.2.160", + "target": "libc" + } + ], + "x86_64-unknown-freebsd": [ + { + "id": "libc 0.2.160", + "target": "libc" + } + ], + "x86_64-unknown-linux-gnu": [ + { + "id": "libc 0.2.160", + "target": "libc" + } + ], + "x86_64-unknown-nixos-gnu": [ + { + "id": "libc 0.2.160", + "target": "libc" + } + ] + } }, "edition": "2018", "version": "1.1.30" @@ -4058,7 +4121,7 @@ "target": "log" }, { - "id": "regex 1.11.0", + "id": "regex 1.11.1", "target": "regex" } ], @@ -10411,14 +10474,14 @@ ], "license_file": "LICENSE" }, - "regex 1.11.0": { + "regex 1.11.1": { "name": "regex", - "version": "1.11.0", + "version": "1.11.1", "package_url": "https://github.com/rust-lang/regex", "repository": { "Http": { - "url": "https://static.crates.io/crates/regex/1.11.0/download", - "sha256": "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" + "url": "https://static.crates.io/crates/regex/1.11.1/download", + "sha256": "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" } }, "targets": [ @@ -10484,7 +10547,7 @@ "selects": {} }, "edition": "2021", - "version": "1.11.0" + "version": "1.11.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -12808,7 +12871,7 @@ "target": "quote" }, { - "id": "regex 1.11.0", + "id": "regex 1.11.1", "target": "regex" }, { @@ -14073,7 +14136,7 @@ "deps": { "common": [ { - "id": "anyhow 1.0.89", + "id": "anyhow 1.0.91", "target": "anyhow" }, { @@ -14117,7 +14180,7 @@ "target": "log" }, { - "id": "regex 1.11.0", + "id": "regex 1.11.1", "target": "regex" }, { @@ -22673,7 +22736,7 @@ ] }, "direct_deps": [ - "anyhow 1.0.89", + "anyhow 1.0.91", "apple-flat-package 0.18.0", "bzip2 0.4.4", "clap 4.5.20", @@ -22684,7 +22747,7 @@ "flate2 1.0.34", "infer 0.16.0", "log 0.4.22", - "regex 1.11.0", + "regex 1.11.1", "reqwest 0.12.8", "serde 1.0.210", "serde_json 1.0.128", diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 2d606bec17983..2696beb8223eb 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -98,9 +98,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" dependencies = [ "backtrace", ] @@ -1567,9 +1567,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", diff --git a/scripts/github-actions/release_header.md b/scripts/github-actions/release_header.md index 357a1461dd907..f56da16681f6b 100644 --- a/scripts/github-actions/release_header.md +++ b/scripts/github-actions/release_header.md @@ -1,4 +1,5 @@ ## Detailed Changelogs by Component - **[Java](https://github.com/SeleniumHQ/selenium/blob/trunk/java/CHANGELOG)**     |     **[Python](https://github.com/SeleniumHQ/selenium/blob/trunk/py/CHANGES)**     |     **[DotNet](https://github.com/SeleniumHQ/selenium/blob/trunk/dotnet/CHANGELOG)**     |     **[Ruby](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES)**     |     **[JavaScript](https://github.com/SeleniumHQ/selenium/blob/trunk/javascript/node/selenium-webdriver/CHANGES.md)**     |     **[IEDriver](https://github.com/SeleniumHQ/selenium/blob/trunk/cpp/iedriverserver/CHANGELOG)** + **[Java](https://github.com/SeleniumHQ/selenium/blob/trunk/java/CHANGELOG)**     |     **[Python](https://github.com/SeleniumHQ/selenium/blob/trunk/py/CHANGES)**     |     **[DotNet](https://github.com/SeleniumHQ/selenium/blob/trunk/dotnet/CHANGELOG)**     |     **[Ruby](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES)**     |     **[JavaScript](https://github.com/SeleniumHQ/selenium/blob/trunk/javascript/node/selenium-webdriver/CHANGES.md)**     |     **[IEDriver](https://github.com/SeleniumHQ/selenium/blob/trunk/cpp/iedriverserver/CHANGELOG)**
+ diff --git a/scripts/pinned_browsers.py b/scripts/pinned_browsers.py index 70be06421e5f9..b41b169577f46 100755 --- a/scripts/pinned_browsers.py +++ b/scripts/pinned_browsers.py @@ -261,12 +261,15 @@ def edge(): def edgedriver(): - r = http.request("GET", "https://msedgedriver.azureedge.net/LATEST_STABLE") - v = r.data.decode("utf-16").strip() + r_stable = http.request("GET", "https://msedgedriver.azureedge.net/LATEST_STABLE") + stable_version = r_stable.data.decode("utf-16").strip() + major_version = stable_version.split('.')[0] + r = http.request("GET", f"https://msedgedriver.azureedge.net/LATEST_RELEASE_{major_version}_LINUX") + linux_version = r.data.decode("utf-16").strip() content = "" - linux = "https://msedgedriver.azureedge.net/%s/edgedriver_linux64.zip" % v + linux = "https://msedgedriver.azureedge.net/%s/edgedriver_linux64.zip" % linux_version sha = calculate_hash(linux) content = ( content @@ -291,7 +294,9 @@ def edgedriver(): % (linux, sha) ) - mac = "https://msedgedriver.azureedge.net/%s/edgedriver_mac64.zip" % v + r = http.request("GET", f"https://msedgedriver.azureedge.net/LATEST_RELEASE_{major_version}_MACOS") + macos_version = r.data.decode("utf-16").strip() + mac = "https://msedgedriver.azureedge.net/%s/edgedriver_mac64.zip" % macos_version sha = calculate_hash(mac) content = ( content diff --git a/scripts/remote-image/Dockerfile b/scripts/remote-image/Dockerfile index 28b7a8b9afb7a..37969e044aa9c 100644 --- a/scripts/remote-image/Dockerfile +++ b/scripts/remote-image/Dockerfile @@ -1,5 +1,5 @@ # Our images must be for Linux x86_64 -FROM --platform=linux/amd64 ubuntu:focal@sha256:0b897358ff6624825fb50d20ffb605ab0eaea77ced0adb8c6a4b756513dec6fc +FROM --platform=linux/amd64 ubuntu:focal@sha256:8e5c4f0285ecbb4ead070431d29b576a530d3166df73ec44affc1cd27555141b ENV DEBIAN_FRONTEND=noninteractive diff --git a/scripts/update_copyright.py b/scripts/update_copyright.py index 9ca241b65789b..e1af49ec80ce2 100755 --- a/scripts/update_copyright.py +++ b/scripts/update_copyright.py @@ -28,13 +28,13 @@ def __init__(self, comment_characters='//', prefix=None): def update(self, files): for file in files: - with open(file, 'r') as f: + with open(file, 'r', encoding='utf-8-sig') as f: lines = f.readlines() index = -1 for i, line in enumerate(lines): if line.startswith(self._comment_characters) or \ - self.valid_copyright_notice_line(line, index): + self.valid_copyright_notice_line(line, index, file): index += 1 else: break @@ -43,20 +43,24 @@ def update(self, files): self.write_update_notice(file, lines) else: current = ''.join(lines[:index + 1]) - if current != self.copyright_notice: + if current != self.copyright_notice(file): self.write_update_notice(file, lines[index + 1:]) - def valid_copyright_notice_line(self, line, index): - return index + 1 < len(self.copyright_notice_lines) and \ - line.startswith(self.copyright_notice_lines[index + 1]) + def valid_copyright_notice_line(self, line, index, file): + return index + 1 < len(self.copyright_notice_lines(file)) and \ + line.startswith(self.copyright_notice_lines(file)[index + 1]) - @property - def copyright_notice(self): - return ''.join(self.copyright_notice_lines) + def copyright_notice(self, file): + return ''.join(self.copyright_notice_lines(file)) - @property - def copyright_notice_lines(self): - return self._prefix + self.commented_notice_lines + def copyright_notice_lines(self, file): + return self.dotnet(file) if file.endswith("cs") else self._prefix + self.commented_notice_lines + + def dotnet(self, file): + file_name = os.path.basename(file) + first = f"{self._comment_characters} \n" + last = f"{self._comment_characters} " + return [first] + self.commented_notice_lines + [last] @property def commented_notice_lines(self): @@ -65,8 +69,11 @@ def commented_notice_lines(self): def write_update_notice(self, file, lines): print(f"Adding notice to {file}") with open(file, 'w') as f: - f.write(self.copyright_notice + "\n") - f.writelines(lines) + f.write(self.copyright_notice(file) + "\n") + if lines and lines[0] != "\n": + f.write("\n") + trimmed_lines = [line.rstrip() + "\n" for line in lines] + f.writelines(trimmed_lines) ROOT = Path(os.path.realpath(__file__)).parent.parent @@ -109,3 +116,4 @@ def update_files(file_pattern, exclusions, comment_characters='//', prefix=None) update_files(f"{ROOT}/rb/**/*.rb", [], comment_characters="#", prefix=["# frozen_string_literal: true\n", "\n"]) update_files(f"{ROOT}/java/**/*.java", []) update_files(f"{ROOT}/rust/**/*.rs", []) + update_files(f"{ROOT}/dotnet/**/*.cs", [])