├── .bazelignore ├── .bazelrc ├── .bazelversion ├── .bcr ├── README.md ├── config.yml ├── metadata.template.json ├── presubmit.yml └── source.template.json ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .gitattributes ├── .github └── workflows │ ├── BUILD.bazel │ ├── buildifier.yaml.disabled │ ├── ci.bazelrc │ ├── release.yml │ ├── release_prep.sh │ ├── tag.yaml │ └── tests.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .prettierignore ├── BUILD.bazel ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── MODULE.bazel ├── README.md ├── WORKSPACE.bazel ├── docs ├── BUILD.bazel ├── extensions.md ├── integrity_map.md └── repositories.md ├── examples ├── rules_js │ ├── .bazelignore │ ├── .bazelrc │ ├── .bazelversion │ ├── .gitignore │ ├── BUILD.bazel │ ├── MODULE.bazel │ ├── WORKSPACE.bazel │ ├── browsers.1.50.1.json │ ├── package.json │ ├── playwright.config.ts │ ├── pnpm-lock.yaml │ ├── tests │ │ ├── example.spec.ts │ │ ├── snapshots.spec.ts │ │ └── snapshots.spec.ts-snapshots │ │ │ ├── loaded-chromium-darwin.png │ │ │ ├── loaded-chromium-linux.png │ │ │ ├── loaded-firefox-darwin.png │ │ │ ├── loaded-firefox-linux.png │ │ │ ├── loaded-webkit-darwin.png │ │ │ └── loaded-webkit-linux.png │ ├── todomvc │ │ ├── base.css │ │ ├── bundle.js │ │ ├── director.js │ │ ├── index.css │ │ └── index.html │ └── tsconfig.json └── workspace │ ├── .bazelrc │ ├── .bazelversion │ ├── BUILD.bazel │ ├── WORKSPACE.bazel │ ├── package.json │ ├── playwright.config.ts │ ├── pnpm-lock.yaml │ └── simple.e2e.ts ├── playwright ├── BUILD.bazel ├── defs.bzl ├── extensions.bzl ├── private │ ├── BUILD.bazel │ ├── cli │ │ ├── BUILD.bazel │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── browser_targets.rs │ │ │ ├── browsers.rs │ │ │ ├── download_paths.json │ │ │ ├── download_paths.rs │ │ │ ├── extract_zip.rs │ │ │ ├── integrity_map.rs │ │ │ ├── main.rs │ │ │ ├── platform_groups.rs │ │ │ ├── templates │ │ │ ├── aliases.rs │ │ │ ├── browsers.rs │ │ │ ├── mod.rs │ │ │ └── root.rs │ │ │ └── test │ │ │ ├── BUILD.bazel │ │ │ └── browsers.json │ ├── integrity_map.bzl │ ├── known_browsers.bzl │ ├── select_exec.bzl │ ├── unzip_browser.bzl │ └── util.bzl └── repositories.bzl ├── renovate.json └── tools ├── BUILD.bazel ├── config_setting └── BUILD.bazel ├── linkers └── BUILD.bazel ├── platforms └── BUILD.bazel ├── release ├── BUILD.bazel ├── artifacts │ ├── cli-arm64-apple-darwin │ ├── cli-arm64-unknown-linux-musl │ ├── cli-x86_64-apple-darwin │ └── cli-x86_64-unknown-linux-musl ├── copy_release_artifacts.sh └── defs.bzl └── version.bzl /.bazelignore: -------------------------------------------------------------------------------- 1 | target/ 2 | node_modules/ 3 | examples/rules_js 4 | gen/ -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | # Bazel settings that apply to this repository. 2 | # Take care to document any settings that you expect users to apply. 3 | # Settings that apply only to CI are in .github/workflows/ci.bazelrc 4 | 5 | common --incompatible_enable_cc_toolchain_resolution 6 | common --incompatible_enable_proto_toolchain_resolution 7 | 8 | # No external runfiles 9 | common --nolegacy_external_runfiles 10 | 11 | # Don’t want to push a rules author to update their deps if not needed. 12 | # https://bazel.build/reference/command-line-reference#flag--check_direct_dependencies 13 | # https://bazelbuild.slack.com/archives/C014RARENH0/p1691158021917459?thread_ts=1691156601.420349&cid=C014RARENH0 14 | common --check_direct_dependencies=off 15 | 16 | # Minimize the size of our rust binaries 17 | build:release --compilation_mode=opt 18 | build:release --@rules_rust//rust/settings:lto=fat 19 | 20 | # Never Compile protoc Again 21 | # Don't build protoc from the cc_binary, it's slow and spammy when cache miss 22 | common --per_file_copt=external/.*protobuf.*@--PROTOBUF_WAS_NOT_SUPPOSED_TO_BE_BUILT 23 | common --host_per_file_copt=external/.*protobuf.*@--PROTOBUF_WAS_NOT_SUPPOSED_TO_BE_BUILT 24 | 25 | # Stardoc needs a jdk; give a hermetic one 26 | common --java_runtime_version=remotejdk_11 27 | 28 | # Load any settings specific to the current user. 29 | # .bazelrc.user should appear in .gitignore so that settings are not shared with team members 30 | # This needs to be last statement in this 31 | # config, as the user configuration should be able to overwrite flags from this file. 32 | # See https://docs.bazel.build/versions/master/best-practices.html#bazelrc 33 | # (Note that we use .bazelrc.user so the file appears next to .bazelrc in directory listing, 34 | # rather than user.bazelrc as suggested in the Bazel docs) 35 | try-import %workspace%/.bazelrc.user 36 | -------------------------------------------------------------------------------- /.bazelversion: -------------------------------------------------------------------------------- 1 | latest 2 | # The first line of this file is used by Bazelisk and Bazel to be sure 3 | # the right version of Bazel is used to build and test this repo. 4 | # This also defines which version is used on CI. 5 | # 6 | # Note that you should also run integration_tests against other Bazel 7 | # versions you support. 8 | -------------------------------------------------------------------------------- /.bcr/README.md: -------------------------------------------------------------------------------- 1 | # Bazel Central Registry 2 | 3 | When the ruleset is released, we want it to be published to the 4 | Bazel Central Registry automatically: 5 | 6 | 7 | This folder contains configuration files to automate the publish step. 8 | See 9 | for authoritative documentation about these files. 10 | -------------------------------------------------------------------------------- /.bcr/config.yml: -------------------------------------------------------------------------------- 1 | # See https://github.com/bazel-contrib/publish-to-bcr#a-note-on-release-automation 2 | # for guidance about whether to uncomment this section: 3 | # 4 | # fixedReleaser: 5 | # login: my_github_handle 6 | # email: me@my.org 7 | -------------------------------------------------------------------------------- /.bcr/metadata.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "homepage": "https://github.com/mrmeku/rules_playwright", 3 | "maintainers": [ 4 | { 5 | "name": "Daniel Muller", 6 | "email": "mrmeku@gmail.com", 7 | "github": "mrmeku" 8 | } 9 | ], 10 | "repository": ["github:mrmeku/rules_playwright"], 11 | "versions": [], 12 | "yanked_versions": {} 13 | } 14 | -------------------------------------------------------------------------------- /.bcr/presubmit.yml: -------------------------------------------------------------------------------- 1 | bcr_test_module: 2 | module_path: "examples/rules_js" 3 | matrix: 4 | platform: ["ubuntu2004"] 5 | bazel: ["8.x"] 6 | tasks: 7 | run_tests: 8 | name: "Run test module" 9 | platform: ${{ platform }} 10 | bazel: ${{ bazel }} 11 | build_targets: 12 | - "//..." 13 | -------------------------------------------------------------------------------- /.bcr/source.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "integrity": "**leave this alone**", 3 | "strip_prefix": "{REPO}-{VERSION}", 4 | "url": "https://github.com/{OWNER}/{REPO}/releases/download/{TAG}/{REPO}-{TAG}.tar.gz" 5 | } 6 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/playwright:v1.50.1-noble 2 | 3 | # Download bazelisk and place it in $PATH 4 | RUN wget https://github.com/bazelbuild/bazelisk/releases/download/v1.18.0/bazelisk-linux-arm64 5 | RUN chmod +x bazelisk-linux-arm64 6 | RUN mv bazelisk-linux-arm64 /usr/local/bin/bazel -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | // instructs devcontainers to use a Dockerfile 4 | // rather than a pre-defined image 5 | "dockerfile": "Dockerfile" 6 | }, 7 | "customizations": { 8 | "vscode": { 9 | "extensions": [ 10 | "ms-azuretools.vscode-docker", // docker support 11 | "BazelBuild.vscode-bazel" // bazel support 12 | ] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # In code review, collapse generated files 2 | docs/*.md linguist-generated=true 3 | 4 | -------------------------------------------------------------------------------- /.github/workflows/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@buildifier_prebuilt//:rules.bzl", "buildifier") 2 | 3 | buildifier( 4 | name = "buildifier.check", 5 | exclude_patterns = ["./.git/*"], 6 | lint_mode = "warn", 7 | mode = "diff", 8 | ) 9 | -------------------------------------------------------------------------------- /.github/workflows/buildifier.yaml.disabled: -------------------------------------------------------------------------------- 1 | name: Buildifier 2 | 3 | # Controls when the action will run. 4 | on: 5 | # Triggers the workflow on push or pull request events but only for the main branch 6 | push: 7 | branches: [main] 8 | pull_request: 9 | branches: [main] 10 | # Allows you to run this workflow manually from the Actions tab 11 | workflow_dispatch: 12 | 13 | jobs: 14 | check: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: buildifier 19 | run: bazel run --enable_bzlmod //.github/workflows:buildifier.check 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.bazelrc: -------------------------------------------------------------------------------- 1 | # This file contains Bazel settings to apply on CI only. 2 | # It is referenced with a --bazelrc option in the call to bazel in ci.yaml 3 | 4 | # Debug where options came from 5 | build --announce_rc 6 | # This directory is configured in GitHub actions to be persisted between runs. 7 | # We do not enable the repository cache to cache downloaded external artifacts 8 | # as these are generally faster to download again than to fetch them from the 9 | # GitHub actions cache. 10 | build --disk_cache=~/.cache/bazel 11 | # Don't rely on test logs being easily accessible from the test runner, 12 | # though it makes the log noisier. 13 | test --test_output=errors 14 | # Allows tests to run bazelisk-in-bazel, since this is the cache folder used 15 | test --test_env=XDG_CACHE_HOME -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - "v*.*.*" 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Prepare release notes 18 | run: .github/workflows/release_prep.sh > release_notes.txt 19 | 20 | - name: Release 21 | uses: softprops/action-gh-release@v2 22 | with: 23 | prerelease: false 24 | body_path: release_notes.txt 25 | # Use GH feature to populate the changelog automatically 26 | generate_release_notes: true 27 | files: | 28 | rules_playwright-*.tar.gz 29 | fail_on_unmatched_files: true 30 | -------------------------------------------------------------------------------- /.github/workflows/release_prep.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit -o nounset -o pipefail 4 | 5 | TAG=${GITHUB_REF_NAME} 6 | # The prefix is chosen to match what GitHub generates for source archives 7 | # This guarantees that users can easily switch from a released artifact to a source archive 8 | # with minimal differences in their code (e.g. strip_prefix remains the same) 9 | PREFIX="rules_playwright-${TAG:1}" 10 | ARCHIVE="rules_playwright-$TAG.tar.gz" 11 | 12 | # NB: configuration for 'git archive' is in /.gitattributes 13 | git archive --format=tar --prefix=${PREFIX}/ ${TAG} | gzip > $ARCHIVE 14 | SHA=$(shasum -a 256 $ARCHIVE | awk '{print $1}') 15 | 16 | cat << EOF 17 | ## Using Bzlmod with Bazel 6 or greater 18 | 19 | Add to your \`MODULE.bazel\` file: 20 | 21 | \`\`\`starlark 22 | bazel_dep(name = "rules_playwright", version = "${TAG:1}") 23 | 24 | playwright = use_extension("@rules_playwright//playwright:extensions.bzl", "playwright") 25 | playwright.repo( 26 | name = "playwright", 27 | playwright_version = "", # Match the exact version from your pnpm lock file of playwright-core 28 | browser_json = "", # Or vendor the browsers.json file from playwright core into your repo 29 | ) 30 | use_repo(playwright, "playwright") 31 | \`\`\` 32 | EOF -------------------------------------------------------------------------------- /.github/workflows/tag.yaml: -------------------------------------------------------------------------------- 1 | # Tag a new release using https://github.com/marketplace/actions/conventional-commits-versioner-action 2 | # 3 | # This is easier than having to run manual `git` operations on a local clone. 4 | # It also runs on a schedule so we don't leave commits unreleased indefinitely 5 | # (avoiding users having to ping "hey could someone cut a release"). 6 | 7 | name: Tag a Release 8 | on: 9 | # Allow devs to tag manually through the GitHub UI. 10 | # For example after landing a fix that customers are waiting for. 11 | workflow_dispatch: 12 | 13 | # Run twice a month, on the 1rst and 15th at 3PM UTC (8AM PST) 14 | # This is a trade-off between making too many releases, 15 | # which overwhelms BCR maintainers and over-notifies users, 16 | # and releasing too infrequently which delays delivery of bugfixes and features. 17 | schedule: 18 | - cron: "0 15 1,15 * *" 19 | 20 | jobs: 21 | tag: 22 | permissions: 23 | contents: write # allow create tag 24 | runs-on: ubuntu-latest 25 | outputs: 26 | new-tag: ${{ steps.ccv.outputs.new-tag }} 27 | new-tag-version: ${{steps.ccv.outputs.new-tag-version}} 28 | steps: 29 | - uses: actions/checkout@v4 30 | with: 31 | # Need enough history to find the prior release tag 32 | fetch-depth: 0 33 | - name: Bump tag if necessary 34 | id: ccv 35 | uses: smlx/ccv@7318e2f25a52dcd550e75384b84983973251a1f8 # v0.10.0 36 | release: 37 | needs: tag 38 | uses: ./.github/workflows/release.yml 39 | with: 40 | tag_name: ${{ needs.tag.outputs.new-tag-version }} 41 | if: needs.tag.outputs.new-tag == 'true' && needs.tag.outputs.new-tag-version-type != 'major' 42 | permissions: 43 | contents: write # allow create release 44 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | # One active job per PR, cancel older ones on push 10 | concurrency: 11 | group: ${{ github.head_ref || github.run_id }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | tests: 16 | name: Build and Test 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: "18" 25 | 26 | # Install only Playwright system dependencies 27 | - name: Install Playwright system dependencies 28 | run: npx playwright install-deps 29 | 30 | - name: Build and Test WORKSPACE example 31 | working-directory: examples/workspace 32 | run: bazelisk test //... 33 | 34 | - name: Build and Test MODULE.bazel example 35 | working-directory: examples/rules_js 36 | run: bazelisk test //... --test_output=all 37 | 38 | - uses: actions/upload-artifact@v4 39 | if: failure() 40 | with: 41 | name: bazel-testlogs 42 | path: bazel-testlogs 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bazel-* 2 | .bazelrc.user 3 | .idea/ 4 | .ijwb/ 5 | node_modules 6 | out/ 7 | # Added by cargo 8 | 9 | /target 10 | MODULE.bazel.lock 11 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See CONTRIBUTING.md for instructions. 2 | # See https://pre-commit.com for more information 3 | # See https://pre-commit.com/hooks.html for more hooks 4 | 5 | # Commitizen runs in commit-msg stage 6 | # but we don't want to run the other hooks on commit messages 7 | default_stages: [commit] 8 | 9 | # Use a slightly older version of node by default 10 | # as the default uses a very new version of GLIBC 11 | default_language_version: 12 | node: 16.18.0 13 | 14 | repos: 15 | # Check formatting and lint for starlark code 16 | - repo: https://github.com/keith/pre-commit-buildifier 17 | rev: 7.3.1.1 18 | hooks: 19 | - id: buildifier 20 | - id: buildifier-lint 21 | # Enforce that commit messages allow for later changelog generation 22 | - repo: https://github.com/commitizen-tools/commitizen 23 | rev: v4.1.0 24 | hooks: 25 | # Requires that commitizen is already installed 26 | - id: commitizen 27 | stages: [commit-msg] 28 | - repo: https://github.com/pre-commit/mirrors-prettier 29 | rev: v3.1.0 30 | hooks: 31 | - id: prettier 32 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | docs/*.md 2 | pnpm-lock.yaml -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//rules:common_settings.bzl", "string_flag") 2 | load("//playwright:defs.bzl", "LINUX_DISTROS", "MACOS_VERSIONS") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | string_flag( 7 | name = "macos_version", 8 | build_setting_default = "14", 9 | values = MACOS_VERSIONS, 10 | ) 11 | 12 | string_flag( 13 | name = "linux_distro", 14 | build_setting_default = "ubuntu24.04", 15 | values = LINUX_DISTROS, 16 | ) 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | ## Using devcontainers 4 | 5 | If you are using [devcontainers](https://code.visualstudio.com/docs/devcontainers/containers) 6 | and/or [codespaces](https://github.com/features/codespaces) then you can start 7 | contributing immediately and skip the next step. 8 | 9 | ## Formatting 10 | 11 | Starlark files should be formatted by buildifier. 12 | We suggest using a pre-commit hook to automate this. 13 | First [install pre-commit](https://pre-commit.com/#installation), 14 | then run 15 | 16 | ```shell 17 | pre-commit install 18 | ``` 19 | 20 | Otherwise later tooling on CI will yell at you about formatting/linting violations. 21 | 22 | ## Updating BUILD files 23 | 24 | Some targets are generated from sources. 25 | Currently this is just the `bzl_library` targets. 26 | Run `bazel run //:gazelle` to keep them up-to-date. 27 | 28 | ## Using this as a development dependency of other rules 29 | 30 | You'll commonly find that you develop in another WORKSPACE, such as 31 | some other ruleset that depends on rules_playwright, or in a nested 32 | WORKSPACE in the integration_tests folder. 33 | 34 | To always tell Bazel to use this directory rather than some release 35 | artifact or a version fetched from the internet, run this from this 36 | directory: 37 | 38 | ```sh 39 | OVERRIDE="--override_repository=rules_playwright=$(pwd)/rules_playwright" 40 | echo "common $OVERRIDE" >> ~/.bazelrc 41 | ``` 42 | 43 | This means that any usage of `@rules_playwright` on your system will point to this folder. 44 | 45 | ## Releasing 46 | 47 | Releases are automated on a cron trigger. 48 | The new version is determined automatically from the commit history, assuming the commit messages follow conventions, using 49 | https://github.com/marketplace/actions/conventional-commits-versioner-action. 50 | If you do nothing, eventually the newest commits will be released automatically as a patch or minor release. 51 | This automation is defined in .github/workflows/tag.yaml. 52 | 53 | Rather than wait for the cron event, you can trigger manually. Navigate to 54 | https://github.com/mrmeku/rules_playwright/actions/workflows/tag.yaml 55 | and press the "Run workflow" button. 56 | 57 | If you need control over the next release version, for example when making a release candidate for a new major, 58 | then: tag the repo and push the tag, for example 59 | 60 | ```sh 61 | % git fetch 62 | % git tag v1.0.0-rc0 origin/main 63 | % git push origin v1.0.0-rc0 64 | ``` 65 | 66 | Then watch the automation run on GitHub actions which creates the release. 67 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["playwright/private/cli"] 4 | 5 | [workspace.package] 6 | edition = "2021" 7 | rust-version = "1.84.0" 8 | readme = "README.md" 9 | 10 | [profile.release] 11 | opt-level = "z" # Optimize for size 12 | strip = true # Automatically strip debug symbols from the binary 13 | lto = true # Enable Link Time Optimization (LTO) 14 | codegen-units = 1 # Reduce Parallel Code Generation Units to Increase Optimization 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | "Bazel dependencies" 2 | 3 | module( 4 | name = "rules_playwright", 5 | version = "0.0.0", 6 | compatibility_level = 1, 7 | ) 8 | 9 | bazel_dep(name = "bazel_skylib", version = "1.4.1") 10 | bazel_dep(name = "platforms", version = "0.0.10") 11 | bazel_dep(name = "aspect_bazel_lib", version = "2.11.0") 12 | 13 | bazel_dep(name = "toolchains_musl", version = "0.1.20", dev_dependency = True) 14 | 15 | toolchains_musl = use_extension( 16 | "@toolchains_musl//:toolchains_musl.bzl", 17 | "toolchains_musl", 18 | dev_dependency = True, 19 | ) 20 | toolchains_musl.config( 21 | extra_target_compatible_with = ["@//tools/linkers:musl"], 22 | ) 23 | 24 | bazel_dep(name = "toolchains_llvm", version = "1.2.0", dev_dependency = True) 25 | 26 | # Configure and register the toolchain. 27 | llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm", dev_dependency = True) 28 | llvm.toolchain( 29 | llvm_version = "16.0.0", 30 | ) 31 | use_repo(llvm, "llvm_toolchain") 32 | 33 | register_toolchains( 34 | "@llvm_toolchain//:all", 35 | dev_dependency = True, 36 | ) 37 | 38 | bazel_dep(name = "buildifier_prebuilt", version = "6.1.2", dev_dependency = True) 39 | bazel_dep( 40 | name = "rules_rust", 41 | version = "0.59.2", 42 | dev_dependency = True, 43 | ) 44 | 45 | rust = use_extension( 46 | "@rules_rust//rust:extensions.bzl", 47 | "rust", 48 | dev_dependency = True, 49 | ) 50 | rust.toolchain( 51 | edition = "2021", 52 | extra_target_triples = { 53 | "x86_64-apple-darwin": [ 54 | "@platforms//cpu:x86_64", 55 | "@platforms//os:macos", 56 | ], 57 | "aarch64-apple-darwin": [ 58 | "@platforms//cpu:arm64", 59 | "@platforms//os:macos", 60 | ], 61 | "aarch64-unknown-linux-musl": [ 62 | "@platforms//cpu:arm64", 63 | "@platforms//os:linux", 64 | "@//tools/linkers:musl", 65 | ], 66 | "x86_64-unknown-linux-musl": [ 67 | "@platforms//cpu:x86_64", 68 | "@platforms//os:linux", 69 | "@//tools/linkers:musl", 70 | ], 71 | }, 72 | sha256s = { 73 | "rustc-1.84.0-aarch64-apple-darwin.tar.xz": "7e2a1594c1e174f28b84adab25e96699feb8d0455dabe8dac1b2850945b400b1", 74 | "clippy-1.84.0-aarch64-apple-darwin.tar.xz": "5317d98dc9d6994f8d087aabecc16627d1a202500acd09f752d722f5462587df", 75 | "cargo-1.84.0-aarch64-apple-darwin.tar.xz": "0dc1cf2510c692f0d2ac254bf8474b3f96a719eeb5062b16dceb30361c0d235e", 76 | "llvm-tools-1.84.0-aarch64-apple-darwin.tar.xz": "2600f7f19064157b3b4108a852a311c29bbe3353d1f24b284cc942b89693f3f6", 77 | "rust-std-1.84.0-aarch64-apple-darwin.tar.xz": "c3ec73225e0f0228e4a8a9c650195b3f9e63f839954e65509d47ea58316f8f5d", 78 | }, 79 | versions = ["1.84.0"], 80 | ) 81 | use_repo(rust, "rust_toolchains") 82 | 83 | register_toolchains( 84 | "@rust_toolchains//:all", 85 | dev_dependency = True, 86 | ) 87 | 88 | crate = use_extension( 89 | "@rules_rust//crate_universe:extension.bzl", 90 | "crate", 91 | dev_dependency = True, 92 | ) 93 | crate.from_cargo( 94 | name = "crate_index", 95 | cargo_lockfile = "//:Cargo.lock", 96 | manifests = [ 97 | "//:Cargo.toml", 98 | "//playwright/private/cli:Cargo.toml", 99 | ], 100 | supported_platform_triples = [ 101 | "x86_64-apple-darwin", 102 | "aarch64-apple-darwin", 103 | "aarch64-unknown-linux-gnu", 104 | "x86_64-unknown-linux-gnu", 105 | ], 106 | ) 107 | use_repo(crate, "crate_index") 108 | 109 | http_jar = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_jar") 110 | 111 | http_jar( 112 | name = "stardoc-prebuilt", 113 | integrity = "sha256-jDi5ITmziwwiHCsfd8v0UOoraWXIAfICIll+wbpg/vE=", 114 | urls = ["https://github.com/alexeagle/stardoc-prebuilt/releases/download/v0.7.1/renderer_deploy.jar"], 115 | ) 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rules_playwright 2 | 3 | Bazel rules for downloading and using Playwright browsers. These rules provide access to browser binaries that can be used with other rule sets like `aspect_rules_js`. 4 | 5 | ## Overview 6 | 7 | `rules_playwright` provides Bazel targets for downloading Playwright browser binaries for use in your Bazel builds. It handles platform-specific downloads and configuration, making it easier to integrate browser-based testing into your Bazel workspace. 8 | 9 | ## Documentation 10 | 11 | For detailed information on the rules and how to use them, see: 12 | 13 | - [Module extension](docs/extensions.md) 14 | - [Repository rule](docs/repositories.md) 15 | - [Generating an integrity map](docs/integrity_map.md) 16 | 17 | ## Setup 18 | 19 | Add the following to your `MODULE.bazel` file: 20 | 21 | ```python 22 | bazel_dep(name = "rules_playwright", version = "0.0.0") 23 | 24 | playwright = use_extension("@rules_playwright//playwright:extensions.bzl", "playwright") 25 | playwright.repo( 26 | name = "playwright", 27 | playwright_version = "MATCH_YOUR_PACKAGE_JSON", 28 | ) 29 | use_repo(playwright, "playwright") 30 | ``` 31 | 32 | ## Usage with rules_js 33 | 34 | Here's an example of how to use `rules_playwright` with Playwright's test runner and `rules_js`: 35 | 36 | ```python 37 | load("@npm//:defs.bzl", "npm_link_all_packages") 38 | load("@npm//:@playwright/test/package_json.bzl", playwright_bin = "bin") 39 | 40 | npm_link_all_packages(name = "node_modules") 41 | 42 | playwright_bin.playwright_test( 43 | name = "test", 44 | args = ["test"], 45 | data = [ 46 | "playwright.config.ts", 47 | "tests/example.spec.ts", 48 | "//:node_modules/@playwright/test", 49 | "@playwright//:chromium-headless-shell", 50 | "@playwright//:firefox", 51 | "@playwright//:webkit", 52 | ], 53 | env = { 54 | "PLAYWRIGHT_BROWSERS_PATH": "$(rootpath @playwright//:chromium-headless-shell)/../", 55 | }, 56 | tags = ["no-sandbox"], # Required for Firefox on macOS 57 | ) 58 | ``` 59 | 60 | For a complete example, see [examples/rules_js](examples/rules_js). 61 | 62 | ## Integrity Verification 63 | 64 | You can use the `playwright_integrity_map` rule to generate integrity hashes for browser downloads: 65 | 66 | ```python 67 | load("@rules_playwright//playwright:defs.bzl", "playwright_browser_matrix", "playwright_integrity_map") 68 | 69 | playwright_integrity_map( 70 | name = "integrity_map", 71 | browsers = playwright_browser_matrix( 72 | browser_names = [ 73 | "chromium-headless-shell", 74 | "firefox", 75 | "webkit", 76 | ], 77 | platforms = [ 78 | "mac14-arm64", 79 | "ubuntu20.04-x64", 80 | ], 81 | playright_repo_name = "playwright", 82 | ), 83 | ) 84 | ``` 85 | 86 | Then add the generated integrity values to your MODULE.bazel file: 87 | 88 | ```python 89 | playwright.repo( 90 | name = "playwright", 91 | playwright_version = "1.50.1", 92 | integrity_path_map = { 93 | "builds/chromium/1155/chromium-headless-shell-mac-arm64.zip": "sha256-a499efe4d7e8b2dec1b654a51b524fce94078335e5c5ead91f20e8577ff534cd", 94 | # Other browser hashes... 95 | }, 96 | ) 97 | ``` 98 | 99 | ## Available Browsers and Executables 100 | 101 | The following targets are available within the generated repository: 102 | 103 | - `@playwright//:chromium` - Standard Chromium browser 104 | - `@playwright//:chromium-headless-shell` - Headless Chromium shell 105 | - `@playwright//:chromium-tip-of-tree` - Latest Chromium build 106 | - `@playwright//:firefox` - Standard Firefox browser 107 | - `@playwright//:firefox-beta` - Firefox Beta version 108 | - `@playwright//:webkit` - WebKit browser 109 | - `@playwright//:android` - Android browser support 110 | - `@playwright//:ffmpeg` - FFmpeg for video recording 111 | 112 | Note `@playwright` is merely the default workspace name. Your repository name will be whatever you declare in your MODULE.bazel file. 113 | 114 | ## Platform Configuration 115 | 116 | `rules_playwright` provides configuration flags to specify the target platform version for browser downloads. 117 | 118 | Add the following to your `BUILD.bazel` file: 119 | 120 | ```python 121 | load("@bazel_skylib//rules:common_settings.bzl", "string_flag") 122 | 123 | string_flag( 124 | name = "macos_version", 125 | build_setting_default = "12", 126 | values = [ 127 | "10.13", 128 | "10.14", 129 | "10.15", 130 | "11", 131 | "12", 132 | "13", 133 | "14", 134 | "15", 135 | ], 136 | ) 137 | 138 | string_flag( 139 | name = "linux_distro", 140 | build_setting_default = "debian11", 141 | values = [ 142 | "debian11", 143 | "debian12", 144 | "ubuntu20.04", 145 | "ubuntu22.04", 146 | "ubuntu24.04", 147 | ], 148 | ) 149 | ``` 150 | 151 | You can override these defaults using Bazel's command line flags: 152 | 153 | ```bash 154 | bazel test //... --@rules_playwright//:macos_version=13 155 | bazel test //... --@rules_playwright//:linux_distro=ubuntu22.04 156 | ``` 157 | 158 | ## Notes 159 | 160 | - Firefox requires the `no-sandbox` tag when running on macOS. 161 | - The `PLAYWRIGHT_BROWSERS_PATH` environment variable must be set to the directory containing the browser binaries. You can use the make variable `rootpath` on any browser followed by `../`. 162 | - Browser selection is handled automatically based on the current platform and specified version flags. Make sure you set the appropriate flag for your test exec platform. 163 | 164 | ## Contributing 165 | 166 | See [CONTRIBUTING.md](CONTRIBUTING.md) for information on how to contribute to this project. 167 | -------------------------------------------------------------------------------- /WORKSPACE.bazel: -------------------------------------------------------------------------------- 1 | # Marker that this is the root of a Bazel workspace. 2 | -------------------------------------------------------------------------------- /docs/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@aspect_bazel_lib//lib:docs.bzl", "stardoc_with_diff_test", "update_docs") 2 | 3 | stardoc_with_diff_test( 4 | name = "integrity_map", 5 | bzl_library_target = "//playwright:defs", 6 | symbol_names = [ 7 | "playwright_integrity_map", 8 | "playwright_browser_matrix", 9 | ], 10 | renderer = "//tools:stardoc_renderer", 11 | ) 12 | 13 | stardoc_with_diff_test( 14 | name = "extensions", 15 | bzl_library_target = "//playwright:extensions", 16 | renderer = "//tools:stardoc_renderer", 17 | ) 18 | 19 | stardoc_with_diff_test( 20 | name = "repositories", 21 | bzl_library_target = "//playwright:repositories", 22 | renderer = "//tools:stardoc_renderer", 23 | ) 24 | 25 | update_docs(name = "update") 26 | -------------------------------------------------------------------------------- /docs/extensions.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Extensions for bzlmod. 4 | 5 | Installs a playwright toolchain. 6 | Every module can define a toolchain version under the default name, "playwright". 7 | The latest of those versions will be selected (the rest discarded), 8 | and will always be registered by rules_playwright. 9 | 10 | Additionally, the root module can define arbitrarily many more toolchain versions under different 11 | names (the latest version will be picked for each name) and can register them as it sees fit, 12 | effectively overriding the default named toolchain due to toolchain resolution precedence. 13 | 14 | 15 | 16 | ## playwright 17 | 18 |
19 | playwright = use_extension("@rules_playwright//playwright:extensions.bzl", "playwright")
20 | playwright.repo(name, browsers_json, integrity_map, integrity_path_map, playwright_version)
21 | 
22 | 23 | 24 | **TAG CLASSES** 25 | 26 | 27 | 28 | ### repo 29 | 30 | **Attributes** 31 | 32 | | Name | Description | Type | Mandatory | Default | 33 | | :------------- | :------------- | :------------- | :------------- | :------------- | 34 | | name | Base name for generated repositories, allowing more than one playwright toolchain to be registered. Overriding the default is only permitted in the root module. | Name | optional | `"playwright"` | 35 | | browsers_json | Alternative to playwright_version. Skips downloading from unpkg | Label | optional | `None` | 36 | | integrity_map | Deprecated: Mapping from brower target to integrity hash | Dictionary: String -> String | optional | `{}` | 37 | | integrity_path_map | Mapping from browser path to integrity hash | Dictionary: String -> String | optional | `{}` | 38 | | playwright_version | Explicit version of playwright to download browsers.json from | String | optional | `""` | 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/integrity_map.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public API re-exports 4 | 5 | 6 | 7 | ## playwright_integrity_map 8 | 9 |
10 | load("@rules_playwright//playwright:defs.bzl", "playwright_integrity_map")
11 | 
12 | playwright_integrity_map(name, browsers, output, silent)
13 | 
14 | 15 | 16 | 17 | **ATTRIBUTES** 18 | 19 | 20 | | Name | Description | Type | Mandatory | Default | 21 | | :------------- | :------------- | :------------- | :------------- | :------------- | 22 | | name | A unique name for this target. | Name | required | | 23 | | browsers | A list of browser targets to generate integrity values for.

These targets should usually be the result of the playwright_browser_matrix() function, which creates a cross-product of browser names and platforms. Each target must provide the UnzippedBrowserInfo provider. | List of labels | optional | `[]` | 24 | | output | The name of the output file to create containing the integrity map.

This file will contain the generated integrity values for the specified browsers. Defaults to "target_name.json" | String | optional | `""` | 25 | | silent | Whether to suppress debug information output.

When set to False (default), the rule will print integrity information that users would typically copy and paste into their MODULE.bazel or WORKSPACE file. Set to True to prevent this debug output from being printed. | Boolean | optional | `False` | 26 | 27 | 28 | 29 | 30 | ## playwright_browser_matrix 31 | 32 |
33 | load("@rules_playwright//playwright:defs.bzl", "playwright_browser_matrix")
34 | 
35 | playwright_browser_matrix(playright_repo_name, platforms, browser_names)
36 | 
37 | 38 | Generates a list of Bazel target labels for browser dependencies. 39 | 40 | This function creates a cross-product of browser names and platforms, constructing 41 | the appropriate Bazel target labels for each combination. 42 | 43 | "@{playright_repo_name}//browsers:{browser_name}-{platform}" 44 | 45 | 46 | **PARAMETERS** 47 | 48 | 49 | | Name | Description | Default Value | 50 | | :------------- | :------------- | :------------- | 51 | | playright_repo_name | The name of the Playwright repository. | none | 52 | | platforms | A list of platform identifiers (e.g., ['mac14-arm', 'ubuntu20.04-x64]). | none | 53 | | browser_names | A list of browser names (e.g., ['chromium', 'firefox']). | none | 54 | 55 | **RETURNS** 56 | 57 | A list of browser labels to be used as the browsers attribute of the integrity_map rule. 58 | 59 | 60 | -------------------------------------------------------------------------------- /docs/repositories.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Declare runtime dependencies 4 | 5 | These are needed for local dev, and users must install them as well. 6 | See https://docs.bazel.build/versions/main/skylark/deploying.html#dependencies 7 | 8 | 9 | 10 | ## define_browsers 11 | 12 |
13 | load("@rules_playwright//playwright:repositories.bzl", "define_browsers")
14 | 
15 | define_browsers(name, browser_integrity, browsers_json, repo_mapping)
16 | 
17 | 18 | **ATTRIBUTES** 19 | 20 | 21 | | Name | Description | Type | Mandatory | Default | 22 | | :------------- | :------------- | :------------- | :------------- | :------------- | 23 | | name | A unique name for this repository. | Name | required | | 24 | | browser_integrity | A dictionary of browser names to their integrity hashes | Dictionary: String -> String | optional | `{}` | 25 | | browsers_json | - | Label | optional | `None` | 26 | | repo_mapping | In `WORKSPACE` context only: a dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.

For example, an entry `"@foo": "@bar"` declares that, for any time this repository depends on `@foo` (such as a dependency on `@foo//some:target`, it should actually resolve that dependency within globally-declared `@bar` (`@bar//some:target`).

This attribute is _not_ supported in `MODULE.bazel` context (when invoking a repository rule inside a module extension's implementation function). | Dictionary: String -> String | optional | | 27 | 28 | 29 | 30 | 31 | ## playwright_repository 32 | 33 |
34 | load("@rules_playwright//playwright:repositories.bzl", "playwright_repository")
35 | 
36 | playwright_repository(name, browsers_json, browsers_workspace_name_prefix, playwright_version,
37 |                       playwright_version_from, repo_mapping, rules_playwright_cannonical_name)
38 | 
39 | 40 | Fetch external tools needed for playwright toolchain 41 | 42 | **ATTRIBUTES** 43 | 44 | 45 | | Name | Description | Type | Mandatory | Default | 46 | | :------------- | :------------- | :------------- | :------------- | :------------- | 47 | | name | A unique name for this repository. | Name | required | | 48 | | browsers_json | The browsers.json file to use. For example https://unpkg.com/playwright-core@1.51.0/browsers.json | Label | optional | `None` | 49 | | browsers_workspace_name_prefix | The namespace prefix used when defining browser workspace repositories. | String | required | | 50 | | playwright_version | The version of playwright to install | String | optional | `""` | 51 | | playwright_version_from | The package.json file to use to find the version of playwright to install | Label | optional | `None` | 52 | | repo_mapping | In `WORKSPACE` context only: a dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.

For example, an entry `"@foo": "@bar"` declares that, for any time this repository depends on `@foo` (such as a dependency on `@foo//some:target`, it should actually resolve that dependency within globally-declared `@bar` (`@bar//some:target`).

This attribute is _not_ supported in `MODULE.bazel` context (when invoking a repository rule inside a module extension's implementation function). | Dictionary: String -> String | optional | | 53 | | rules_playwright_cannonical_name | The cannonical name given to the rules_playwright repository. See https://bazel.build/external/module | String | required | | 54 | 55 | 56 | -------------------------------------------------------------------------------- /examples/rules_js/.bazelignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /examples/rules_js/.bazelrc: -------------------------------------------------------------------------------- 1 | startup --host_jvm_args=-DBAZEL_TRACK_SOURCE_DIRECTORIES=1 2 | common --enable_bzlmod 3 | common --@mrmeku_rules_playwright//:macos_version=14 4 | -------------------------------------------------------------------------------- /examples/rules_js/.bazelversion: -------------------------------------------------------------------------------- 1 | 8.1.1 2 | -------------------------------------------------------------------------------- /examples/rules_js/.gitignore: -------------------------------------------------------------------------------- 1 | playwright-report 2 | test-results -------------------------------------------------------------------------------- /examples/rules_js/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@mrmeku_rules_playwright//playwright:defs.bzl", "playwright_browser_matrix", "playwright_integrity_map") 2 | load("@npm//:@playwright/test/package_json.bzl", playwright_bin = "bin") 3 | load("@npm//:defs.bzl", "npm_link_all_packages") 4 | load("@npm//:http-server/package_json.bzl", http_server_bin = "bin") 5 | 6 | npm_link_all_packages(name = "node_modules") 7 | 8 | filegroup( 9 | name = "todomvc_files", 10 | srcs = ["todomvc"], 11 | visibility = ["//visibility:public"], 12 | ) 13 | 14 | http_server_bin.http_server_binary( 15 | name = "test-server", 16 | copy_data_to_bin = False, 17 | data = [ 18 | "//:todomvc_files", 19 | ], 20 | fixed_args = [ 21 | "$(rootpath //:todomvc_files)", 22 | "-p 1234", 23 | ], 24 | ) 25 | 26 | DATA = glob(["tests/**/*"]) + [ 27 | "package.json", 28 | "playwright.config.ts", 29 | "//:node_modules/@playwright/test", 30 | ":test-server", 31 | ] 32 | 33 | ENV = { 34 | "TEST_SERVER": "$(rootpath :test-server)", 35 | "PLAYWRIGHT_BROWSERS_PATH": "$(rootpath @playwright//:chromium-headless-shell)/../", 36 | } 37 | 38 | ALL_BROWSERS = [ 39 | "@playwright//:chromium-headless-shell", 40 | "@playwright//:firefox", 41 | "@playwright//:webkit", 42 | ] 43 | 44 | playwright_bin.playwright_test( 45 | name = "test", 46 | args = [ 47 | "test", 48 | ], 49 | data = DATA + ALL_BROWSERS, 50 | env = ENV | { 51 | "CI": "1", 52 | }, 53 | tags = [ 54 | # Firefox does not run on mac with sandboxing 55 | "no-sandbox", 56 | ], 57 | ) 58 | 59 | playwright_bin.playwright_binary( 60 | name = "ui", 61 | args = [ 62 | "test", 63 | "--project=chromium", 64 | "--ui", 65 | ], 66 | data = DATA + [ 67 | "@playwright//:chromium-headless-shell", 68 | ], 69 | env = ENV, 70 | ) 71 | 72 | playwright_bin.playwright_binary( 73 | name = "update-snapshots", 74 | args = [ 75 | "test", 76 | "--update-snapshots", 77 | "--grep=\"Snapshots\"", 78 | ], 79 | data = DATA + ALL_BROWSERS, 80 | env = ENV | { 81 | "UPDATE_SNAPSHOTS": "true", 82 | }, 83 | ) 84 | 85 | # Use this target to generate an integrity map for the toolchain within MODULE.bazel 86 | playwright_integrity_map( 87 | name = "playwright_integrity_map", 88 | browsers = playwright_browser_matrix( 89 | browser_names = [ 90 | "chromium-headless-shell", 91 | "firefox", 92 | "webkit", 93 | ], 94 | platforms = [ 95 | "mac14-arm64", 96 | "ubuntu20.04-x64", 97 | ], 98 | playright_repo_name = "playwright", 99 | ), 100 | ) 101 | -------------------------------------------------------------------------------- /examples/rules_js/MODULE.bazel: -------------------------------------------------------------------------------- 1 | bazel_dep(name = "rules_playwright", version = "0.0.0", repo_name = "mrmeku_rules_playwright") 2 | local_path_override( 3 | module_name = "rules_playwright", 4 | path = "../..", 5 | ) 6 | 7 | playwright = use_extension("@mrmeku_rules_playwright//playwright:extensions.bzl", "playwright") 8 | playwright.repo( 9 | name = "playwright", 10 | browsers_json = "//:browsers.1.50.1.json", 11 | # Derived from running //:playwright_integrity_map 12 | integrity_map = { 13 | "builds/chromium/1155/chromium-headless-shell-mac-arm64.zip": "sha256-a499efe4d7e8b2dec1b654a51b524fce94078335e5c5ead91f20e8577ff534cd", 14 | "builds/chromium/1155/chromium-headless-shell-linux.zip": "sha256-3536f44d07d251389a57be819cdb2ea724c5c9b6b1eb0eddd8a37c547cc05234", 15 | "builds/firefox/1471/firefox-mac-arm64.zip": "sha256-f852c48e60b5ca99c023f0d0638b8986dc779103cde265fc2c2d13584b7dc7d6", 16 | "builds/webkit/2123/webkit-mac-14-arm64.zip": "sha256-4621d40c13d50d19679201d7cdaa1e41e031792bcc89634ea6197f1a5123e5a0", 17 | "builds/webkit/2092/webkit-ubuntu-20.04.zip": "sha256-e1d1db96e964c578958958fc22b87acf3ab53ec54245f8e07925a90c7999fb44", 18 | "builds/firefox/1471/firefox-ubuntu-20.04.zip": "sha256-10113c986a9e0452f02e39ebacfca4777ba8740eecd909bcdc81c6f6740ce79a", 19 | }, 20 | ) 21 | use_repo(playwright, "playwright") 22 | 23 | bazel_dep(name = "aspect_rules_js", version = "2.1.2") 24 | 25 | npm = use_extension("@aspect_rules_js//npm:extensions.bzl", "npm", dev_dependency = True) 26 | npm.npm_translate_lock( 27 | name = "npm", 28 | pnpm_lock = "//:pnpm-lock.yaml", 29 | verify_node_modules_ignored = "//:.bazelignore", 30 | ) 31 | use_repo(npm, "npm") 32 | 33 | pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm") 34 | 35 | # Allows developers to use the matching pnpm version, for example: 36 | # bazel run -- @pnpm --dir /home/runner/work/rules_js/rules_js install 37 | use_repo(pnpm, "pnpm") 38 | -------------------------------------------------------------------------------- /examples/rules_js/WORKSPACE.bazel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmeku/rules_playwright/1a866baba4f7ff795fc46a8fb12a18b9070ae640/examples/rules_js/WORKSPACE.bazel -------------------------------------------------------------------------------- /examples/rules_js/browsers.1.50.1.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "Do not edit this file, use utils/roll_browser.js", 3 | "browsers": [ 4 | { 5 | "name": "chromium", 6 | "revision": "1155", 7 | "installByDefault": true, 8 | "browserVersion": "133.0.6943.16" 9 | }, 10 | { 11 | "name": "chromium-tip-of-tree", 12 | "revision": "1293", 13 | "installByDefault": false, 14 | "browserVersion": "133.0.6943.0" 15 | }, 16 | { 17 | "name": "firefox", 18 | "revision": "1471", 19 | "installByDefault": true, 20 | "browserVersion": "134.0" 21 | }, 22 | { 23 | "name": "firefox-beta", 24 | "revision": "1467", 25 | "installByDefault": false, 26 | "browserVersion": "133.0b9" 27 | }, 28 | { 29 | "name": "webkit", 30 | "revision": "2123", 31 | "installByDefault": true, 32 | "revisionOverrides": { 33 | "debian11-x64": "2105", 34 | "debian11-arm64": "2105", 35 | "mac10.14": "1446", 36 | "mac10.15": "1616", 37 | "mac11": "1816", 38 | "mac11-arm64": "1816", 39 | "mac12": "2009", 40 | "mac12-arm64": "2009", 41 | "ubuntu20.04-x64": "2092", 42 | "ubuntu20.04-arm64": "2092" 43 | }, 44 | "browserVersion": "18.2" 45 | }, 46 | { 47 | "name": "ffmpeg", 48 | "revision": "1011", 49 | "installByDefault": true, 50 | "revisionOverrides": { 51 | "mac12": "1010", 52 | "mac12-arm64": "1010" 53 | } 54 | }, 55 | { 56 | "name": "winldd", 57 | "revision": "1007", 58 | "installByDefault": false 59 | }, 60 | { 61 | "name": "android", 62 | "revision": "1001", 63 | "installByDefault": false 64 | } 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /examples/rules_js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@playwright/test": "1.52.0", 4 | "@types/http-server": "^0.12.4", 5 | "@types/node": "^22.10.8", 6 | "http-server": "^14.1.1" 7 | }, 8 | "pnpm": { 9 | "onlyBuiltDependencies": [] 10 | }, 11 | "scripts": {} 12 | } -------------------------------------------------------------------------------- /examples/rules_js/playwright.config.ts: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { defineConfig, devices } from "@playwright/test"; 3 | import { join } from "node:path"; 4 | 5 | console.log(process.env.PWD, process.env.TEST_SERVER); 6 | 7 | /** 8 | * @see https://playwright.dev/docs/test-configuration 9 | */ 10 | export default defineConfig({ 11 | testDir: "./tests", 12 | 13 | snapshotDir: process.env.UPDATE_SNAPSHOTS 14 | ? join(process.env.BUILD_WORKSPACE_DIRECTORY!, "tests") 15 | : "tests", 16 | 17 | expect: { 18 | toHaveScreenshot: { 19 | animations: "disabled", 20 | caret: "hide", 21 | threshold: 0.02, 22 | }, 23 | }, 24 | 25 | /* Run tests in files in parallel */ 26 | fullyParallel: true, 27 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 28 | forbidOnly: !!process.env.CI, 29 | /* Retry on CI only */ 30 | retries: process.env.CI ? 2 : 0, 31 | /* Opt out of parallel tests on CI. */ 32 | workers: process.env.CI ? 1 : undefined, 33 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 34 | reporter: process.env.CI ? "list" : "html", 35 | // globalSetup: "./global-setup", 36 | webServer: { 37 | command: `./${process.env.TEST_SERVER}`, 38 | port: 1234, 39 | reuseExistingServer: false, 40 | }, 41 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 42 | use: { 43 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 44 | trace: "on-first-retry", 45 | }, 46 | /* Configure projects for major browsers */ 47 | projects: [ 48 | { 49 | name: "chromium", 50 | use: { ...devices["Desktop Chrome"] }, 51 | }, 52 | 53 | { 54 | name: "firefox", 55 | use: { ...devices["Desktop Firefox"] }, 56 | }, 57 | 58 | { 59 | name: "webkit", 60 | use: { ...devices["Desktop Safari"] }, 61 | }, 62 | ], 63 | }); 64 | -------------------------------------------------------------------------------- /examples/rules_js/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | devDependencies: 11 | '@playwright/test': 12 | specifier: 1.50.1 13 | version: 1.50.1 14 | '@types/http-server': 15 | specifier: ^0.12.4 16 | version: 0.12.4 17 | '@types/node': 18 | specifier: ^22.10.8 19 | version: 22.10.8 20 | http-server: 21 | specifier: ^14.1.1 22 | version: 14.1.1 23 | 24 | packages: 25 | 26 | '@playwright/test@1.50.1': 27 | resolution: {integrity: sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==} 28 | engines: {node: '>=18'} 29 | hasBin: true 30 | 31 | '@types/connect@3.4.38': 32 | resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} 33 | 34 | '@types/http-server@0.12.4': 35 | resolution: {integrity: sha512-vsn4pvP2oRFALLuM5Rca6qUmSPG7u0VNjOuqvL57l3bKldQRWdUZPeSiARhzagDxgfNCHn/o8WlWk4KinBauUg==} 36 | 37 | '@types/node@22.10.8': 38 | resolution: {integrity: sha512-rk+QvAEGsbX/ZPiiyel6hJHNUS9cnSbPWVaZLvE+Er3tLqQFzWMz9JOfWW7XUmKvRPfxJfbl3qYWve+RGXncFw==} 39 | 40 | ansi-styles@4.3.0: 41 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 42 | engines: {node: '>=8'} 43 | 44 | async@2.6.4: 45 | resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} 46 | 47 | basic-auth@2.0.1: 48 | resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} 49 | engines: {node: '>= 0.8'} 50 | 51 | call-bind-apply-helpers@1.0.2: 52 | resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} 53 | engines: {node: '>= 0.4'} 54 | 55 | call-bound@1.0.3: 56 | resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} 57 | engines: {node: '>= 0.4'} 58 | 59 | chalk@4.1.2: 60 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 61 | engines: {node: '>=10'} 62 | 63 | color-convert@2.0.1: 64 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 65 | engines: {node: '>=7.0.0'} 66 | 67 | color-name@1.1.4: 68 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 69 | 70 | corser@2.0.1: 71 | resolution: {integrity: sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==} 72 | engines: {node: '>= 0.4.0'} 73 | 74 | debug@3.2.7: 75 | resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} 76 | peerDependencies: 77 | supports-color: '*' 78 | peerDependenciesMeta: 79 | supports-color: 80 | optional: true 81 | 82 | dunder-proto@1.0.1: 83 | resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} 84 | engines: {node: '>= 0.4'} 85 | 86 | es-define-property@1.0.1: 87 | resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} 88 | engines: {node: '>= 0.4'} 89 | 90 | es-errors@1.3.0: 91 | resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} 92 | engines: {node: '>= 0.4'} 93 | 94 | es-object-atoms@1.1.1: 95 | resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} 96 | engines: {node: '>= 0.4'} 97 | 98 | eventemitter3@4.0.7: 99 | resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} 100 | 101 | follow-redirects@1.15.9: 102 | resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} 103 | engines: {node: '>=4.0'} 104 | peerDependencies: 105 | debug: '*' 106 | peerDependenciesMeta: 107 | debug: 108 | optional: true 109 | 110 | fsevents@2.3.2: 111 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 112 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 113 | os: [darwin] 114 | 115 | function-bind@1.1.2: 116 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 117 | 118 | get-intrinsic@1.2.7: 119 | resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} 120 | engines: {node: '>= 0.4'} 121 | 122 | get-proto@1.0.1: 123 | resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} 124 | engines: {node: '>= 0.4'} 125 | 126 | gopd@1.2.0: 127 | resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} 128 | engines: {node: '>= 0.4'} 129 | 130 | has-flag@4.0.0: 131 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 132 | engines: {node: '>=8'} 133 | 134 | has-symbols@1.1.0: 135 | resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} 136 | engines: {node: '>= 0.4'} 137 | 138 | hasown@2.0.2: 139 | resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 140 | engines: {node: '>= 0.4'} 141 | 142 | he@1.2.0: 143 | resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} 144 | hasBin: true 145 | 146 | html-encoding-sniffer@3.0.0: 147 | resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} 148 | engines: {node: '>=12'} 149 | 150 | http-proxy@1.18.1: 151 | resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} 152 | engines: {node: '>=8.0.0'} 153 | 154 | http-server@14.1.1: 155 | resolution: {integrity: sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==} 156 | engines: {node: '>=12'} 157 | hasBin: true 158 | 159 | iconv-lite@0.6.3: 160 | resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} 161 | engines: {node: '>=0.10.0'} 162 | 163 | lodash@4.17.21: 164 | resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 165 | 166 | math-intrinsics@1.1.0: 167 | resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} 168 | engines: {node: '>= 0.4'} 169 | 170 | mime@1.6.0: 171 | resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} 172 | engines: {node: '>=4'} 173 | hasBin: true 174 | 175 | minimist@1.2.8: 176 | resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 177 | 178 | mkdirp@0.5.6: 179 | resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} 180 | hasBin: true 181 | 182 | ms@2.1.3: 183 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 184 | 185 | object-inspect@1.13.4: 186 | resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} 187 | engines: {node: '>= 0.4'} 188 | 189 | opener@1.5.2: 190 | resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} 191 | hasBin: true 192 | 193 | playwright-core@1.50.1: 194 | resolution: {integrity: sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==} 195 | engines: {node: '>=18'} 196 | hasBin: true 197 | 198 | playwright@1.50.1: 199 | resolution: {integrity: sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==} 200 | engines: {node: '>=18'} 201 | hasBin: true 202 | 203 | portfinder@1.0.32: 204 | resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==} 205 | engines: {node: '>= 0.12.0'} 206 | 207 | qs@6.14.0: 208 | resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} 209 | engines: {node: '>=0.6'} 210 | 211 | requires-port@1.0.0: 212 | resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} 213 | 214 | safe-buffer@5.1.2: 215 | resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} 216 | 217 | safer-buffer@2.1.2: 218 | resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 219 | 220 | secure-compare@3.0.1: 221 | resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==} 222 | 223 | side-channel-list@1.0.0: 224 | resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} 225 | engines: {node: '>= 0.4'} 226 | 227 | side-channel-map@1.0.1: 228 | resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} 229 | engines: {node: '>= 0.4'} 230 | 231 | side-channel-weakmap@1.0.2: 232 | resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} 233 | engines: {node: '>= 0.4'} 234 | 235 | side-channel@1.1.0: 236 | resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} 237 | engines: {node: '>= 0.4'} 238 | 239 | supports-color@7.2.0: 240 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 241 | engines: {node: '>=8'} 242 | 243 | undici-types@6.20.0: 244 | resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} 245 | 246 | union@0.5.0: 247 | resolution: {integrity: sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==} 248 | engines: {node: '>= 0.8.0'} 249 | 250 | url-join@4.0.1: 251 | resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} 252 | 253 | whatwg-encoding@2.0.0: 254 | resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} 255 | engines: {node: '>=12'} 256 | 257 | snapshots: 258 | 259 | '@playwright/test@1.50.1': 260 | dependencies: 261 | playwright: 1.50.1 262 | 263 | '@types/connect@3.4.38': 264 | dependencies: 265 | '@types/node': 22.10.8 266 | 267 | '@types/http-server@0.12.4': 268 | dependencies: 269 | '@types/connect': 3.4.38 270 | 271 | '@types/node@22.10.8': 272 | dependencies: 273 | undici-types: 6.20.0 274 | 275 | ansi-styles@4.3.0: 276 | dependencies: 277 | color-convert: 2.0.1 278 | 279 | async@2.6.4: 280 | dependencies: 281 | lodash: 4.17.21 282 | 283 | basic-auth@2.0.1: 284 | dependencies: 285 | safe-buffer: 5.1.2 286 | 287 | call-bind-apply-helpers@1.0.2: 288 | dependencies: 289 | es-errors: 1.3.0 290 | function-bind: 1.1.2 291 | 292 | call-bound@1.0.3: 293 | dependencies: 294 | call-bind-apply-helpers: 1.0.2 295 | get-intrinsic: 1.2.7 296 | 297 | chalk@4.1.2: 298 | dependencies: 299 | ansi-styles: 4.3.0 300 | supports-color: 7.2.0 301 | 302 | color-convert@2.0.1: 303 | dependencies: 304 | color-name: 1.1.4 305 | 306 | color-name@1.1.4: {} 307 | 308 | corser@2.0.1: {} 309 | 310 | debug@3.2.7: 311 | dependencies: 312 | ms: 2.1.3 313 | 314 | dunder-proto@1.0.1: 315 | dependencies: 316 | call-bind-apply-helpers: 1.0.2 317 | es-errors: 1.3.0 318 | gopd: 1.2.0 319 | 320 | es-define-property@1.0.1: {} 321 | 322 | es-errors@1.3.0: {} 323 | 324 | es-object-atoms@1.1.1: 325 | dependencies: 326 | es-errors: 1.3.0 327 | 328 | eventemitter3@4.0.7: {} 329 | 330 | follow-redirects@1.15.9: {} 331 | 332 | fsevents@2.3.2: 333 | optional: true 334 | 335 | function-bind@1.1.2: {} 336 | 337 | get-intrinsic@1.2.7: 338 | dependencies: 339 | call-bind-apply-helpers: 1.0.2 340 | es-define-property: 1.0.1 341 | es-errors: 1.3.0 342 | es-object-atoms: 1.1.1 343 | function-bind: 1.1.2 344 | get-proto: 1.0.1 345 | gopd: 1.2.0 346 | has-symbols: 1.1.0 347 | hasown: 2.0.2 348 | math-intrinsics: 1.1.0 349 | 350 | get-proto@1.0.1: 351 | dependencies: 352 | dunder-proto: 1.0.1 353 | es-object-atoms: 1.1.1 354 | 355 | gopd@1.2.0: {} 356 | 357 | has-flag@4.0.0: {} 358 | 359 | has-symbols@1.1.0: {} 360 | 361 | hasown@2.0.2: 362 | dependencies: 363 | function-bind: 1.1.2 364 | 365 | he@1.2.0: {} 366 | 367 | html-encoding-sniffer@3.0.0: 368 | dependencies: 369 | whatwg-encoding: 2.0.0 370 | 371 | http-proxy@1.18.1: 372 | dependencies: 373 | eventemitter3: 4.0.7 374 | follow-redirects: 1.15.9 375 | requires-port: 1.0.0 376 | transitivePeerDependencies: 377 | - debug 378 | 379 | http-server@14.1.1: 380 | dependencies: 381 | basic-auth: 2.0.1 382 | chalk: 4.1.2 383 | corser: 2.0.1 384 | he: 1.2.0 385 | html-encoding-sniffer: 3.0.0 386 | http-proxy: 1.18.1 387 | mime: 1.6.0 388 | minimist: 1.2.8 389 | opener: 1.5.2 390 | portfinder: 1.0.32 391 | secure-compare: 3.0.1 392 | union: 0.5.0 393 | url-join: 4.0.1 394 | transitivePeerDependencies: 395 | - debug 396 | - supports-color 397 | 398 | iconv-lite@0.6.3: 399 | dependencies: 400 | safer-buffer: 2.1.2 401 | 402 | lodash@4.17.21: {} 403 | 404 | math-intrinsics@1.1.0: {} 405 | 406 | mime@1.6.0: {} 407 | 408 | minimist@1.2.8: {} 409 | 410 | mkdirp@0.5.6: 411 | dependencies: 412 | minimist: 1.2.8 413 | 414 | ms@2.1.3: {} 415 | 416 | object-inspect@1.13.4: {} 417 | 418 | opener@1.5.2: {} 419 | 420 | playwright-core@1.50.1: {} 421 | 422 | playwright@1.50.1: 423 | dependencies: 424 | playwright-core: 1.50.1 425 | optionalDependencies: 426 | fsevents: 2.3.2 427 | 428 | portfinder@1.0.32: 429 | dependencies: 430 | async: 2.6.4 431 | debug: 3.2.7 432 | mkdirp: 0.5.6 433 | transitivePeerDependencies: 434 | - supports-color 435 | 436 | qs@6.14.0: 437 | dependencies: 438 | side-channel: 1.1.0 439 | 440 | requires-port@1.0.0: {} 441 | 442 | safe-buffer@5.1.2: {} 443 | 444 | safer-buffer@2.1.2: {} 445 | 446 | secure-compare@3.0.1: {} 447 | 448 | side-channel-list@1.0.0: 449 | dependencies: 450 | es-errors: 1.3.0 451 | object-inspect: 1.13.4 452 | 453 | side-channel-map@1.0.1: 454 | dependencies: 455 | call-bound: 1.0.3 456 | es-errors: 1.3.0 457 | get-intrinsic: 1.2.7 458 | object-inspect: 1.13.4 459 | 460 | side-channel-weakmap@1.0.2: 461 | dependencies: 462 | call-bound: 1.0.3 463 | es-errors: 1.3.0 464 | get-intrinsic: 1.2.7 465 | object-inspect: 1.13.4 466 | side-channel-map: 1.0.1 467 | 468 | side-channel@1.1.0: 469 | dependencies: 470 | es-errors: 1.3.0 471 | object-inspect: 1.13.4 472 | side-channel-list: 1.0.0 473 | side-channel-map: 1.0.1 474 | side-channel-weakmap: 1.0.2 475 | 476 | supports-color@7.2.0: 477 | dependencies: 478 | has-flag: 4.0.0 479 | 480 | undici-types@6.20.0: {} 481 | 482 | union@0.5.0: 483 | dependencies: 484 | qs: 6.14.0 485 | 486 | url-join@4.0.1: {} 487 | 488 | whatwg-encoding@2.0.0: 489 | dependencies: 490 | iconv-lite: 0.6.3 491 | -------------------------------------------------------------------------------- /examples/rules_js/tests/example.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable notice/notice */ 2 | 3 | import { test, expect } from "@playwright/test"; 4 | import type { Page } from "@playwright/test"; 5 | 6 | test.describe.configure({ mode: "parallel" }); 7 | 8 | test.beforeEach(async ({ page }) => { 9 | await page.goto("/"); 10 | }); 11 | 12 | const TODO_ITEMS = [ 13 | "buy some cheese", 14 | "feed the cat", 15 | "book a doctors appointment", 16 | ]; 17 | 18 | test.describe("New Todo", () => { 19 | test("should allow me to add todo items", async ({ page }) => { 20 | // create a new todo locator 21 | const newTodo = page.getByPlaceholder("What needs to be done?"); 22 | // Create 1st todo. 23 | await newTodo.fill(TODO_ITEMS[0]); 24 | await newTodo.press("Enter"); 25 | 26 | // Make sure the list only has one todo item. 27 | await expect(page.getByTestId("todo-title")).toHaveText([TODO_ITEMS[0]]); 28 | 29 | // Create 2nd todo. 30 | await newTodo.fill(TODO_ITEMS[1]); 31 | await newTodo.press("Enter"); 32 | 33 | // Make sure the list now has two todo items. 34 | await expect(page.getByTestId("todo-title")).toHaveText([ 35 | TODO_ITEMS[0], 36 | TODO_ITEMS[1], 37 | ]); 38 | 39 | await checkNumberOfTodosInLocalStorage(page, 2); 40 | }); 41 | 42 | test("should clear text input field when an item is added", async ({ 43 | page, 44 | }) => { 45 | // create a new todo locator 46 | const newTodo = page.getByPlaceholder("What needs to be done?"); 47 | 48 | // Create one todo item. 49 | await newTodo.fill(TODO_ITEMS[0]); 50 | await newTodo.press("Enter"); 51 | 52 | // Check that input is empty. 53 | await expect(newTodo).toBeEmpty(); 54 | await checkNumberOfTodosInLocalStorage(page, 1); 55 | }); 56 | 57 | test("should append new items to the bottom of the list", async ({ 58 | page, 59 | }) => { 60 | // Create 3 items. 61 | await createDefaultTodos(page); 62 | 63 | // Check test using different methods. 64 | await expect(page.getByText("3 items left")).toBeVisible(); 65 | await expect(page.getByTestId("todo-count")).toHaveText("3 items left"); 66 | await expect(page.getByTestId("todo-count")).toContainText("3"); 67 | await expect(page.getByTestId("todo-count")).toHaveText(/3/); 68 | 69 | // Check all items in one call. 70 | await expect(page.getByTestId("todo-title")).toHaveText(TODO_ITEMS); 71 | await checkNumberOfTodosInLocalStorage(page, 3); 72 | }); 73 | 74 | test("should show #main and #footer when items added", async ({ page }) => { 75 | // create a new todo locator 76 | const newTodo = page.getByPlaceholder("What needs to be done?"); 77 | 78 | await newTodo.fill(TODO_ITEMS[0]); 79 | await newTodo.press("Enter"); 80 | 81 | await expect(page.locator(".main")).toBeVisible(); 82 | await expect(page.locator(".footer")).toBeVisible(); 83 | await checkNumberOfTodosInLocalStorage(page, 1); 84 | }); 85 | }); 86 | 87 | test.describe("Mark all as completed", () => { 88 | test.beforeEach(async ({ page }) => { 89 | await createDefaultTodos(page); 90 | await checkNumberOfTodosInLocalStorage(page, 3); 91 | }); 92 | 93 | test.afterEach(async ({ page }) => { 94 | await checkNumberOfTodosInLocalStorage(page, 3); 95 | }); 96 | 97 | test("should allow me to mark all items as completed", async ({ page }) => { 98 | // Complete all todos. 99 | await page.getByLabel("Mark all as complete").check(); 100 | 101 | // Ensure all todos have 'completed' class. 102 | await expect(page.getByTestId("todo-item")).toHaveClass([ 103 | "completed", 104 | "completed", 105 | "completed", 106 | ]); 107 | await checkNumberOfCompletedTodosInLocalStorage(page, 3); 108 | }); 109 | 110 | test("should allow me to clear the complete state of all items", async ({ 111 | page, 112 | }) => { 113 | const toggleAll = page.getByLabel("Mark all as complete"); 114 | // Check and then immediately uncheck. 115 | await toggleAll.check(); 116 | await toggleAll.uncheck(); 117 | 118 | // Should be no completed classes. 119 | await expect(page.getByTestId("todo-item")).toHaveClass(["", "", ""]); 120 | }); 121 | 122 | test("complete all checkbox should update state when items are completed / cleared", async ({ 123 | page, 124 | }) => { 125 | const toggleAll = page.getByLabel("Mark all as complete"); 126 | await toggleAll.check(); 127 | await expect(toggleAll).toBeChecked(); 128 | await checkNumberOfCompletedTodosInLocalStorage(page, 3); 129 | 130 | // Uncheck first todo. 131 | const firstTodo = page.getByTestId("todo-item").nth(0); 132 | await firstTodo.getByRole("checkbox").uncheck(); 133 | 134 | // Reuse toggleAll locator and make sure its not checked. 135 | await expect(toggleAll).not.toBeChecked(); 136 | 137 | await firstTodo.getByRole("checkbox").check(); 138 | await checkNumberOfCompletedTodosInLocalStorage(page, 3); 139 | 140 | // Assert the toggle all is checked again. 141 | await expect(toggleAll).toBeChecked(); 142 | }); 143 | }); 144 | 145 | test.describe("Item", () => { 146 | test("should allow me to mark items as complete", async ({ page }) => { 147 | // create a new todo locator 148 | const newTodo = page.getByPlaceholder("What needs to be done?"); 149 | 150 | // Create two items. 151 | for (const item of TODO_ITEMS.slice(0, 2)) { 152 | await newTodo.fill(item); 153 | await newTodo.press("Enter"); 154 | } 155 | 156 | // Check first item. 157 | const firstTodo = page.getByTestId("todo-item").nth(0); 158 | await firstTodo.getByRole("checkbox").check(); 159 | await expect(firstTodo).toHaveClass("completed"); 160 | 161 | // Check second item. 162 | const secondTodo = page.getByTestId("todo-item").nth(1); 163 | await expect(secondTodo).not.toHaveClass("completed"); 164 | await secondTodo.getByRole("checkbox").check(); 165 | 166 | // Assert completed class. 167 | await expect(firstTodo).toHaveClass("completed"); 168 | await expect(secondTodo).toHaveClass("completed"); 169 | }); 170 | 171 | test("should allow me to un-mark items as complete", async ({ page }) => { 172 | // create a new todo locator 173 | const newTodo = page.getByPlaceholder("What needs to be done?"); 174 | 175 | // Create two items. 176 | for (const item of TODO_ITEMS.slice(0, 2)) { 177 | await newTodo.fill(item); 178 | await newTodo.press("Enter"); 179 | } 180 | 181 | const firstTodo = page.getByTestId("todo-item").nth(0); 182 | const secondTodo = page.getByTestId("todo-item").nth(1); 183 | await firstTodo.getByRole("checkbox").check(); 184 | await expect(firstTodo).toHaveClass("completed"); 185 | await expect(secondTodo).not.toHaveClass("completed"); 186 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 187 | 188 | await firstTodo.getByRole("checkbox").uncheck(); 189 | await expect(firstTodo).not.toHaveClass("completed"); 190 | await expect(secondTodo).not.toHaveClass("completed"); 191 | await checkNumberOfCompletedTodosInLocalStorage(page, 0); 192 | }); 193 | 194 | test("should allow me to edit an item", async ({ page }) => { 195 | await createDefaultTodos(page); 196 | 197 | const todoItems = page.getByTestId("todo-item"); 198 | const secondTodo = todoItems.nth(1); 199 | await secondTodo.dblclick(); 200 | await expect(secondTodo.getByRole("textbox", { name: "Edit" })).toHaveValue( 201 | TODO_ITEMS[1], 202 | ); 203 | await secondTodo 204 | .getByRole("textbox", { name: "Edit" }) 205 | .fill("buy some sausages"); 206 | await secondTodo.getByRole("textbox", { name: "Edit" }).press("Enter"); 207 | 208 | // Explicitly assert the new text value. 209 | await expect(todoItems).toHaveText([ 210 | TODO_ITEMS[0], 211 | "buy some sausages", 212 | TODO_ITEMS[2], 213 | ]); 214 | await checkTodosInLocalStorage(page, "buy some sausages"); 215 | }); 216 | }); 217 | 218 | test.describe("Editing", () => { 219 | test.beforeEach(async ({ page }) => { 220 | await createDefaultTodos(page); 221 | await checkNumberOfTodosInLocalStorage(page, 3); 222 | }); 223 | 224 | test("should hide other controls when editing", async ({ page }) => { 225 | const todoItem = page.getByTestId("todo-item").nth(1); 226 | await todoItem.dblclick(); 227 | await expect(todoItem.getByRole("checkbox")).not.toBeVisible(); 228 | await expect( 229 | todoItem.locator("label", { 230 | hasText: TODO_ITEMS[1], 231 | }), 232 | ).not.toBeVisible(); 233 | await checkNumberOfTodosInLocalStorage(page, 3); 234 | }); 235 | 236 | test("should save edits on blur", async ({ page }) => { 237 | const todoItems = page.getByTestId("todo-item"); 238 | await todoItems.nth(1).dblclick(); 239 | await todoItems 240 | .nth(1) 241 | .getByRole("textbox", { name: "Edit" }) 242 | .fill("buy some sausages"); 243 | await todoItems 244 | .nth(1) 245 | .getByRole("textbox", { name: "Edit" }) 246 | .dispatchEvent("blur"); 247 | 248 | await expect(todoItems).toHaveText([ 249 | TODO_ITEMS[0], 250 | "buy some sausages", 251 | TODO_ITEMS[2], 252 | ]); 253 | await checkTodosInLocalStorage(page, "buy some sausages"); 254 | }); 255 | 256 | test("should trim entered text", async ({ page }) => { 257 | const todoItems = page.getByTestId("todo-item"); 258 | await todoItems.nth(1).dblclick(); 259 | await todoItems 260 | .nth(1) 261 | .getByRole("textbox", { name: "Edit" }) 262 | .fill(" buy some sausages "); 263 | await todoItems 264 | .nth(1) 265 | .getByRole("textbox", { name: "Edit" }) 266 | .press("Enter"); 267 | 268 | await expect(todoItems).toHaveText([ 269 | TODO_ITEMS[0], 270 | "buy some sausages", 271 | TODO_ITEMS[2], 272 | ]); 273 | await checkTodosInLocalStorage(page, "buy some sausages"); 274 | }); 275 | 276 | test("should remove the item if an empty text string was entered", async ({ 277 | page, 278 | }) => { 279 | const todoItems = page.getByTestId("todo-item"); 280 | await todoItems.nth(1).dblclick(); 281 | await todoItems.nth(1).getByRole("textbox", { name: "Edit" }).fill(""); 282 | await todoItems 283 | .nth(1) 284 | .getByRole("textbox", { name: "Edit" }) 285 | .press("Enter"); 286 | 287 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); 288 | }); 289 | 290 | test("should cancel edits on escape", async ({ page }) => { 291 | const todoItems = page.getByTestId("todo-item"); 292 | await todoItems.nth(1).dblclick(); 293 | await todoItems 294 | .nth(1) 295 | .getByRole("textbox", { name: "Edit" }) 296 | .fill("buy some sausages"); 297 | await todoItems 298 | .nth(1) 299 | .getByRole("textbox", { name: "Edit" }) 300 | .press("Escape"); 301 | await expect(todoItems).toHaveText(TODO_ITEMS); 302 | }); 303 | }); 304 | 305 | test.describe("Counter", () => { 306 | test("should display the current number of todo items", async ({ page }) => { 307 | // create a new todo locator 308 | const newTodo = page.getByPlaceholder("What needs to be done?"); 309 | 310 | await newTodo.fill(TODO_ITEMS[0]); 311 | await newTodo.press("Enter"); 312 | await expect(page.getByTestId("todo-count")).toContainText("1"); 313 | 314 | await newTodo.fill(TODO_ITEMS[1]); 315 | await newTodo.press("Enter"); 316 | await expect(page.getByTestId("todo-count")).toContainText("2"); 317 | 318 | await checkNumberOfTodosInLocalStorage(page, 2); 319 | }); 320 | }); 321 | 322 | test.describe("Clear completed button", () => { 323 | test.beforeEach(async ({ page }) => { 324 | await createDefaultTodos(page); 325 | }); 326 | 327 | test("should display the correct text", async ({ page }) => { 328 | await page.locator(".todo-list li .toggle").first().check(); 329 | await expect( 330 | page.getByRole("button", { name: "Clear completed" }), 331 | ).toBeVisible(); 332 | }); 333 | 334 | test("should remove completed items when clicked", async ({ page }) => { 335 | const todoItems = page.getByTestId("todo-item"); 336 | await todoItems.nth(1).getByRole("checkbox").check(); 337 | await page.getByRole("button", { name: "Clear completed" }).click(); 338 | await expect(todoItems).toHaveCount(2); 339 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); 340 | }); 341 | 342 | test("should be hidden when there are no items that are completed", async ({ 343 | page, 344 | }) => { 345 | await page.locator(".todo-list li .toggle").first().check(); 346 | await page.getByRole("button", { name: "Clear completed" }).click(); 347 | await expect( 348 | page.getByRole("button", { name: "Clear completed" }), 349 | ).toBeHidden(); 350 | }); 351 | }); 352 | 353 | test.describe("Persistence", () => { 354 | test("should persist its data", async ({ page }) => { 355 | // create a new todo locator 356 | const newTodo = page.getByPlaceholder("What needs to be done?"); 357 | 358 | for (const item of TODO_ITEMS.slice(0, 2)) { 359 | await newTodo.fill(item); 360 | await newTodo.press("Enter"); 361 | } 362 | 363 | const todoItems = page.getByTestId("todo-item"); 364 | await todoItems.nth(0).getByRole("checkbox").check(); 365 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); 366 | await expect(todoItems).toHaveClass(["completed", ""]); 367 | 368 | // Ensure there is 1 completed item. 369 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 370 | 371 | // Now reload. 372 | await page.reload(); 373 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); 374 | await expect(todoItems).toHaveClass(["completed", ""]); 375 | }); 376 | }); 377 | 378 | test.describe("Routing", () => { 379 | test.beforeEach(async ({ page }) => { 380 | await createDefaultTodos(page); 381 | // make sure the app had a chance to save updated todos in storage 382 | // before navigating to a new view, otherwise the items can get lost :( 383 | // in some frameworks like Durandal 384 | await checkTodosInLocalStorage(page, TODO_ITEMS[0]); 385 | }); 386 | 387 | test("should allow me to display active items", async ({ page }) => { 388 | await page.locator(".todo-list li .toggle").nth(1).check(); 389 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 390 | await page.getByRole("link", { name: "Active" }).click(); 391 | await expect(page.getByTestId("todo-item")).toHaveCount(2); 392 | await expect(page.getByTestId("todo-item")).toHaveText([ 393 | TODO_ITEMS[0], 394 | TODO_ITEMS[2], 395 | ]); 396 | }); 397 | 398 | test("should respect the back button", async ({ page }) => { 399 | await page.locator(".todo-list li .toggle").nth(1).check(); 400 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 401 | 402 | await test.step("Showing all items", async () => { 403 | await page.getByRole("link", { name: "All" }).click(); 404 | await expect(page.getByTestId("todo-item")).toHaveCount(3); 405 | }); 406 | 407 | await test.step("Showing active items", async () => { 408 | await page.getByRole("link", { name: "Active" }).click(); 409 | }); 410 | 411 | await test.step("Showing completed items", async () => { 412 | await page.getByRole("link", { name: "Completed" }).click(); 413 | }); 414 | 415 | await expect(page.getByTestId("todo-item")).toHaveCount(1); 416 | await page.goBack(); 417 | await expect(page.getByTestId("todo-item")).toHaveCount(2); 418 | await page.goBack(); 419 | await expect(page.getByTestId("todo-item")).toHaveCount(3); 420 | }); 421 | 422 | test("should allow me to display completed items", async ({ page }) => { 423 | await page.locator(".todo-list li .toggle").nth(1).check(); 424 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 425 | await page.getByRole("link", { name: "Completed" }).click(); 426 | await expect(page.getByTestId("todo-item")).toHaveCount(1); 427 | }); 428 | 429 | test("should allow me to display all items", async ({ page }) => { 430 | await page.locator(".todo-list li .toggle").nth(1).check(); 431 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 432 | await page.getByRole("link", { name: "Active" }).click(); 433 | await page.getByRole("link", { name: "Completed" }).click(); 434 | await page.getByRole("link", { name: "All" }).click(); 435 | await expect(page.getByTestId("todo-item")).toHaveCount(3); 436 | }); 437 | 438 | test("should highlight the currently applied filter", async ({ page }) => { 439 | await expect(page.getByRole("link", { name: "All" })).toHaveClass( 440 | "selected", 441 | ); 442 | await page.getByRole("link", { name: "Active" }).click(); 443 | // Page change - active items. 444 | await expect(page.getByRole("link", { name: "Active" })).toHaveClass( 445 | "selected", 446 | ); 447 | await page.getByRole("link", { name: "Completed" }).click(); 448 | // Page change - completed items. 449 | await expect(page.getByRole("link", { name: "Completed" })).toHaveClass( 450 | "selected", 451 | ); 452 | }); 453 | }); 454 | 455 | async function createDefaultTodos(page) { 456 | // create a new todo locator 457 | const newTodo = page.getByPlaceholder("What needs to be done?"); 458 | 459 | for (const item of TODO_ITEMS) { 460 | await newTodo.fill(item); 461 | await newTodo.press("Enter"); 462 | } 463 | } 464 | 465 | async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { 466 | return await page.waitForFunction((e) => { 467 | return JSON.parse(localStorage["react-todos"]).length === e; 468 | }, expected); 469 | } 470 | 471 | async function checkNumberOfCompletedTodosInLocalStorage( 472 | page: Page, 473 | expected: number, 474 | ) { 475 | return await page.waitForFunction((e) => { 476 | return ( 477 | JSON.parse(localStorage["react-todos"]).filter( 478 | (todo: any) => todo.completed, 479 | ).length === e 480 | ); 481 | }, expected); 482 | } 483 | 484 | async function checkTodosInLocalStorage(page: Page, title: string) { 485 | return await page.waitForFunction((t) => { 486 | return JSON.parse(localStorage["react-todos"]) 487 | .map((todo: any) => todo.title) 488 | .includes(t); 489 | }, title); 490 | } 491 | -------------------------------------------------------------------------------- /examples/rules_js/tests/snapshots.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable notice/notice */ 2 | 3 | import { test, expect } from "@playwright/test"; 4 | import type { Page } from "@playwright/test"; 5 | 6 | test.describe.configure({ mode: "parallel" }); 7 | 8 | test.beforeEach(async ({ page }) => { 9 | await page.goto("/"); 10 | }); 11 | 12 | const TODO_ITEMS = [ 13 | "buy some cheese", 14 | "feed the cat", 15 | "book a doctors appointment", 16 | ]; 17 | 18 | test.describe("Snapshots", () => { 19 | test("should look great", async ({ page }) => { 20 | await expect(page).toHaveScreenshot("loaded.png"); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /examples/rules_js/tests/snapshots.spec.ts-snapshots/loaded-chromium-darwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmeku/rules_playwright/1a866baba4f7ff795fc46a8fb12a18b9070ae640/examples/rules_js/tests/snapshots.spec.ts-snapshots/loaded-chromium-darwin.png -------------------------------------------------------------------------------- /examples/rules_js/tests/snapshots.spec.ts-snapshots/loaded-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmeku/rules_playwright/1a866baba4f7ff795fc46a8fb12a18b9070ae640/examples/rules_js/tests/snapshots.spec.ts-snapshots/loaded-chromium-linux.png -------------------------------------------------------------------------------- /examples/rules_js/tests/snapshots.spec.ts-snapshots/loaded-firefox-darwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmeku/rules_playwright/1a866baba4f7ff795fc46a8fb12a18b9070ae640/examples/rules_js/tests/snapshots.spec.ts-snapshots/loaded-firefox-darwin.png -------------------------------------------------------------------------------- /examples/rules_js/tests/snapshots.spec.ts-snapshots/loaded-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmeku/rules_playwright/1a866baba4f7ff795fc46a8fb12a18b9070ae640/examples/rules_js/tests/snapshots.spec.ts-snapshots/loaded-firefox-linux.png -------------------------------------------------------------------------------- /examples/rules_js/tests/snapshots.spec.ts-snapshots/loaded-webkit-darwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmeku/rules_playwright/1a866baba4f7ff795fc46a8fb12a18b9070ae640/examples/rules_js/tests/snapshots.spec.ts-snapshots/loaded-webkit-darwin.png -------------------------------------------------------------------------------- /examples/rules_js/tests/snapshots.spec.ts-snapshots/loaded-webkit-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmeku/rules_playwright/1a866baba4f7ff795fc46a8fb12a18b9070ae640/examples/rules_js/tests/snapshots.spec.ts-snapshots/loaded-webkit-linux.png -------------------------------------------------------------------------------- /examples/rules_js/todomvc/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: "“"; 73 | font-size: 50px; 74 | opacity: 0.15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: "”"; 82 | font-size: 50px; 83 | opacity: 0.15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, 0.04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ""; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, 0.04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, 0.6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/rules_js/todomvc/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | } 22 | 23 | body { 24 | font: 25 | 14px "Helvetica Neue", 26 | Helvetica, 27 | Arial, 28 | sans-serif; 29 | line-height: 1.4em; 30 | background: #f5f5f5; 31 | color: #4d4d4d; 32 | min-width: 230px; 33 | max-width: 550px; 34 | margin: 0 auto; 35 | -webkit-font-smoothing: antialiased; 36 | -moz-osx-font-smoothing: grayscale; 37 | font-weight: 300; 38 | } 39 | 40 | :focus { 41 | outline: 0; 42 | } 43 | 44 | .hidden { 45 | display: none; 46 | } 47 | 48 | .todoapp { 49 | background: #fff; 50 | margin: 130px 0 40px 0; 51 | position: relative; 52 | box-shadow: 53 | 0 2px 4px 0 rgba(0, 0, 0, 0.2), 54 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 55 | } 56 | 57 | .todoapp input::-webkit-input-placeholder { 58 | font-style: italic; 59 | font-weight: 300; 60 | color: #e6e6e6; 61 | } 62 | 63 | .todoapp input::-moz-placeholder { 64 | font-style: italic; 65 | font-weight: 300; 66 | color: #e6e6e6; 67 | } 68 | 69 | .todoapp input::input-placeholder { 70 | font-style: italic; 71 | font-weight: 300; 72 | color: #e6e6e6; 73 | } 74 | 75 | .todoapp h1 { 76 | position: absolute; 77 | top: -155px; 78 | width: 100%; 79 | font-size: 100px; 80 | font-weight: 100; 81 | text-align: center; 82 | color: rgba(175, 47, 47, 0.15); 83 | -webkit-text-rendering: optimizeLegibility; 84 | -moz-text-rendering: optimizeLegibility; 85 | text-rendering: optimizeLegibility; 86 | } 87 | 88 | .new-todo, 89 | .edit { 90 | position: relative; 91 | margin: 0; 92 | width: 100%; 93 | font-size: 24px; 94 | font-family: inherit; 95 | font-weight: inherit; 96 | line-height: 1.4em; 97 | border: 0; 98 | color: inherit; 99 | padding: 6px; 100 | border: 1px solid #999; 101 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 102 | box-sizing: border-box; 103 | -webkit-font-smoothing: antialiased; 104 | -moz-osx-font-smoothing: grayscale; 105 | } 106 | 107 | .new-todo { 108 | padding: 16px 16px 16px 60px; 109 | border: none; 110 | background: rgba(0, 0, 0, 0.003); 111 | box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03); 112 | } 113 | 114 | .main { 115 | position: relative; 116 | z-index: 2; 117 | border-top: 1px solid #e6e6e6; 118 | } 119 | 120 | .toggle-all { 121 | width: 1px; 122 | height: 1px; 123 | border: none; /* Mobile Safari */ 124 | opacity: 0; 125 | position: absolute; 126 | right: 100%; 127 | bottom: 100%; 128 | } 129 | 130 | .toggle-all + label { 131 | width: 60px; 132 | height: 34px; 133 | font-size: 0; 134 | position: absolute; 135 | top: -52px; 136 | left: -13px; 137 | -webkit-transform: rotate(90deg); 138 | transform: rotate(90deg); 139 | } 140 | 141 | .toggle-all + label:before { 142 | content: "❯"; 143 | font-size: 22px; 144 | color: #e6e6e6; 145 | padding: 10px 27px 10px 27px; 146 | } 147 | 148 | .toggle-all:checked + label:before { 149 | color: #737373; 150 | } 151 | 152 | .todo-list { 153 | margin: 0; 154 | padding: 0; 155 | list-style: none; 156 | } 157 | 158 | .todo-list li { 159 | position: relative; 160 | font-size: 24px; 161 | border-bottom: 1px solid #ededed; 162 | } 163 | 164 | .todo-list li:last-child { 165 | border-bottom: none; 166 | } 167 | 168 | .todo-list li.editing { 169 | border-bottom: none; 170 | padding: 0; 171 | } 172 | 173 | .todo-list li.editing .edit { 174 | display: block; 175 | width: 506px; 176 | padding: 12px 16px; 177 | margin: 0 0 0 43px; 178 | } 179 | 180 | .todo-list li.editing .view { 181 | display: none; 182 | } 183 | 184 | .todo-list li .toggle { 185 | text-align: center; 186 | width: 40px; 187 | /* auto, since non-WebKit browsers doesn't support input styling */ 188 | height: auto; 189 | position: absolute; 190 | top: 0; 191 | bottom: 0; 192 | margin: auto 0; 193 | border: none; /* Mobile Safari */ 194 | -webkit-appearance: none; 195 | appearance: none; 196 | } 197 | 198 | .todo-list li .toggle { 199 | opacity: 0; 200 | } 201 | 202 | .todo-list li .toggle + label { 203 | /* 204 | Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433 205 | IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/ 206 | */ 207 | background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E"); 208 | background-repeat: no-repeat; 209 | background-position: center left; 210 | } 211 | 212 | .todo-list li .toggle:checked + label { 213 | background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E"); 214 | } 215 | 216 | .todo-list li label { 217 | word-break: break-all; 218 | padding: 15px 15px 15px 60px; 219 | display: block; 220 | line-height: 1.2; 221 | transition: color 0.4s; 222 | } 223 | 224 | .todo-list li.completed label { 225 | color: #d9d9d9; 226 | text-decoration: line-through; 227 | } 228 | 229 | .todo-list li .destroy { 230 | display: none; 231 | position: absolute; 232 | top: 0; 233 | right: 10px; 234 | bottom: 0; 235 | width: 40px; 236 | height: 40px; 237 | margin: auto 0; 238 | font-size: 30px; 239 | color: #cc9a9a; 240 | margin-bottom: 11px; 241 | transition: color 0.2s ease-out; 242 | } 243 | 244 | .todo-list li .destroy:hover { 245 | color: #af5b5e; 246 | } 247 | 248 | .todo-list li .destroy:after { 249 | content: "×"; 250 | } 251 | 252 | .todo-list li:hover .destroy { 253 | display: block; 254 | } 255 | 256 | .todo-list li .edit { 257 | display: none; 258 | } 259 | 260 | .todo-list li.editing:last-child { 261 | margin-bottom: -1px; 262 | } 263 | 264 | .footer { 265 | color: #777; 266 | padding: 10px 15px; 267 | height: 20px; 268 | text-align: center; 269 | border-top: 1px solid #e6e6e6; 270 | } 271 | 272 | .footer:before { 273 | content: ""; 274 | position: absolute; 275 | right: 0; 276 | bottom: 0; 277 | left: 0; 278 | height: 50px; 279 | overflow: hidden; 280 | box-shadow: 281 | 0 1px 1px rgba(0, 0, 0, 0.2), 282 | 0 8px 0 -3px #f6f6f6, 283 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 284 | 0 16px 0 -6px #f6f6f6, 285 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 286 | } 287 | 288 | .todo-count { 289 | float: left; 290 | text-align: left; 291 | } 292 | 293 | .todo-count strong { 294 | font-weight: 300; 295 | } 296 | 297 | .filters { 298 | margin: 0; 299 | padding: 0; 300 | list-style: none; 301 | position: absolute; 302 | right: 0; 303 | left: 0; 304 | } 305 | 306 | .filters li { 307 | display: inline; 308 | } 309 | 310 | .filters li a { 311 | color: inherit; 312 | margin: 3px; 313 | padding: 3px 7px; 314 | text-decoration: none; 315 | border: 1px solid transparent; 316 | border-radius: 3px; 317 | } 318 | 319 | .filters li a:hover { 320 | border-color: rgba(175, 47, 47, 0.1); 321 | } 322 | 323 | .filters li a.selected { 324 | border-color: rgba(175, 47, 47, 0.2); 325 | } 326 | 327 | .clear-completed, 328 | html .clear-completed:active { 329 | float: right; 330 | position: relative; 331 | line-height: 20px; 332 | text-decoration: none; 333 | cursor: pointer; 334 | } 335 | 336 | .clear-completed:hover { 337 | text-decoration: underline; 338 | } 339 | 340 | .info { 341 | margin: 65px auto 0; 342 | color: #bfbfbf; 343 | font-size: 10px; 344 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 345 | text-align: center; 346 | } 347 | 348 | .info p { 349 | line-height: 1; 350 | } 351 | 352 | .info a { 353 | color: inherit; 354 | text-decoration: none; 355 | font-weight: 400; 356 | } 357 | 358 | .info a:hover { 359 | text-decoration: underline; 360 | } 361 | 362 | /* 363 | Hack to remove background from Mobile Safari. 364 | Can't use it globally since it destroys checkboxes in Firefox 365 | */ 366 | @media screen and (-webkit-min-device-pixel-ratio: 0) { 367 | .toggle-all, 368 | .todo-list li .toggle { 369 | background: none; 370 | } 371 | 372 | .todo-list li .toggle { 373 | height: 40px; 374 | } 375 | } 376 | 377 | @media (max-width: 430px) { 378 | .footer { 379 | height: 50px; 380 | } 381 | 382 | .filters { 383 | bottom: 10px; 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /examples/rules_js/todomvc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React • TodoMVC 8 | 9 | 10 | 11 | 12 |
21 | This is just a demo of TodoMVC for testing, not the 22 | real TodoMVC app. 23 |
24 |
25 |
26 |
27 |

todos

28 | 29 |
30 |
31 |
32 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/rules_js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["node"] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/workspace/.bazelrc: -------------------------------------------------------------------------------- 1 | common --noenable_bzlmod --enable_workspace 2 | -------------------------------------------------------------------------------- /examples/workspace/.bazelversion: -------------------------------------------------------------------------------- 1 | 7.4.0 2 | -------------------------------------------------------------------------------- /examples/workspace/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm//:@playwright/test/package_json.bzl", playwright_bin = "bin") 2 | load("@npm//:defs.bzl", "npm_link_all_packages") 3 | 4 | npm_link_all_packages(name = "node_modules") 5 | 6 | playwright_bin.playwright_test( 7 | name = "test", 8 | args = ["test"], 9 | data = [ 10 | "playwright.config.ts", 11 | "simple.e2e.ts", 12 | "//:node_modules/@playwright/test", 13 | "@playwright//:chromium-headless-shell", 14 | ], 15 | env = { 16 | "PLAYWRIGHT_BROWSERS_PATH": "$(rootpath @playwright//:chromium-headless-shell)/../", 17 | }, 18 | tags = ["requires-network"], 19 | ) 20 | 21 | playwright_bin.playwright_test( 22 | name = "test_2", 23 | args = ["test"], 24 | data = [ 25 | "playwright.config.ts", 26 | "simple.e2e.ts", 27 | "//:node_modules/@playwright/test", 28 | "@playwright_2//:chromium-headless-shell", 29 | ], 30 | env = { 31 | "PLAYWRIGHT_BROWSERS_PATH": "$(rootpath @playwright_2//:chromium-headless-shell)/../", 32 | }, 33 | tags = ["requires-network"], 34 | ) 35 | -------------------------------------------------------------------------------- /examples/workspace/WORKSPACE.bazel: -------------------------------------------------------------------------------- 1 | workspace(name = "workspace_example") 2 | 3 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 4 | 5 | http_archive( 6 | name = "aspect_rules_js", 7 | sha256 = "d66f8abf914a0454a69181b7b17acaae56d7b0e2784cb26b40cb3273c4d836d1", 8 | strip_prefix = "rules_js-2.2.0", 9 | url = "https://github.com/aspect-build/rules_js/releases/download/v2.2.0/rules_js-v2.2.0.tar.gz", 10 | ) 11 | 12 | load("@aspect_rules_js//js:repositories.bzl", "rules_js_dependencies") 13 | 14 | rules_js_dependencies() 15 | 16 | load("@aspect_rules_js//js:toolchains.bzl", "DEFAULT_NODE_VERSION", "rules_js_register_toolchains") 17 | 18 | rules_js_register_toolchains(node_version = DEFAULT_NODE_VERSION) 19 | 20 | load("@aspect_rules_js//npm:repositories.bzl", "npm_translate_lock") 21 | 22 | npm_translate_lock( 23 | name = "npm", 24 | pnpm_lock = "//:pnpm-lock.yaml", 25 | ) 26 | 27 | load("@npm//:repositories.bzl", "npm_repositories") 28 | 29 | npm_repositories() 30 | 31 | local_repository( 32 | name = "mrmeku_rules_playwright", 33 | path = "../..", 34 | ) 35 | 36 | http_archive( 37 | name = "playwright-core-1.51.0", 38 | build_file_content = """ 39 | exports_files([ 40 | "browsers.json" 41 | ]) 42 | """, 43 | integrity = "sha256-AjT7444dRLpDAElJJjUS+1RNkElB95H2aseuQsFn0fQ=", 44 | strip_prefix = "package", 45 | url = "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.0.tgz", 46 | ) 47 | 48 | load("@mrmeku_rules_playwright//playwright:repositories.bzl", "define_browsers", "playwright_repository") 49 | 50 | define_browsers( 51 | name = "browsers_index", 52 | browsers_json = "@playwright-core-1.51.0//:browsers.json", 53 | ) 54 | 55 | load("@browsers_index//:browsers.bzl", "fetch_browsers") 56 | 57 | fetch_browsers() 58 | 59 | playwright_repository( 60 | name = "playwright", 61 | browsers_workspace_name_prefix = "browsers_index", 62 | playwright_version = "1.51.0", 63 | rules_playwright_cannonical_name = "mrmeku_rules_playwright", 64 | ) 65 | 66 | # ########################################################### 67 | # # Example loading playwright version from package.json. # 68 | # ########################################################### 69 | 70 | playwright_repository( 71 | name = "playwright_2", 72 | browsers_workspace_name_prefix = "browsers_index", 73 | playwright_version_from = "//:package.json", 74 | rules_playwright_cannonical_name = "mrmeku_rules_playwright", 75 | ) 76 | -------------------------------------------------------------------------------- /examples/workspace/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@playwright/test": "1.51.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/workspace/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "@playwright/test"; 2 | 3 | export default defineConfig({ 4 | testMatch: /.*\.e2e.ts/, 5 | }); 6 | -------------------------------------------------------------------------------- /examples/workspace/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | devDependencies: 8 | '@playwright/test': 9 | specifier: 1.51.0 10 | version: 1.51.0 11 | 12 | packages: 13 | 14 | /@playwright/test@1.51.0: 15 | resolution: {integrity: sha1-jVyEALRloL/bz5k+OQzuy5A+ptI=} 16 | engines: {node: '>=18'} 17 | hasBin: true 18 | dependencies: 19 | playwright: 1.51.0 20 | dev: true 21 | 22 | /fsevents@2.3.2: 23 | resolution: {integrity: sha1-ilJveLj99GI7cJ4Ll1xSwkwC/Ro=} 24 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 25 | os: [darwin] 26 | requiresBuild: true 27 | dev: true 28 | optional: true 29 | 30 | /playwright-core@1.51.0: 31 | resolution: {integrity: sha1-uyPqa7YpgkLQiK5elm/8+NyYJ+g=} 32 | engines: {node: '>=18'} 33 | hasBin: true 34 | dev: true 35 | 36 | /playwright@1.51.0: 37 | resolution: {integrity: sha1-m6FUSXumK8bcGZxY7hkpXrNaRwc=} 38 | engines: {node: '>=18'} 39 | hasBin: true 40 | dependencies: 41 | playwright-core: 1.51.0 42 | optionalDependencies: 43 | fsevents: 2.3.2 44 | dev: true 45 | -------------------------------------------------------------------------------- /examples/workspace/simple.e2e.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | 3 | test("basic test", async ({ page }) => { 4 | await page.goto("https://bazel.build/"); 5 | await expect(page.getByText("{ Fast, Correct } — Choose two").first()).toBeVisible(); 6 | }); 7 | -------------------------------------------------------------------------------- /playwright/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//:bzl_library.bzl", "bzl_library") 2 | 3 | exports_files( 4 | glob(["*.bzl"]), 5 | visibility = ["//docs:__pkg__"], 6 | ) 7 | 8 | bzl_library( 9 | name = "defs", 10 | srcs = ["defs.bzl"], 11 | visibility = [ 12 | "//docs:__subpackages__", 13 | ], 14 | deps = [ 15 | "//playwright/private:integrity_map", 16 | "//playwright/private:known_browsers", 17 | "//playwright/private:select_exec", 18 | "//playwright/private:unzip_browser", 19 | "//playwright/private:util", 20 | ], 21 | ) 22 | 23 | bzl_library( 24 | name = "extensions", 25 | srcs = ["extensions.bzl"], 26 | visibility = [ 27 | "//docs:__subpackages__", 28 | ], 29 | deps = [ 30 | ":repositories", 31 | "@bazel_tools//tools/build_defs/repo:cache.bzl", 32 | ], 33 | ) 34 | 35 | bzl_library( 36 | name = "repositories", 37 | srcs = ["repositories.bzl"], 38 | visibility = [ 39 | ":__subpackages__", 40 | "//docs:__subpackages__", 41 | ], 42 | deps = [ 43 | "//playwright/private:known_browsers", 44 | "//playwright/private:util", 45 | "@bazel_tools//tools/build_defs/repo:http.bzl", 46 | "@bazel_tools//tools/build_defs/repo:utils.bzl", 47 | ], 48 | ) 49 | -------------------------------------------------------------------------------- /playwright/defs.bzl: -------------------------------------------------------------------------------- 1 | "Public API re-exports" 2 | 3 | load("//playwright/private:integrity_map.bzl", _playwright_browser_matrix = "playwright_browser_matrix", _playwright_integrity_map = "playwright_integrity_map") 4 | load("//playwright/private:select_exec.bzl", _select_exec = "select_exec") 5 | load("//playwright/private:unzip_browser.bzl", _unzip_browser = "unzip_browser") 6 | 7 | LINUX_DISTROS = [ 8 | "debian11", 9 | "debian12", 10 | "ubuntu20.04", 11 | "ubuntu22.04", 12 | "ubuntu24.04", 13 | ] 14 | 15 | MACOS_VERSIONS = [ 16 | "10.13", 17 | "10.14", 18 | "10.15", 19 | "11", 20 | "12", 21 | "13", 22 | "14", 23 | "15", 24 | ] 25 | 26 | playwright_integrity_map = _playwright_integrity_map 27 | playwright_browser_matrix = _playwright_browser_matrix 28 | unzip_browser = _unzip_browser 29 | select_exec = _select_exec 30 | -------------------------------------------------------------------------------- /playwright/extensions.bzl: -------------------------------------------------------------------------------- 1 | """Extensions for bzlmod. 2 | 3 | Installs a playwright toolchain. 4 | Every module can define a toolchain version under the default name, "playwright". 5 | The latest of those versions will be selected (the rest discarded), 6 | and will always be registered by rules_playwright. 7 | 8 | Additionally, the root module can define arbitrarily many more toolchain versions under different 9 | names (the latest version will be picked for each name) and can register them as it sees fit, 10 | effectively overriding the default named toolchain due to toolchain resolution precedence. 11 | """ 12 | 13 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") 14 | load("//playwright/private:known_browsers.bzl", "KNOWN_BROWSER_INTEGRITY") 15 | load("//playwright/private:util.bzl", "get_browsers_json_path", "get_cli_path") 16 | load(":repositories.bzl", "playwright_repository") 17 | 18 | _DEFAULT_NAME = "playwright" 19 | 20 | playwright_repo = tag_class(attrs = { 21 | "name": attr.string(doc = """\ 22 | Base name for generated repositories, allowing more than one playwright toolchain to be registered. 23 | Overriding the default is only permitted in the root module. 24 | """, default = _DEFAULT_NAME), 25 | "playwright_version": attr.string(doc = "Explicit version of playwright to download browsers.json from"), 26 | "browsers_json": attr.label(doc = "Alternative to playwright_version. Skips downloading from unpkg", allow_single_file = True), 27 | "integrity_map": attr.string_dict( 28 | default = {}, 29 | doc = "Deprecated: Mapping from brower target to integrity hash", 30 | ), 31 | "integrity_path_map": attr.string_dict( 32 | default = {}, 33 | doc = "Mapping from browser path to integrity hash", 34 | ), 35 | }) 36 | 37 | def _extension_impl(module_ctx): 38 | for mod in module_ctx.modules: 39 | for repo in mod.tags.repo: 40 | name = repo.name 41 | 42 | if name != _DEFAULT_NAME and not mod.is_root: 43 | fail("""\ 44 | Only the root module may override the default name for the playwright toolchain. 45 | This prevents conflicting registrations in the global namespace of external repos. 46 | """) 47 | 48 | if not repo.playwright_version and not repo.browsers_json: 49 | fail("""\ 50 | One of playwright_version or browsers_json must be specified. 51 | """) 52 | 53 | cli = get_cli_path(module_ctx) 54 | module_ctx.watch(cli) 55 | 56 | # Step 1: use module_ctx exec to get the list of browsers to iterate over and declare with http file 57 | result = module_ctx.execute( 58 | [ 59 | cli, 60 | "http-files", 61 | "--browser-json-path", 62 | get_browsers_json_path(module_ctx, repo.playwright_version, repo.browsers_json), 63 | "--browsers-workspace-name-prefix", 64 | repo.name, 65 | ], 66 | ) 67 | if result.return_code != 0: 68 | fail("http-files command failed", result.stdout, result.stderr) 69 | 70 | for http_file_json in json.decode(result.stdout): 71 | browser_name = http_file_json["name"] 72 | path = http_file_json["path"] 73 | integrity = repo.integrity_map.get(browser_name, None) 74 | if not integrity: 75 | integrity = repo.integrity_path_map.get(path, None) 76 | if not integrity: 77 | integrity = KNOWN_BROWSER_INTEGRITY.get(path, None) 78 | 79 | http_file( 80 | name = browser_name, 81 | integrity = integrity, 82 | urls = [ 83 | "https://playwright.azureedge.net/{}".format(path), 84 | "https://playwright-akamai.azureedge.net/{}".format(path), 85 | "https://playwright-verizon.azureedge.net/{}".format(path), 86 | ], 87 | ) 88 | 89 | # Step 2: generate repository which references said http_files 90 | playwright_repository( 91 | name = name, 92 | playwright_version = repo.playwright_version, 93 | browsers_json = repo.browsers_json, 94 | browsers_workspace_name_prefix = name, 95 | # Map the apparant name of the module to the cannonical name 96 | # See https://bazel.build/external/module 97 | rules_playwright_cannonical_name = "@" + Label("rules_playwright").repo_name, 98 | ) 99 | 100 | playwright = module_extension( 101 | implementation = _extension_impl, 102 | tag_classes = {"repo": playwright_repo}, 103 | ) 104 | -------------------------------------------------------------------------------- /playwright/private/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//:bzl_library.bzl", "bzl_library") 2 | 3 | exports_files( 4 | glob(["*.bzl"]), 5 | visibility = ["//docs:__pkg__"], 6 | ) 7 | 8 | bzl_library( 9 | name = "integrity_map", 10 | srcs = ["integrity_map.bzl"], 11 | visibility = [ 12 | "//docs:__subpackages__", 13 | "//playwright:__subpackages__", 14 | ], 15 | deps = [":unzip_browser"], 16 | ) 17 | 18 | bzl_library( 19 | name = "unzip_browser", 20 | srcs = ["unzip_browser.bzl"], 21 | visibility = [ 22 | "//docs:__subpackages__", 23 | "//playwright:__subpackages__", 24 | ], 25 | ) 26 | 27 | bzl_library( 28 | name = "util", 29 | srcs = ["util.bzl"], 30 | visibility = [ 31 | "//docs:__subpackages__", 32 | "//playwright:__subpackages__", 33 | ], 34 | ) 35 | 36 | bzl_library( 37 | name = "known_browsers", 38 | srcs = ["known_browsers.bzl"], 39 | visibility = [ 40 | "//docs:__subpackages__", 41 | "//playwright:__subpackages__", 42 | ], 43 | ) 44 | 45 | bzl_library( 46 | name = "select_exec", 47 | srcs = ["select_exec.bzl"], 48 | visibility = [ 49 | "//docs:__subpackages__", 50 | "//playwright:__subpackages__", 51 | ], 52 | ) 53 | -------------------------------------------------------------------------------- /playwright/private/cli/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@crate_index//:defs.bzl", "all_crate_deps") 2 | load("//tools/release:defs.bzl", "rust_binary") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | rust_binary( 7 | name = "cli", 8 | srcs = glob([ 9 | "src/**/*.rs", 10 | ]), 11 | compile_data = [ 12 | "src/download_paths.json", 13 | ], 14 | deps = all_crate_deps( 15 | normal = True, 16 | ), 17 | ) 18 | -------------------------------------------------------------------------------- /playwright/private/cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cli" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | serde_json = { version = "1.0.137", features = [ 8 | "std", 9 | ], default-features = false } 10 | askama = { version = "0.12.1", features = [], default-features = false } 11 | serde = { version = "1.0.217", features = [ 12 | "serde_derive", 13 | ], default-features = false } 14 | xflags = { version = "0.3.2", features = [], default-features = false } 15 | zip = "2.6.0" 16 | sha2 = "0.10.8" 17 | -------------------------------------------------------------------------------- /playwright/private/cli/src/browser_targets.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use serde::Serialize; 4 | 5 | use crate::{ 6 | browsers::{BrowserData, Browsers}, 7 | download_paths::{DownloadPaths, Platform}, 8 | }; 9 | 10 | #[derive(Debug, Serialize, Clone)] 11 | pub struct BrowserTarget { 12 | pub http_file_workspace_name: String, 13 | pub http_file_path: String, 14 | pub label: String, 15 | pub output_dir: String, 16 | pub platform: Platform, 17 | pub browser: String, 18 | pub browser_name: String, 19 | } 20 | 21 | #[derive(Debug, Serialize)] 22 | pub struct HttpFile { 23 | pub name: String, 24 | pub path: String, 25 | } 26 | 27 | impl From for HttpFile { 28 | fn from(value: BrowserTarget) -> Self { 29 | HttpFile { 30 | name: value.http_file_workspace_name, 31 | path: value.http_file_path, 32 | } 33 | } 34 | } 35 | 36 | pub fn get_browser_rules( 37 | browsers_workspace_name_prefix: &str, 38 | browser_json_path: &PathBuf, 39 | ) -> std::io::Result> { 40 | let browsers_json = std::fs::read_to_string(browser_json_path)?; 41 | let browsers: Browsers = serde_json::from_str(&browsers_json)?; 42 | 43 | let download_paths_json = include_str!("download_paths.json"); 44 | let download_paths: DownloadPaths = serde_json::from_str(download_paths_json)?; 45 | 46 | let has_headles = browsers 47 | .browsers 48 | .iter() 49 | .any(|b| b.name.ends_with("-headless-shell")); 50 | 51 | let mut browser_rules: Vec = browsers 52 | .browsers 53 | .into_iter() 54 | .flat_map(|browser| { 55 | if has_headles { 56 | return vec![browser]; 57 | } 58 | // Handle headless browser variants 59 | match browser.name.as_str() { 60 | "chromium" | "chromium-tip-of-tree" => vec![ 61 | browser.clone(), 62 | BrowserData { 63 | name: format!("{}-headless-shell", browser.name), 64 | ..browser 65 | }, 66 | ], 67 | _ => vec![browser], 68 | } 69 | }) 70 | .flat_map(|browser| { 71 | let paths = download_paths.paths.get(&browser.name); 72 | if paths.is_none() { 73 | return vec![]; 74 | } 75 | let browser_rules: Vec = paths 76 | .unwrap() 77 | .paths 78 | .iter() 79 | .filter_map(|(platform, template)| { 80 | if *platform == Platform::Unknown { 81 | return None; 82 | } 83 | match ( 84 | template, 85 | serde_json::to_string(platform) 86 | .map(|name| name.trim_matches('"').to_string()), 87 | serde_json::to_string(&browser.name) 88 | .map(|name| name.trim_matches('"').to_string()), 89 | ) { 90 | (Some(template), Ok(platform_str), Ok(browser_name)) => { 91 | let has_revision_override = browser 92 | .revision_overrides 93 | .as_ref() 94 | .and_then(|overrides| overrides.get(platform)) 95 | .is_some(); 96 | 97 | let revision = browser 98 | .revision_overrides 99 | .as_ref() 100 | .and_then(|overrides| overrides.get(platform)) 101 | .unwrap_or(&browser.revision); 102 | 103 | let snake_case_browser_name = browser_name.replace("-", "_"); 104 | let browser_directory_prefix = if has_revision_override { 105 | format!( 106 | "{}_{}_{}", 107 | snake_case_browser_name, platform_str, "special" 108 | ) 109 | } else { 110 | snake_case_browser_name 111 | }; 112 | 113 | Some(BrowserTarget { 114 | http_file_workspace_name: format!( 115 | "{browsers_workspace_name_prefix}-{browser_name}-{platform_str}" 116 | ), 117 | http_file_path: template.replace("%s", revision), 118 | label: format!("{browser_name}-{platform_str}"), 119 | output_dir: format!( 120 | "{platform_str}/{}-{}", 121 | browser_directory_prefix, browser.revision 122 | ), 123 | browser_name, 124 | platform: platform.clone(), 125 | browser: browser.name.clone(), 126 | }) 127 | } 128 | _ => None, 129 | } 130 | }) 131 | .collect(); 132 | 133 | browser_rules 134 | }) 135 | .collect(); 136 | 137 | browser_rules.sort_by(|a, b| a.label.cmp(&b.label)); 138 | 139 | Ok(browser_rules) 140 | } 141 | -------------------------------------------------------------------------------- /playwright/private/cli/src/browsers.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::download_paths::Platform; 6 | 7 | #[derive(Debug, Serialize, Deserialize)] 8 | pub struct Browsers { 9 | pub browsers: Vec, 10 | } 11 | 12 | #[derive(Debug, Serialize, Deserialize, Clone)] 13 | #[serde(rename_all = "camelCase")] 14 | pub struct BrowserData { 15 | pub name: String, 16 | pub revision: String, 17 | pub install_by_default: bool, 18 | #[serde(skip_serializing_if = "Option::is_none")] 19 | pub browser_version: Option, 20 | #[serde(skip_serializing_if = "Option::is_none")] 21 | pub revision_overrides: Option>, 22 | } 23 | -------------------------------------------------------------------------------- /playwright/private/cli/src/download_paths.json: -------------------------------------------------------------------------------- 1 | { 2 | "chromium": { 3 | "": null, 4 | "ubuntu18.04-x64": null, 5 | "ubuntu20.04-x64": "builds/chromium/%s/chromium-linux.zip", 6 | "ubuntu22.04-x64": "builds/chromium/%s/chromium-linux.zip", 7 | "ubuntu24.04-x64": "builds/chromium/%s/chromium-linux.zip", 8 | "ubuntu18.04-arm64": null, 9 | "ubuntu20.04-arm64": "builds/chromium/%s/chromium-linux-arm64.zip", 10 | "ubuntu22.04-arm64": "builds/chromium/%s/chromium-linux-arm64.zip", 11 | "ubuntu24.04-arm64": "builds/chromium/%s/chromium-linux-arm64.zip", 12 | "debian11-x64": "builds/chromium/%s/chromium-linux.zip", 13 | "debian11-arm64": "builds/chromium/%s/chromium-linux-arm64.zip", 14 | "debian12-x64": "builds/chromium/%s/chromium-linux.zip", 15 | "debian12-arm64": "builds/chromium/%s/chromium-linux-arm64.zip", 16 | "mac10.13": "builds/chromium/%s/chromium-mac.zip", 17 | "mac10.14": "builds/chromium/%s/chromium-mac.zip", 18 | "mac10.15": "builds/chromium/%s/chromium-mac.zip", 19 | "mac11": "builds/chromium/%s/chromium-mac.zip", 20 | "mac11-arm64": "builds/chromium/%s/chromium-mac-arm64.zip", 21 | "mac12": "builds/chromium/%s/chromium-mac.zip", 22 | "mac12-arm64": "builds/chromium/%s/chromium-mac-arm64.zip", 23 | "mac13": "builds/chromium/%s/chromium-mac.zip", 24 | "mac13-arm64": "builds/chromium/%s/chromium-mac-arm64.zip", 25 | "mac14": "builds/chromium/%s/chromium-mac.zip", 26 | "mac14-arm64": "builds/chromium/%s/chromium-mac-arm64.zip", 27 | "mac15": "builds/chromium/%s/chromium-mac.zip", 28 | "mac15-arm64": "builds/chromium/%s/chromium-mac-arm64.zip", 29 | "win64": "builds/chromium/%s/chromium-win64.zip" 30 | }, 31 | "chromium-headless-shell": { 32 | "": null, 33 | "ubuntu18.04-x64": null, 34 | "ubuntu20.04-x64": "builds/chromium/%s/chromium-headless-shell-linux.zip", 35 | "ubuntu22.04-x64": "builds/chromium/%s/chromium-headless-shell-linux.zip", 36 | "ubuntu24.04-x64": "builds/chromium/%s/chromium-headless-shell-linux.zip", 37 | "ubuntu18.04-arm64": null, 38 | "ubuntu20.04-arm64": "builds/chromium/%s/chromium-headless-shell-linux-arm64.zip", 39 | "ubuntu22.04-arm64": "builds/chromium/%s/chromium-headless-shell-linux-arm64.zip", 40 | "ubuntu24.04-arm64": "builds/chromium/%s/chromium-headless-shell-linux-arm64.zip", 41 | "debian11-x64": "builds/chromium/%s/chromium-headless-shell-linux.zip", 42 | "debian11-arm64": "builds/chromium/%s/chromium-headless-shell-linux-arm64.zip", 43 | "debian12-x64": "builds/chromium/%s/chromium-headless-shell-linux.zip", 44 | "debian12-arm64": "builds/chromium/%s/chromium-headless-shell-linux-arm64.zip", 45 | "mac10.13": null, 46 | "mac10.14": null, 47 | "mac10.15": null, 48 | "mac11": "builds/chromium/%s/chromium-headless-shell-mac.zip", 49 | "mac11-arm64": "builds/chromium/%s/chromium-headless-shell-mac-arm64.zip", 50 | "mac12": "builds/chromium/%s/chromium-headless-shell-mac.zip", 51 | "mac12-arm64": "builds/chromium/%s/chromium-headless-shell-mac-arm64.zip", 52 | "mac13": "builds/chromium/%s/chromium-headless-shell-mac.zip", 53 | "mac13-arm64": "builds/chromium/%s/chromium-headless-shell-mac-arm64.zip", 54 | "mac14": "builds/chromium/%s/chromium-headless-shell-mac.zip", 55 | "mac14-arm64": "builds/chromium/%s/chromium-headless-shell-mac-arm64.zip", 56 | "mac15": "builds/chromium/%s/chromium-headless-shell-mac.zip", 57 | "mac15-arm64": "builds/chromium/%s/chromium-headless-shell-mac-arm64.zip", 58 | "win64": "builds/chromium/%s/chromium-headless-shell-win64.zip" 59 | }, 60 | "chromium-tip-of-tree": { 61 | "": null, 62 | "ubuntu18.04-x64": null, 63 | "ubuntu20.04-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip", 64 | "ubuntu22.04-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip", 65 | "ubuntu24.04-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip", 66 | "ubuntu18.04-arm64": null, 67 | "ubuntu20.04-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip", 68 | "ubuntu22.04-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip", 69 | "ubuntu24.04-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip", 70 | "debian11-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip", 71 | "debian11-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip", 72 | "debian12-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip", 73 | "debian12-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip", 74 | "mac10.13": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip", 75 | "mac10.14": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip", 76 | "mac10.15": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip", 77 | "mac11": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip", 78 | "mac11-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip", 79 | "mac12": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip", 80 | "mac12-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip", 81 | "mac13": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip", 82 | "mac13-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip", 83 | "mac14": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip", 84 | "mac14-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip", 85 | "mac15": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip", 86 | "mac15-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip", 87 | "win64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-win64.zip" 88 | }, 89 | "chromium-tip-of-tree-headless-shell": { 90 | "": null, 91 | "ubuntu18.04-x64": null, 92 | "ubuntu20.04-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip", 93 | "ubuntu22.04-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip", 94 | "ubuntu24.04-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip", 95 | "ubuntu18.04-arm64": null, 96 | "ubuntu20.04-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip", 97 | "ubuntu22.04-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip", 98 | "ubuntu24.04-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip", 99 | "debian11-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip", 100 | "debian11-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip", 101 | "debian12-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip", 102 | "debian12-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip", 103 | "mac10.13": null, 104 | "mac10.14": null, 105 | "mac10.15": null, 106 | "mac11": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip", 107 | "mac11-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip", 108 | "mac12": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip", 109 | "mac12-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip", 110 | "mac13": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip", 111 | "mac13-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip", 112 | "mac14": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip", 113 | "mac14-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip", 114 | "mac15": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip", 115 | "mac15-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip", 116 | "win64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-win64.zip" 117 | }, 118 | "firefox": { 119 | "": null, 120 | "ubuntu18.04-x64": null, 121 | "ubuntu20.04-x64": "builds/firefox/%s/firefox-ubuntu-20.04.zip", 122 | "ubuntu22.04-x64": "builds/firefox/%s/firefox-ubuntu-22.04.zip", 123 | "ubuntu24.04-x64": "builds/firefox/%s/firefox-ubuntu-24.04.zip", 124 | "ubuntu18.04-arm64": null, 125 | "ubuntu20.04-arm64": "builds/firefox/%s/firefox-ubuntu-20.04-arm64.zip", 126 | "ubuntu22.04-arm64": "builds/firefox/%s/firefox-ubuntu-22.04-arm64.zip", 127 | "ubuntu24.04-arm64": "builds/firefox/%s/firefox-ubuntu-24.04-arm64.zip", 128 | "debian11-x64": "builds/firefox/%s/firefox-debian-11.zip", 129 | "debian11-arm64": "builds/firefox/%s/firefox-debian-11-arm64.zip", 130 | "debian12-x64": "builds/firefox/%s/firefox-debian-12.zip", 131 | "debian12-arm64": "builds/firefox/%s/firefox-debian-12-arm64.zip", 132 | "mac10.13": "builds/firefox/%s/firefox-mac.zip", 133 | "mac10.14": "builds/firefox/%s/firefox-mac.zip", 134 | "mac10.15": "builds/firefox/%s/firefox-mac.zip", 135 | "mac11": "builds/firefox/%s/firefox-mac.zip", 136 | "mac11-arm64": "builds/firefox/%s/firefox-mac-arm64.zip", 137 | "mac12": "builds/firefox/%s/firefox-mac.zip", 138 | "mac12-arm64": "builds/firefox/%s/firefox-mac-arm64.zip", 139 | "mac13": "builds/firefox/%s/firefox-mac.zip", 140 | "mac13-arm64": "builds/firefox/%s/firefox-mac-arm64.zip", 141 | "mac14": "builds/firefox/%s/firefox-mac.zip", 142 | "mac14-arm64": "builds/firefox/%s/firefox-mac-arm64.zip", 143 | "mac15": "builds/firefox/%s/firefox-mac.zip", 144 | "mac15-arm64": "builds/firefox/%s/firefox-mac-arm64.zip", 145 | "win64": "builds/firefox/%s/firefox-win64.zip" 146 | }, 147 | "firefox-beta": { 148 | "": null, 149 | "ubuntu18.04-x64": null, 150 | "ubuntu20.04-x64": "builds/firefox-beta/%s/firefox-beta-ubuntu-20.04.zip", 151 | "ubuntu22.04-x64": "builds/firefox-beta/%s/firefox-beta-ubuntu-22.04.zip", 152 | "ubuntu24.04-x64": "builds/firefox-beta/%s/firefox-beta-ubuntu-24.04.zip", 153 | "ubuntu18.04-arm64": null, 154 | "ubuntu20.04-arm64": null, 155 | "ubuntu22.04-arm64": "builds/firefox-beta/%s/firefox-beta-ubuntu-22.04-arm64.zip", 156 | "ubuntu24.04-arm64": "builds/firefox-beta/%s/firefox-beta-ubuntu-24.04-arm64.zip", 157 | "debian11-x64": "builds/firefox-beta/%s/firefox-beta-debian-11.zip", 158 | "debian11-arm64": "builds/firefox-beta/%s/firefox-beta-debian-11-arm64.zip", 159 | "debian12-x64": "builds/firefox-beta/%s/firefox-beta-debian-12.zip", 160 | "debian12-arm64": "builds/firefox-beta/%s/firefox-beta-debian-12-arm64.zip", 161 | "mac10.13": "builds/firefox-beta/%s/firefox-beta-mac.zip", 162 | "mac10.14": "builds/firefox-beta/%s/firefox-beta-mac.zip", 163 | "mac10.15": "builds/firefox-beta/%s/firefox-beta-mac.zip", 164 | "mac11": "builds/firefox-beta/%s/firefox-beta-mac.zip", 165 | "mac11-arm64": "builds/firefox-beta/%s/firefox-beta-mac-arm64.zip", 166 | "mac12": "builds/firefox-beta/%s/firefox-beta-mac.zip", 167 | "mac12-arm64": "builds/firefox-beta/%s/firefox-beta-mac-arm64.zip", 168 | "mac13": "builds/firefox-beta/%s/firefox-beta-mac.zip", 169 | "mac13-arm64": "builds/firefox-beta/%s/firefox-beta-mac-arm64.zip", 170 | "mac14": "builds/firefox-beta/%s/firefox-beta-mac.zip", 171 | "mac14-arm64": "builds/firefox-beta/%s/firefox-beta-mac-arm64.zip", 172 | "mac15": "builds/firefox-beta/%s/firefox-beta-mac.zip", 173 | "mac15-arm64": "builds/firefox-beta/%s/firefox-beta-mac-arm64.zip", 174 | "win64": "builds/firefox-beta/%s/firefox-beta-win64.zip" 175 | }, 176 | "webkit": { 177 | "": null, 178 | "ubuntu18.04-x64": null, 179 | "ubuntu20.04-x64": "builds/webkit/%s/webkit-ubuntu-20.04.zip", 180 | "ubuntu22.04-x64": "builds/webkit/%s/webkit-ubuntu-22.04.zip", 181 | "ubuntu24.04-x64": "builds/webkit/%s/webkit-ubuntu-24.04.zip", 182 | "ubuntu18.04-arm64": null, 183 | "ubuntu20.04-arm64": "builds/webkit/%s/webkit-ubuntu-20.04-arm64.zip", 184 | "ubuntu22.04-arm64": "builds/webkit/%s/webkit-ubuntu-22.04-arm64.zip", 185 | "ubuntu24.04-arm64": "builds/webkit/%s/webkit-ubuntu-24.04-arm64.zip", 186 | "debian11-x64": "builds/webkit/%s/webkit-debian-11.zip", 187 | "debian11-arm64": "builds/webkit/%s/webkit-debian-11-arm64.zip", 188 | "debian12-x64": "builds/webkit/%s/webkit-debian-12.zip", 189 | "debian12-arm64": "builds/webkit/%s/webkit-debian-12-arm64.zip", 190 | "mac10.13": null, 191 | "mac10.14": "builds/deprecated-webkit-mac-10.14/%s/deprecated-webkit-mac-10.14.zip", 192 | "mac10.15": "builds/deprecated-webkit-mac-10.15/%s/deprecated-webkit-mac-10.15.zip", 193 | "mac11": "builds/webkit/%s/webkit-mac-11.zip", 194 | "mac11-arm64": "builds/webkit/%s/webkit-mac-11-arm64.zip", 195 | "mac12": "builds/webkit/%s/webkit-mac-12.zip", 196 | "mac12-arm64": "builds/webkit/%s/webkit-mac-12-arm64.zip", 197 | "mac13": "builds/webkit/%s/webkit-mac-13.zip", 198 | "mac13-arm64": "builds/webkit/%s/webkit-mac-13-arm64.zip", 199 | "mac14": "builds/webkit/%s/webkit-mac-14.zip", 200 | "mac14-arm64": "builds/webkit/%s/webkit-mac-14-arm64.zip", 201 | "mac15": "builds/webkit/%s/webkit-mac-15.zip", 202 | "mac15-arm64": "builds/webkit/%s/webkit-mac-15-arm64.zip", 203 | "win64": "builds/webkit/%s/webkit-win64.zip" 204 | }, 205 | "ffmpeg": { 206 | "": null, 207 | "ubuntu18.04-x64": null, 208 | "ubuntu20.04-x64": "builds/ffmpeg/%s/ffmpeg-linux.zip", 209 | "ubuntu22.04-x64": "builds/ffmpeg/%s/ffmpeg-linux.zip", 210 | "ubuntu24.04-x64": "builds/ffmpeg/%s/ffmpeg-linux.zip", 211 | "ubuntu18.04-arm64": null, 212 | "ubuntu20.04-arm64": "builds/ffmpeg/%s/ffmpeg-linux-arm64.zip", 213 | "ubuntu22.04-arm64": "builds/ffmpeg/%s/ffmpeg-linux-arm64.zip", 214 | "ubuntu24.04-arm64": "builds/ffmpeg/%s/ffmpeg-linux-arm64.zip", 215 | "debian11-x64": "builds/ffmpeg/%s/ffmpeg-linux.zip", 216 | "debian11-arm64": "builds/ffmpeg/%s/ffmpeg-linux-arm64.zip", 217 | "debian12-x64": "builds/ffmpeg/%s/ffmpeg-linux.zip", 218 | "debian12-arm64": "builds/ffmpeg/%s/ffmpeg-linux-arm64.zip", 219 | "mac10.13": "builds/ffmpeg/%s/ffmpeg-mac.zip", 220 | "mac10.14": "builds/ffmpeg/%s/ffmpeg-mac.zip", 221 | "mac10.15": "builds/ffmpeg/%s/ffmpeg-mac.zip", 222 | "mac11": "builds/ffmpeg/%s/ffmpeg-mac.zip", 223 | "mac11-arm64": "builds/ffmpeg/%s/ffmpeg-mac-arm64.zip", 224 | "mac12": "builds/ffmpeg/%s/ffmpeg-mac.zip", 225 | "mac12-arm64": "builds/ffmpeg/%s/ffmpeg-mac-arm64.zip", 226 | "mac13": "builds/ffmpeg/%s/ffmpeg-mac.zip", 227 | "mac13-arm64": "builds/ffmpeg/%s/ffmpeg-mac-arm64.zip", 228 | "mac14": "builds/ffmpeg/%s/ffmpeg-mac.zip", 229 | "mac14-arm64": "builds/ffmpeg/%s/ffmpeg-mac-arm64.zip", 230 | "mac15": "builds/ffmpeg/%s/ffmpeg-mac.zip", 231 | "mac15-arm64": "builds/ffmpeg/%s/ffmpeg-mac-arm64.zip", 232 | "win64": "builds/ffmpeg/%s/ffmpeg-win64.zip" 233 | }, 234 | "winldd": { 235 | "": null, 236 | "ubuntu18.04-x64": null, 237 | "ubuntu20.04-x64": null, 238 | "ubuntu22.04-x64": null, 239 | "ubuntu24.04-x64": null, 240 | "ubuntu18.04-arm64": null, 241 | "ubuntu20.04-arm64": null, 242 | "ubuntu22.04-arm64": null, 243 | "ubuntu24.04-arm64": null, 244 | "debian11-x64": null, 245 | "debian11-arm64": null, 246 | "debian12-x64": null, 247 | "debian12-arm64": null, 248 | "mac10.13": null, 249 | "mac10.14": null, 250 | "mac10.15": null, 251 | "mac11": null, 252 | "mac11-arm64": null, 253 | "mac12": null, 254 | "mac12-arm64": null, 255 | "mac13": null, 256 | "mac13-arm64": null, 257 | "mac14": null, 258 | "mac14-arm64": null, 259 | "mac15": null, 260 | "mac15-arm64": null, 261 | "win64": "builds/winldd/%s/winldd-win64.zip" 262 | }, 263 | "android": { 264 | "": "builds/android/%s/android.zip", 265 | "ubuntu18.04-x64": null, 266 | "ubuntu20.04-x64": "builds/android/%s/android.zip", 267 | "ubuntu22.04-x64": "builds/android/%s/android.zip", 268 | "ubuntu24.04-x64": "builds/android/%s/android.zip", 269 | "ubuntu18.04-arm64": null, 270 | "ubuntu20.04-arm64": "builds/android/%s/android.zip", 271 | "ubuntu22.04-arm64": "builds/android/%s/android.zip", 272 | "ubuntu24.04-arm64": "builds/android/%s/android.zip", 273 | "debian11-x64": "builds/android/%s/android.zip", 274 | "debian11-arm64": "builds/android/%s/android.zip", 275 | "debian12-x64": "builds/android/%s/android.zip", 276 | "debian12-arm64": "builds/android/%s/android.zip", 277 | "mac10.13": "builds/android/%s/android.zip", 278 | "mac10.14": "builds/android/%s/android.zip", 279 | "mac10.15": "builds/android/%s/android.zip", 280 | "mac11": "builds/android/%s/android.zip", 281 | "mac11-arm64": "builds/android/%s/android.zip", 282 | "mac12": "builds/android/%s/android.zip", 283 | "mac12-arm64": "builds/android/%s/android.zip", 284 | "mac13": "builds/android/%s/android.zip", 285 | "mac13-arm64": "builds/android/%s/android.zip", 286 | "mac14": "builds/android/%s/android.zip", 287 | "mac14-arm64": "builds/android/%s/android.zip", 288 | "mac15": "builds/android/%s/android.zip", 289 | "mac15-arm64": "builds/android/%s/android.zip", 290 | "win64": "builds/android/%s/android.zip" 291 | }, 292 | "bidi": {} 293 | } 294 | -------------------------------------------------------------------------------- /playwright/private/cli/src/download_paths.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | // Main structure representing the entire JSON 6 | #[derive(Debug, Serialize, Deserialize)] 7 | pub struct DownloadPaths { 8 | #[serde(flatten)] 9 | pub paths: HashMap, 10 | } 11 | 12 | #[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)] 13 | #[serde(rename_all = "kebab-case")] 14 | pub enum Platform { 15 | #[serde(rename = "ubuntu18.04-x64")] 16 | Ubuntu1804X64, 17 | #[serde(rename = "ubuntu20.04-x64")] 18 | Ubuntu2004X64, 19 | #[serde(rename = "ubuntu22.04-x64")] 20 | Ubuntu2204X64, 21 | #[serde(rename = "ubuntu24.04-x64")] 22 | Ubuntu2404X64, 23 | #[serde(rename = "ubuntu18.04-arm64")] 24 | Ubuntu1804Arm64, 25 | #[serde(rename = "ubuntu20.04-arm64")] 26 | Ubuntu2004Arm64, 27 | #[serde(rename = "ubuntu22.04-arm64")] 28 | Ubuntu2204Arm64, 29 | #[serde(rename = "ubuntu24.04-arm64")] 30 | Ubuntu2404Arm64, 31 | Debian11X64, 32 | Debian11Arm64, 33 | Debian12X64, 34 | Debian12Arm64, 35 | #[serde(rename = "mac10.13")] 36 | Mac1013, 37 | #[serde(rename = "mac10.14")] 38 | Mac1014, 39 | #[serde(rename = "mac10.15")] 40 | Mac1015, 41 | Mac11, 42 | Mac11Arm64, 43 | Mac12, 44 | Mac12Arm64, 45 | Mac13, 46 | Mac13Arm64, 47 | Mac14, 48 | Mac14Arm64, 49 | Mac15, 50 | Mac15Arm64, 51 | #[serde(rename = "", other)] 52 | Unknown, 53 | } 54 | 55 | pub trait PlatformBase { 56 | fn base_name(&self) -> &str; 57 | } 58 | 59 | impl PlatformBase for Platform { 60 | fn base_name(&self) -> &str { 61 | match self { 62 | Platform::Ubuntu1804X64 | Platform::Ubuntu1804Arm64 => "ubuntu18.04", 63 | Platform::Ubuntu2004X64 | Platform::Ubuntu2004Arm64 => "ubuntu20.04", 64 | Platform::Ubuntu2204X64 | Platform::Ubuntu2204Arm64 => "ubuntu22.04", 65 | Platform::Ubuntu2404X64 | Platform::Ubuntu2404Arm64 => "ubuntu24.04", 66 | Platform::Debian11X64 | Platform::Debian11Arm64 => "debian11", 67 | Platform::Debian12X64 | Platform::Debian12Arm64 => "debian12", 68 | Platform::Mac1013 => "mac10.13", 69 | Platform::Mac1014 => "mac10.14", 70 | Platform::Mac1015 => "mac10.15", 71 | Platform::Mac11 | Platform::Mac11Arm64 => "mac11", 72 | Platform::Mac12 | Platform::Mac12Arm64 => "mac12", 73 | Platform::Mac13 | Platform::Mac13Arm64 => "mac13", 74 | Platform::Mac14 | Platform::Mac14Arm64 => "mac14", 75 | Platform::Mac15 | Platform::Mac15Arm64 => "mac15", 76 | Platform::Unknown => "", 77 | } 78 | } 79 | } 80 | 81 | #[derive(Debug, Serialize, Deserialize)] 82 | pub struct BuildPaths { 83 | #[serde(flatten)] 84 | pub paths: HashMap>, 85 | } 86 | -------------------------------------------------------------------------------- /playwright/private/cli/src/extract_zip.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::fs::File; 3 | use std::io::BufReader; 4 | use std::path::PathBuf; 5 | use zip::ZipArchive; 6 | 7 | pub fn extract_zip(zip_path: PathBuf, output_dir: PathBuf) -> std::io::Result<()> { 8 | fs::create_dir_all(&output_dir)?; 9 | let zip_file = File::open(zip_path)?; 10 | let mut archive = 11 | ZipArchive::new(BufReader::new(zip_file)).expect("couldn't open test zip file"); 12 | archive.extract(&output_dir).unwrap(); 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /playwright/private/cli/src/integrity_map.rs: -------------------------------------------------------------------------------- 1 | use sha2::{Digest, Sha256}; 2 | use std::collections::HashMap; 3 | use std::fs; 4 | use std::fs::File; 5 | use std::io::Read; 6 | use std::path::PathBuf; 7 | 8 | pub fn integrity_map(output_path: PathBuf, browsers: Vec, silent: bool) { 9 | let integrity_map: HashMap = browsers 10 | .into_iter() 11 | .map(|browser| { 12 | ( 13 | to_integity_map_key(&browser), 14 | to_integity_map_value(&browser), 15 | ) 16 | }) 17 | .collect::>(); 18 | 19 | let map_str = serde_json::to_string_pretty(&integrity_map) 20 | .expect("Could not serialize integrity map to json"); 21 | if !silent { 22 | println!("integrity_map = {}", map_str); 23 | } 24 | 25 | fs::write(output_path, map_str).expect("Could not write file"); 26 | } 27 | 28 | fn to_integity_map_key(browser: &str) -> String { 29 | browser 30 | .split(":") 31 | .next() 32 | .unwrap_or_else(|| panic!("Could not split browser mapping {browser}")) 33 | .to_string() 34 | } 35 | 36 | fn to_integity_map_value(browser: &str) -> String { 37 | let path = browser 38 | .split(":") 39 | .nth(1) 40 | .unwrap_or_else(|| panic!("Could not split browser mapping {browser}")) 41 | .to_string(); 42 | let mut file = 43 | File::open(&path).unwrap_or_else(|_| panic!("Could not read browser archive {path}")); 44 | let mut hasher = Sha256::new(); 45 | let mut buffer = [0; 1024]; 46 | 47 | loop { 48 | let bytes_read = file 49 | .read(&mut buffer) 50 | .expect("Could not read file into buffer"); 51 | if bytes_read == 0 { 52 | break; 53 | } 54 | hasher.update(&buffer[..bytes_read]); 55 | } 56 | 57 | let hash = hasher.finalize(); 58 | format!("sha256-{:x}", hash) 59 | } 60 | -------------------------------------------------------------------------------- /playwright/private/cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | mod browser_targets; 3 | mod browsers; 4 | mod download_paths; 5 | mod extract_zip; 6 | mod integrity_map; 7 | mod platform_groups; 8 | mod templates; 9 | use browser_targets::{get_browser_rules, HttpFile}; 10 | use extract_zip::extract_zip; 11 | use integrity_map::integrity_map; 12 | 13 | mod flags { 14 | use std::path::PathBuf; 15 | 16 | xflags::xflags! { 17 | cmd rules-playwright { 18 | cmd workspace { 19 | required --browser-json-path browser_json_path: PathBuf 20 | required --browsers-workspace-name-prefix browsers_workspace_name_prefix: String 21 | required --rules-playwright-cannonical-name rules_playwright_cannonical_name: String 22 | } 23 | cmd http-files { 24 | required --browser-json-path browser_json_path: PathBuf 25 | required --browsers-workspace-name-prefix browsers_workspace_name_prefix: String 26 | } 27 | cmd unzip { 28 | required --input-path input_path: PathBuf 29 | required --output-path output_path: PathBuf 30 | } 31 | cmd integrity-map { 32 | required --output-path output_path: PathBuf 33 | optional --silent silent: bool 34 | repeated browsers: String 35 | } 36 | } 37 | 38 | } 39 | } 40 | 41 | pub fn main() -> std::io::Result<()> { 42 | let flags = flags::RulesPlaywright::from_env_or_exit(); 43 | 44 | let out_dir = PathBuf::from("./"); 45 | 46 | match flags.subcommand { 47 | flags::RulesPlaywrightCmd::Workspace(cmd) => { 48 | templates::write_workspace( 49 | &out_dir, 50 | get_browser_rules(&cmd.browsers_workspace_name_prefix, &cmd.browser_json_path)?, 51 | &cmd.rules_playwright_cannonical_name, 52 | )?; 53 | } 54 | flags::RulesPlaywrightCmd::HttpFiles(cmd) => { 55 | let http_files: Vec = 56 | get_browser_rules(&cmd.browsers_workspace_name_prefix, &cmd.browser_json_path)? 57 | .into_iter() 58 | .map(|b| b.into()) 59 | .collect(); 60 | serde_json::to_writer(std::io::stdout(), &http_files)?; 61 | } 62 | flags::RulesPlaywrightCmd::Unzip(cmd) => { 63 | extract_zip(cmd.input_path, cmd.output_path)?; 64 | } 65 | flags::RulesPlaywrightCmd::IntegrityMap(cmd) => { 66 | integrity_map(cmd.output_path, cmd.browsers, cmd.silent.unwrap_or(false)); 67 | } 68 | } 69 | 70 | Ok(()) 71 | } 72 | -------------------------------------------------------------------------------- /playwright/private/cli/src/platform_groups.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::download_paths::Platform; 4 | 5 | #[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)] 6 | pub enum PlatformGroup { 7 | LinuxX86_64, 8 | LinuxArm64, 9 | MacosX86_64, 10 | MacosArm64, 11 | } 12 | 13 | impl Into for PlatformGroup { 14 | fn into(self) -> String { 15 | match self { 16 | PlatformGroup::LinuxX86_64 => "linux_x86_64".to_string(), 17 | PlatformGroup::LinuxArm64 => "linux_arm64".to_string(), 18 | PlatformGroup::MacosX86_64 => "macos_x86_64".to_string(), 19 | PlatformGroup::MacosArm64 => "macos_arm64".to_string(), 20 | } 21 | } 22 | } 23 | 24 | impl Into for Platform { 25 | fn into(self) -> PlatformGroup { 26 | match self { 27 | // Linux x86_64 platforms 28 | Platform::Ubuntu1804X64 29 | | Platform::Ubuntu2004X64 30 | | Platform::Ubuntu2204X64 31 | | Platform::Ubuntu2404X64 32 | | Platform::Debian11X64 33 | | Platform::Debian12X64 => PlatformGroup::LinuxX86_64, 34 | 35 | // Linux ARM64 platforms 36 | Platform::Ubuntu1804Arm64 37 | | Platform::Ubuntu2004Arm64 38 | | Platform::Ubuntu2204Arm64 39 | | Platform::Ubuntu2404Arm64 40 | | Platform::Debian11Arm64 41 | | Platform::Debian12Arm64 => PlatformGroup::LinuxArm64, 42 | 43 | // macOS x86_64 platforms 44 | Platform::Mac1013 45 | | Platform::Mac1014 46 | | Platform::Mac1015 47 | | Platform::Mac11 48 | | Platform::Mac12 49 | | Platform::Mac13 50 | | Platform::Mac14 51 | | Platform::Mac15 => PlatformGroup::MacosX86_64, 52 | 53 | // macOS ARM64 platforms 54 | Platform::Mac11Arm64 55 | | Platform::Mac12Arm64 56 | | Platform::Mac13Arm64 57 | | Platform::Mac14Arm64 58 | | Platform::Mac15Arm64 => PlatformGroup::MacosArm64, 59 | 60 | Platform::Unknown => PlatformGroup::LinuxX86_64, // You might want to handle Unknown differently 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /playwright/private/cli/src/templates/aliases.rs: -------------------------------------------------------------------------------- 1 | use crate::download_paths::PlatformBase; 2 | use askama::Template; 3 | use std::{collections::HashMap, fs, io, path::Path}; 4 | 5 | use super::RootTarget; 6 | 7 | #[derive(Template)] 8 | #[template( 9 | source = r#" 10 | package(default_visibility = ["//visibility:public"]) 11 | {% for target in alias_targets %} 12 | alias( 13 | name = "{{ target.label }}", 14 | actual = select( 15 | { 16 | {%- for entry in target.src_select %} 17 | "{{ entry.key }}": "{{ entry.value }}", 18 | {%- endfor %} 19 | }, 20 | ), 21 | ) 22 | {% endfor %} 23 | "#, 24 | ext = "txt" 25 | )] 26 | struct AliasBuildFileTemplate { 27 | alias_targets: Vec, 28 | } 29 | 30 | struct AliasTarget { 31 | label: String, 32 | src_select: Vec, 33 | } 34 | 35 | struct AliasTargetSelect { 36 | key: String, 37 | value: String, 38 | } 39 | 40 | pub fn write_build_file( 41 | out_dir: &Path, 42 | root_targets: &HashMap, 43 | rules_playwright_cannonical_name: &str, 44 | ) -> io::Result<()> { 45 | let template = AliasBuildFileTemplate { 46 | alias_targets: root_targets 47 | .iter() 48 | .flat_map(|(_, root_target)| { 49 | root_target 50 | .src_select 51 | .values() 52 | .map(|platform_group_target| AliasTarget { 53 | label: format!("{}_{}", root_target.name, platform_group_target.name), 54 | src_select: platform_group_target 55 | .src_select 56 | .iter() 57 | .map(|(platform, platform_target)| AliasTargetSelect { 58 | key: format!( 59 | "@{rules_playwright_cannonical_name}//tools/platforms:{}", 60 | platform.base_name() 61 | ), 62 | value: format!("//browsers:{}", platform_target.name), 63 | }) 64 | .collect(), 65 | }) 66 | }) 67 | .collect(), 68 | }; 69 | 70 | let aliases_dir = out_dir.join("aliases"); 71 | fs::create_dir(aliases_dir)?; 72 | fs::write( 73 | out_dir.join("aliases/BUILD.bazel"), 74 | template.render().unwrap(), 75 | ) 76 | } 77 | -------------------------------------------------------------------------------- /playwright/private/cli/src/templates/browsers.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, io, path::Path}; 2 | 3 | use askama::Template; 4 | 5 | use crate::browser_targets::BrowserTarget; 6 | 7 | #[derive(Template)] 8 | #[template( 9 | source = r#" 10 | load("@{{ rules_playwright_cannonical_name }}//playwright:defs.bzl", "unzip_browser") 11 | 12 | package(default_visibility = ["//visibility:public"]) 13 | 14 | {% for target in browser_targets %} 15 | unzip_browser( 16 | name = "{{ target.label }}", 17 | browser = "@{{ target.http_file_workspace_name }}//file", 18 | output_dir = "{{ target.output_dir }}", 19 | http_file_path = "{{ target.http_file_path }}", 20 | ) 21 | {% endfor %} 22 | "#, 23 | ext = "txt" 24 | )] 25 | struct BrowsersBuildFileTemplate<'a> { 26 | browser_targets: &'a Vec, 27 | rules_playwright_cannonical_name: &'a str, 28 | } 29 | 30 | pub fn write_build_file( 31 | out_dir: &Path, 32 | browser_targets: &Vec, 33 | rules_playwright_cannonical_name: &str, 34 | ) -> io::Result<()> { 35 | let browsers_dir = out_dir.join("browsers"); 36 | fs::create_dir(browsers_dir)?; 37 | fs::write( 38 | out_dir.join("browsers/BUILD.bazel"), 39 | BrowsersBuildFileTemplate { 40 | browser_targets, 41 | rules_playwright_cannonical_name, 42 | } 43 | .render() 44 | .unwrap(), 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /playwright/private/cli/src/templates/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, io, path::Path}; 2 | 3 | use crate::{ 4 | browser_targets::BrowserTarget, download_paths::Platform, platform_groups::PlatformGroup, 5 | }; 6 | 7 | mod aliases; 8 | mod browsers; 9 | mod root; 10 | 11 | pub fn write_workspace( 12 | out_dir: &Path, 13 | browser_targets: Vec, 14 | rules_playwright_cannonical_name: &str, 15 | ) -> io::Result<()> { 16 | browsers::write_build_file(out_dir, &browser_targets, rules_playwright_cannonical_name)?; 17 | 18 | let mut root_targets: HashMap = HashMap::new(); 19 | for target in browser_targets { 20 | let root_target = root_targets 21 | .entry(target.browser) 22 | .or_insert_with(|| RootTarget { 23 | name: target.browser_name, 24 | src_select: HashMap::new(), 25 | }); 26 | 27 | let platform_group: PlatformGroup = target.platform.clone().into(); 28 | let platform_group_target = root_target 29 | .src_select 30 | .entry(platform_group.clone()) 31 | .or_insert_with(|| PlatformGroupTarget { 32 | name: platform_group.into(), 33 | src_select: HashMap::new(), 34 | }); 35 | 36 | platform_group_target.src_select.insert( 37 | target.platform, 38 | PlatformTarget { 39 | name: target.label, 40 | browser: target.http_file_workspace_name, 41 | output_dir: target.output_dir, 42 | }, 43 | ); 44 | } 45 | 46 | root::write_build_file(out_dir, &root_targets, rules_playwright_cannonical_name)?; 47 | aliases::write_build_file(out_dir, &root_targets, rules_playwright_cannonical_name) 48 | } 49 | 50 | struct RootTarget { 51 | pub name: String, 52 | pub src_select: HashMap, 53 | } 54 | 55 | struct PlatformGroupTarget { 56 | pub name: String, 57 | pub src_select: HashMap, 58 | } 59 | 60 | #[allow(dead_code)] 61 | struct PlatformTarget { 62 | pub name: String, 63 | pub browser: String, 64 | pub output_dir: String, 65 | } 66 | -------------------------------------------------------------------------------- /playwright/private/cli/src/templates/root.rs: -------------------------------------------------------------------------------- 1 | use askama::Template; 2 | use std::{collections::HashMap, fs, io, path::Path}; 3 | 4 | use super::RootTarget; 5 | 6 | #[derive(Template)] 7 | #[template( 8 | source = r#" 9 | load("@{{ rules_playwright_cannonical_name }}//playwright:defs.bzl", "select_exec") 10 | 11 | package(default_visibility = ["//visibility:public"]) 12 | {% for target in select_targets %} 13 | select_exec( 14 | name = "{{ target.label }}", 15 | src = select( 16 | { 17 | {%- for group in target.platform_groups %} 18 | "{{ group.name }}": "{{ group.alias_label }}", 19 | {%- endfor %} 20 | }, 21 | ), 22 | ) 23 | {% endfor %} 24 | "#, 25 | ext = "txt" 26 | )] 27 | struct RootBuildFileTemplate<'a> { 28 | rules_playwright_cannonical_name: &'a str, 29 | select_targets: &'a [SelectTarget], 30 | } 31 | 32 | struct SelectTarget { 33 | label: String, 34 | platform_groups: Vec, 35 | } 36 | 37 | struct SelectPlatformGroup { 38 | name: String, 39 | alias_label: String, 40 | } 41 | 42 | pub fn write_build_file( 43 | out_dir: &Path, 44 | root_targets: &HashMap, 45 | rules_playwright_cannonical_name: &str, 46 | ) -> io::Result<()> { 47 | let select_targets: Vec = root_targets 48 | .iter() 49 | .map(|(_browser, root_target)| SelectTarget { 50 | label: root_target.name.clone(), 51 | platform_groups: root_target 52 | .src_select 53 | .iter() 54 | .map(|(platform_group, platform_group_target)| { 55 | let name_label: String = platform_group.clone().into(); 56 | 57 | SelectPlatformGroup { 58 | name: format!( 59 | "@{rules_playwright_cannonical_name}//tools/platforms:{}", 60 | name_label 61 | ), 62 | alias_label: format!( 63 | "//aliases:{}_{}", 64 | root_target.name, platform_group_target.name 65 | ), 66 | } 67 | }) 68 | .collect(), 69 | }) 70 | .collect(); 71 | 72 | fs::write( 73 | out_dir.join("BUILD.bazel"), 74 | RootBuildFileTemplate { 75 | select_targets: &select_targets, 76 | rules_playwright_cannonical_name, 77 | } 78 | .render() 79 | .unwrap(), 80 | ) 81 | } 82 | -------------------------------------------------------------------------------- /playwright/private/cli/src/test/BUILD.bazel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmeku/rules_playwright/1a866baba4f7ff795fc46a8fb12a18b9070ae640/playwright/private/cli/src/test/BUILD.bazel -------------------------------------------------------------------------------- /playwright/private/cli/src/test/browsers.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "Do not edit this file, use utils/roll_browser.js", 3 | "browsers": [ 4 | { 5 | "name": "chromium", 6 | "revision": "1148", 7 | "installByDefault": true, 8 | "browserVersion": "131.0.6778.33" 9 | }, 10 | { 11 | "name": "chromium-headless-shell", 12 | "revision": "1148", 13 | "installByDefault": true, 14 | "browserVersion": "131.0.6778.33" 15 | }, 16 | { 17 | "name": "chromium-tip-of-tree", 18 | "revision": "1277", 19 | "installByDefault": false, 20 | "browserVersion": "132.0.6834.0" 21 | }, 22 | { 23 | "name": "firefox", 24 | "revision": "1466", 25 | "installByDefault": true, 26 | "browserVersion": "132.0" 27 | }, 28 | { 29 | "name": "firefox-beta", 30 | "revision": "1465", 31 | "installByDefault": false, 32 | "browserVersion": "132.0b8" 33 | }, 34 | { 35 | "name": "webkit", 36 | "revision": "2104", 37 | "installByDefault": true, 38 | "revisionOverrides": { 39 | "mac10.14": "1446", 40 | "mac10.15": "1616", 41 | "mac11": "1816", 42 | "mac11-arm64": "1816", 43 | "mac12": "2009", 44 | "mac12-arm64": "2009", 45 | "ubuntu20.04-x64": "2092", 46 | "ubuntu20.04-arm64": "2092" 47 | }, 48 | "browserVersion": "18.2" 49 | }, 50 | { 51 | "name": "ffmpeg", 52 | "revision": "1010", 53 | "installByDefault": true, 54 | "revisionOverrides": { 55 | "mac12": "1010", 56 | "mac12-arm64": "1010" 57 | } 58 | }, 59 | { 60 | "name": "android", 61 | "revision": "1001", 62 | "installByDefault": false 63 | } 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /playwright/private/integrity_map.bzl: -------------------------------------------------------------------------------- 1 | """Small rule which unzip a browser to a tree artifact using bash. 2 | """ 3 | 4 | load(":unzip_browser.bzl", "UnzippedBrowserInfo") 5 | 6 | def _playwright_integrity_map_impl(ctx): 7 | output = ctx.actions.declare_file(ctx.attr.output if ctx.attr.output else ctx.attr.name + ".json") 8 | 9 | browser_args = [] 10 | inputs = [] 11 | for browser in ctx.attr.browsers: 12 | http_file_path = browser[UnzippedBrowserInfo].http_file_path 13 | browser_archive = browser[UnzippedBrowserInfo].browser_archive 14 | 15 | inputs.append(browser_archive) 16 | browser_args.append("{}:{}".format(http_file_path, browser_archive.path)) 17 | 18 | silent_args = [] 19 | if ctx.attr.silent: 20 | silent_args = ["--silent", "true"] 21 | 22 | ctx.actions.run( 23 | inputs = inputs, 24 | outputs = [output], 25 | executable = ctx.executable._cli, 26 | arguments = ["integrity-map", "--output-path", output.path] + silent_args + browser_args, 27 | ) 28 | 29 | return [DefaultInfo(files = depset([output]))] 30 | 31 | playwright_integrity_map = rule( 32 | implementation = _playwright_integrity_map_impl, 33 | attrs = { 34 | "browsers": attr.label_list( 35 | providers = [UnzippedBrowserInfo], 36 | doc = """ 37 | A list of browser targets to generate integrity values for. 38 | 39 | These targets should usually be the result of the playwright_browser_matrix() function, 40 | which creates a cross-product of browser names and platforms. 41 | Each target must provide the UnzippedBrowserInfo provider. 42 | """, 43 | ), 44 | "output": attr.string( 45 | doc = """ 46 | The name of the output file to create containing the integrity map. 47 | 48 | This file will contain the generated integrity values for the specified browsers. 49 | Defaults to "target_name.json" 50 | """, 51 | ), 52 | "silent": attr.bool( 53 | doc = """ 54 | Whether to suppress debug information output. 55 | 56 | When set to False (default), the rule will print integrity information 57 | that users would typically copy and paste into their MODULE.bazel or WORKSPACE file. 58 | Set to True to prevent this debug output from being printed. 59 | """, 60 | ), 61 | "_cli": attr.label( 62 | default = "//tools/release:cli", 63 | allow_single_file = True, 64 | executable = True, 65 | cfg = "exec", 66 | ), 67 | }, 68 | ) 69 | 70 | def playwright_browser_matrix(playright_repo_name, platforms, browser_names): 71 | """ 72 | Generates a list of Bazel target labels for browser dependencies. 73 | 74 | This function creates a cross-product of browser names and platforms, constructing 75 | the appropriate Bazel target labels for each combination. 76 | 77 | "@{playright_repo_name}//browsers:{browser_name}-{platform}" 78 | 79 | Args: 80 | playright_repo_name: The name of the Playwright repository. 81 | platforms: A list of platform identifiers (e.g., ['mac14-arm', 'ubuntu20.04-x64]). 82 | browser_names: A list of browser names (e.g., ['chromium', 'firefox']). 83 | 84 | Returns: 85 | A list of browser labels to be used as the browsers attribute of the integrity_map rule. 86 | """ 87 | browser_labels = [] 88 | for browser in browser_names: 89 | for platform in platforms: 90 | browser_labels.append("@{}//browsers:{}-{}".format(playright_repo_name, browser, platform)) 91 | return browser_labels 92 | -------------------------------------------------------------------------------- /playwright/private/known_browsers.bzl: -------------------------------------------------------------------------------- 1 | KNOWN_BROWSER_INTEGRITY = { 2 | "builds/android/1001/android.zip": "sha256-ieX7MC1xEehgOwtZHsyCXOWrW3QDz4B4hEKkWEWsix8=", 3 | "builds/chromium-tip-of-tree/1293/chromium-tip-of-tree-headless-shell-linux-arm64.zip": "sha256-QAoB6/r1XO+Km70SXDLhZJOvbrzYY3rc0egRnv9TSVQ=", 4 | "builds/chromium-tip-of-tree/1293/chromium-tip-of-tree-headless-shell-linux.zip": "sha256-z8gApcO/992dOsX8sG//SKnqpxzpFOXoPWn0rWXAUTA=", 5 | "builds/chromium-tip-of-tree/1293/chromium-tip-of-tree-headless-shell-mac-arm64.zip": "sha256-szB66kX6kvILMsQw6IGHIWDfbEKx570rBfKAMrDz238=", 6 | "builds/chromium-tip-of-tree/1293/chromium-tip-of-tree-headless-shell-mac.zip": "sha256-OeHNIG501X+RwjSbUvT882hAJyYi2U/l0lKwIDYr8hk=", 7 | "builds/chromium-tip-of-tree/1293/chromium-tip-of-tree-linux-arm64.zip": "sha256-hS8DnLU9IOXHpWBYUOdkwbnCqs6FLOKHmNqq5oOwcGY=", 8 | "builds/chromium-tip-of-tree/1293/chromium-tip-of-tree-linux.zip": "sha256-RhHEu/iMrUMBHmmxd7Ly+AGUq1pIqERjkon+TtwIinc=", 9 | "builds/chromium-tip-of-tree/1293/chromium-tip-of-tree-mac-arm64.zip": "sha256-djncNiGeAntcWna/XjkkIBZ+8HfZL5sRqcoZUeq1FWM=", 10 | "builds/chromium-tip-of-tree/1293/chromium-tip-of-tree-mac.zip": "sha256-VAxHjul5mI9NVkgPnS6dwFD8hcc+Fe+8AmgKqvBo07M=", 11 | "builds/chromium-tip-of-tree/1304/chromium-tip-of-tree-headless-shell-linux-arm64.zip": "sha256-oPexMBA97W//sDTn16Im6/3JSfO9g9g+FV+7ZmVbFQY=", 12 | "builds/chromium-tip-of-tree/1304/chromium-tip-of-tree-headless-shell-linux.zip": "sha256-ELGnFE6iQ1d51G3lr1oxbt0dt2o8Kd3j5el4GD/5yPw=", 13 | "builds/chromium-tip-of-tree/1304/chromium-tip-of-tree-headless-shell-mac-arm64.zip": "sha256-ig5Krwf2H4EnexLjnqm1GMAd3FUd2xPgYcGqIW3AdqM=", 14 | "builds/chromium-tip-of-tree/1304/chromium-tip-of-tree-headless-shell-mac.zip": "sha256-SXE4/63V2PEgSlurDaZzGDLGi7/4G/2OoaFrDq8bsDs=", 15 | "builds/chromium-tip-of-tree/1304/chromium-tip-of-tree-linux-arm64.zip": "sha256-bWwqT/MIA4XZdAbmw1ACaGtlarPEtMUB8GWVChFxNrc=", 16 | "builds/chromium-tip-of-tree/1304/chromium-tip-of-tree-linux.zip": "sha256-7XDAbRSuVFBSZF3u9wN2+MJ2ihZ6dnxg93ZDLIC+JU0=", 17 | "builds/chromium-tip-of-tree/1304/chromium-tip-of-tree-mac-arm64.zip": "sha256-AUnkYlX7KDvjKAb3Qduz8esiOGeUxrss5Ws3Z0UzVDM=", 18 | "builds/chromium-tip-of-tree/1304/chromium-tip-of-tree-mac.zip": "sha256-worCWYUSP28FSBASjiNwZC2VSRCcKrT4NbpiyuK5ZVQ=", 19 | "builds/chromium/1155/chromium-headless-shell-linux-arm64.zip": "sha256-w1iNNo47A+TDPnN3FRnW7Ja7qQWSyNvqwPNw7hfZYf0=", 20 | "builds/chromium/1155/chromium-headless-shell-linux.zip": "sha256-NTb0TQfSUTiaV76BnNsupyTFybax6w7d2KN8VHzAUjQ=", 21 | "builds/chromium/1155/chromium-headless-shell-mac-arm64.zip": "sha256-pJnv5Nfost7BtlSlG1JPzpQHgzXlxerZHyDoV3/1NM0=", 22 | "builds/chromium/1155/chromium-headless-shell-mac.zip": "sha256-OWvK/Vy0XS7nvQGWmRpbbgLZLjLNvhCUiCvde+pVEMQ=", 23 | "builds/chromium/1155/chromium-linux-arm64.zip": "sha256-YREKFXUbFeUCgGlj8mBGgqMVy/EP5VgnYl34ed4bV08=", 24 | "builds/chromium/1155/chromium-linux.zip": "sha256-ytuE7p3Ts6XOQ1F1wuOcWFyQRXKSNYU0rPbm8vH6JI0=", 25 | "builds/chromium/1155/chromium-mac-arm64.zip": "sha256-2IF3cWSqlaaWIdL0APVNtebFJdmb8dIrj5Hft/K4/6w=", 26 | "builds/chromium/1155/chromium-mac.zip": "sha256-T6IDnwIDP/D2t5ESSKYLtCcGaJwkvZ8zDU0zhT9F4ac=", 27 | "builds/chromium/1161/chromium-headless-shell-linux-arm64.zip": "sha256-CVI7C0uk7iR8oSRaMtK16h7nd2WgoSUlCbKHTjfu/tU=", 28 | "builds/chromium/1161/chromium-headless-shell-linux.zip": "sha256-tXQwe+KExEDnfUGMyMpSGsHXzgOaXsXxPypcVm2WKQw=", 29 | "builds/chromium/1161/chromium-headless-shell-mac-arm64.zip": "sha256-bDuFwsaMSoivq+9NVhgIKcGzIajlQBtMzXu5XIaaoBQ=", 30 | "builds/chromium/1161/chromium-headless-shell-mac.zip": "sha256-9WQ8sm4mMtapWr1dR6IRvLDA1yoeUrr1kDoyE0qr9Xc=", 31 | "builds/chromium/1161/chromium-linux-arm64.zip": "sha256-/eb3uHAYYmZMbNoNKQtcglmXTNQT3QocXtqRiNjHCzc=", 32 | "builds/chromium/1161/chromium-linux.zip": "sha256-JduALM52KixcCCsRVnG4w3+O+HwtCONn0hbVbpVeJJY=", 33 | "builds/chromium/1161/chromium-mac-arm64.zip": "sha256-itWSiKFK0qdLgnk3b2sAoiJKd1N1F8YeYuNOCPIU7Ko=", 34 | "builds/chromium/1161/chromium-mac.zip": "sha256-moj7lrvonV0K9NcbFo00QdLrtD7PV5YY5cU8t5WEo7I=", 35 | "builds/deprecated-webkit-mac-10.14/1446/deprecated-webkit-mac-10.14.zip": "sha256-onN55LO1UOlcHDsFoCJwpli0Dw4W94FJK4KOP1VWUJ4=", 36 | "builds/deprecated-webkit-mac-10.15/1616/deprecated-webkit-mac-10.15.zip": "sha256-uCA1JD5urKP09FFJTAjBB9lnSEIKKe5zE6mEWic06BY=", 37 | "builds/ffmpeg/1010/ffmpeg-mac-arm64.zip": "sha256-r94UveYB7hhMUfMWtEMGgyJf4ZgtOixo21Nbqmaz2iU=", 38 | "builds/ffmpeg/1010/ffmpeg-mac.zip": "sha256-jzwvO/Ye/XXHTDzJVtZHtUqbW82tIQSa9er0Oioe05I=", 39 | "builds/ffmpeg/1011/ffmpeg-linux-arm64.zip": "sha256-JijAPwUxj/gSyMm6ryB96i3fU+gYwNyTZxSw++OvsAk=", 40 | "builds/ffmpeg/1011/ffmpeg-linux.zip": "sha256-68dPxblIMBdqPCkUrpa9i8f2qR9PM4kCMPhKFy7mHMw=", 41 | "builds/ffmpeg/1011/ffmpeg-mac-arm64.zip": "sha256-fXfrDUS1msxAZfqiR2wN8aJCzJBMNG+CBiaBjJU8Unc=", 42 | "builds/ffmpeg/1011/ffmpeg-mac.zip": "sha256-F+0Vovpg08dBgb78sr33ybsojRmyo7mJO5S2PyziYOQ=", 43 | "builds/firefox-beta/1467/firefox-beta-debian-11-arm64.zip": "sha256-NuBpJvbHhrC0R1CD5ymQeBpsZfMZWAeFH0Fi39yqOqQ=", 44 | "builds/firefox-beta/1467/firefox-beta-debian-11.zip": "sha256-nw7sxx8P9/FPOlTM0mHbF66SCSYfp0ophQ+XmZ5//2I=", 45 | "builds/firefox-beta/1467/firefox-beta-debian-12-arm64.zip": "sha256-qVs7Hy3lXdJMjCEQxtUemmX6vu1qOTc2LErdmtq5j5E=", 46 | "builds/firefox-beta/1467/firefox-beta-debian-12.zip": "sha256-2lvemglKltMLLabGxKuUC/Ur4uKC1VF9sBXUYvpriz0=", 47 | "builds/firefox-beta/1467/firefox-beta-mac-arm64.zip": "sha256-0rQtOPXfmE6jRoielqxRI4+EP4JYbWaTm9Ci+ESEEfE=", 48 | "builds/firefox-beta/1467/firefox-beta-mac.zip": "sha256-9lgRiN7JXXJ6T27x7ygwF35mG22mvb/Z8cE/Mmjz/IE=", 49 | "builds/firefox-beta/1467/firefox-beta-ubuntu-20.04.zip": "sha256-LGzzS4HEAOBCEJQLxcU418OrwfjwaizGdaSSKkhVDgc=", 50 | "builds/firefox-beta/1467/firefox-beta-ubuntu-22.04-arm64.zip": "sha256-k9+rX7RgPkXMEtTKCHVQXfyOg5/1lvTnOtlIJsy7hNg=", 51 | "builds/firefox-beta/1467/firefox-beta-ubuntu-22.04.zip": "sha256-hhiBkK2lRH9+Pc5fcILrjzJQYGu0N+TmrFvEuTuzd4s=", 52 | "builds/firefox-beta/1467/firefox-beta-ubuntu-24.04-arm64.zip": "sha256-e6mYz14pgoBtvVUPvAuzWfIfeyf0UHvgqg0og2DNEj8=", 53 | "builds/firefox-beta/1467/firefox-beta-ubuntu-24.04.zip": "sha256-LewMQFFCDzMLuczskcjIS3FUzj78OzT2wNa2WTux4Os=", 54 | "builds/firefox-beta/1471/firefox-beta-debian-11-arm64.zip": "sha256-0MDZfCYFJFoYLrFjzulLShUZu1wK7XctIxrBQYSDLXg=", 55 | "builds/firefox-beta/1471/firefox-beta-debian-11.zip": "sha256-HVrELz/aZIHn+yWQV2sVQOZC7skKs3/aavd1XAiPwzw=", 56 | "builds/firefox-beta/1471/firefox-beta-debian-12-arm64.zip": "sha256-rJUB8KB5POWRWhIqJJ1Z6fCIvtHo4XZzfDOrgfHLKUI=", 57 | "builds/firefox-beta/1471/firefox-beta-debian-12.zip": "sha256-bOEMUfvOHuPmaPwqWKl0AKlwRkMXvZkTNHx2f0Tdums=", 58 | "builds/firefox-beta/1471/firefox-beta-mac-arm64.zip": "sha256-2G58NZ8tu3FO9dLLzGuvOcQx5O+ORNTh3JNZezh/uK0=", 59 | "builds/firefox-beta/1471/firefox-beta-mac.zip": "sha256-fpXxS1XX9ExE9WjPU5Ds7z6/rGJ8IbM3Nu6auhofcyU=", 60 | "builds/firefox-beta/1471/firefox-beta-ubuntu-20.04.zip": "sha256-3Q1+XRaG0xE5bjMNEqa7btMQFJbK2Y6zQIr8zFcdGOA=", 61 | "builds/firefox-beta/1471/firefox-beta-ubuntu-22.04-arm64.zip": "sha256-InFsEGLAZbiSpD9XzL1m3mHTfZZCm7rl90Gi2+FqUjg=", 62 | "builds/firefox-beta/1471/firefox-beta-ubuntu-22.04.zip": "sha256-QUZtRibm4BvG13swGnGf9HJTEd50wc7C6r8I1YsyVx4=", 63 | "builds/firefox-beta/1471/firefox-beta-ubuntu-24.04-arm64.zip": "sha256-lJZM152VaroKJoVe4XL2xTogBYmz7sEuftuBEKhHc6I=", 64 | "builds/firefox-beta/1471/firefox-beta-ubuntu-24.04.zip": "sha256-kncJC0HSGL+6NEJh4puCLb8EmYrdu/AYkEvUAbrsVZ4=", 65 | "builds/firefox/1471/firefox-debian-11-arm64.zip": "sha256-5mfk3vO/bDzYpBykTKSXKwHE3e5o+4xFhaAukZMaYSY=", 66 | "builds/firefox/1471/firefox-debian-11.zip": "sha256-ldH1upLNjmBm2wCu4sigeZfjdeihfoVD11heOaHdlQM=", 67 | "builds/firefox/1471/firefox-debian-12-arm64.zip": "sha256-EO5hS4rNV+KMAYriFkwFnEb+CVGk+EXih+XLbvB9GDA=", 68 | "builds/firefox/1471/firefox-debian-12.zip": "sha256-en/shJ/oCv9U5nKcgJBSZNnmgOev49QtxLMd7mTmtg0=", 69 | "builds/firefox/1471/firefox-mac-arm64.zip": "sha256-+FLEjmC1ypnAI/DQY4uJhtx3kQPN4mX8LC0TWEt9x9Y=", 70 | "builds/firefox/1471/firefox-mac.zip": "sha256-dA0WENIFgLHi9D1Zf09xJRcJUXKoD5xrMOFofj7LSj8=", 71 | "builds/firefox/1471/firefox-ubuntu-20.04-arm64.zip": "sha256-2x+jbbQIAvXB79189+GiPHIw8inTGu1ZtK+YFPB+5Q8=", 72 | "builds/firefox/1471/firefox-ubuntu-20.04.zip": "sha256-EBE8mGqeBFLwLjnrrPykd3uodA7s2Qm83IHG9nQM55o=", 73 | "builds/firefox/1471/firefox-ubuntu-22.04-arm64.zip": "sha256-MWaPvvVR8fZ6H8VdI7ahvPmz9kj5Pb9c/vfBvls1Prs=", 74 | "builds/firefox/1471/firefox-ubuntu-22.04.zip": "sha256-Zw+sSIFJIRUuMDsBKS5vOjzc5qrYZEAkC/vKvWknjEs=", 75 | "builds/firefox/1471/firefox-ubuntu-24.04-arm64.zip": "sha256-nQzsnQxV9+lt0nESsxlzfJoS904awyNjG2ox8AaH3+k=", 76 | "builds/firefox/1471/firefox-ubuntu-24.04.zip": "sha256-dAX+hn5ev4yl+yB3rINakFdjUGi++/17UOA5swqb93g=", 77 | "builds/firefox/1475/firefox-debian-11-arm64.zip": "sha256-7SSfzzRPce6GQU7XYNi0f6mXUfm2jfZMZPQIJmv7CE8=", 78 | "builds/firefox/1475/firefox-debian-11.zip": "sha256-QN2Y/T/wVVHnwQ1rrbC7nYoUNaW1aaJ6ZspgQmvIbTw=", 79 | "builds/firefox/1475/firefox-debian-12-arm64.zip": "sha256-Zr+lZsHKjtNm45gSnvwcrfk1kQji1h5xRoujo7kauYo=", 80 | "builds/firefox/1475/firefox-debian-12.zip": "sha256-TEi+qG/yz59uCsGjmGtwCF9eqVbKO4MhCFtHyqucUrM=", 81 | "builds/firefox/1475/firefox-mac-arm64.zip": "sha256-TSu/jjiURTJQLBy30HS/vkf+dxbr2IM76ek9S1VG6Ds=", 82 | "builds/firefox/1475/firefox-mac.zip": "sha256-ruMmoq3FJpb8EJUuJ7Sjpwio7/ExwSBP6QX27sPO65c=", 83 | "builds/firefox/1475/firefox-ubuntu-20.04-arm64.zip": "sha256-orGtUpVUrHl+nU3UQconc4uk48Y/wXFvWQZJylYegHo=", 84 | "builds/firefox/1475/firefox-ubuntu-20.04.zip": "sha256-v/Yf/0em+fZ0opUgpDv79kxJ+OdhkYjhBQr3rTjElp8=", 85 | "builds/firefox/1475/firefox-ubuntu-22.04-arm64.zip": "sha256-Nw+wJ3cd066fQ6ufEwocd92aN6TsTQoK/Q0In0oS3jw=", 86 | "builds/firefox/1475/firefox-ubuntu-22.04.zip": "sha256-nv71vb3lERLrMSiTKXcvwdwL7sf1NWCN9r234X+1LKw=", 87 | "builds/firefox/1475/firefox-ubuntu-24.04-arm64.zip": "sha256-DdijnLtZfh41hHv8D8jktP0NbzXIcZgELR6oN4/YDzw=", 88 | "builds/firefox/1475/firefox-ubuntu-24.04.zip": "sha256-kjiQ+MvLGRKj42Q3EMEA1Q8cd4B392+kxqQRBC/wKa4=", 89 | "builds/webkit/1816/webkit-mac-11-arm64.zip": "sha256-GeZSiz/rQ0R3S1CNPtAmGJayTxt3+kJpds4V052z/1Q=", 90 | "builds/webkit/1816/webkit-mac-11.zip": "sha256-RnHAPszuiEUg23UghFgbVKzz1iK42FFYE9t7PKYeJ2s=", 91 | "builds/webkit/2009/webkit-mac-12-arm64.zip": "sha256-OR/j9ZBkFCgUD/CqSz1j3ZQAS6AC2HgLy/mmA/Cm4cM=", 92 | "builds/webkit/2009/webkit-mac-12.zip": "sha256-2s7UJ4+mgLHzkjxpYfdXYs/4Q4PLwb9W1SEuocgLHg0=", 93 | "builds/webkit/2092/webkit-ubuntu-20.04-arm64.zip": "sha256-YMRbRTBA6MQ3uMCgUgR5Oh3MCt9MbrQlepuKH/mCiN0=", 94 | "builds/webkit/2092/webkit-ubuntu-20.04.zip": "sha256-4dHblulkxXiViVj8Irh6zzq1PsVCRfjgeSWpDHmZ+0Q=", 95 | "builds/webkit/2105/webkit-debian-11-arm64.zip": "sha256-Mhpjz9GNZiayIUQOxD92R1mfb1a7wnusdWPJeECkx+4=", 96 | "builds/webkit/2105/webkit-debian-11.zip": "sha256-pkeiZb0RIX+Hjce1JuQEfq6+nHjzGHJvuJxckdxo81o=", 97 | "builds/webkit/2123/webkit-debian-12-arm64.zip": "sha256-0D+nRkc0yV8029UanyERUpl56AW3m5BLUjTkzGME01k=", 98 | "builds/webkit/2123/webkit-debian-12.zip": "sha256-wFfVeKpHrbVyeMyWj3i9gLRfYI1xFqtxZeoaFTZmjlg=", 99 | "builds/webkit/2123/webkit-mac-13-arm64.zip": "sha256-Y9iuWyPwINNiaNqmBeakpMUR+2jItCC+XCL5mWLanow=", 100 | "builds/webkit/2123/webkit-mac-13.zip": "sha256-syMJrUZxDlWTrnjQ3gi/49TN6K1IC798hnVzcGPmP74=", 101 | "builds/webkit/2123/webkit-mac-14-arm64.zip": "sha256-RiHUDBPVDRlnkgHXzaoeQeAxeSvMiWNOphl/GlEj5aA=", 102 | "builds/webkit/2123/webkit-mac-14.zip": "sha256-hFlxJrWAoMeS3cw3v72ogfEsDFvBaYE98m3S+THfrsY=", 103 | "builds/webkit/2123/webkit-mac-15-arm64.zip": "sha256-AVlMD+wIpU0bS19zG3d2DYQZqIweE6pgtJ//XQfXpek=", 104 | "builds/webkit/2123/webkit-mac-15.zip": "sha256-QMTGtbqIAJYQdZardCx91lWd+EcI9qaz2KXyk8F5ibc=", 105 | "builds/webkit/2123/webkit-ubuntu-22.04-arm64.zip": "sha256-1ue3CP0LWwN0Bh6zhlDwTqlOTc6j7nqha0rKYNxybdM=", 106 | "builds/webkit/2123/webkit-ubuntu-22.04.zip": "sha256-oapgSJ2de+mMhAEs9FR/Y3y9V+2fCc+pH+Eo9GbdRjU=", 107 | "builds/webkit/2123/webkit-ubuntu-24.04-arm64.zip": "sha256-t2+1eE8l5DlBUVBbH6HFcX7EMW0Idsxf5dMwZQjDeDg=", 108 | "builds/webkit/2123/webkit-ubuntu-24.04.zip": "sha256-VpeSOGnCQHf/ViYZtFmna97YLoRvpNvmJ3m5pchtpuM=", 109 | "builds/webkit/2140/webkit-debian-12-arm64.zip": "sha256-WkcAft7drObYupUMm/YC0fjpMPa38vGljuLsNGOixeg=", 110 | "builds/webkit/2140/webkit-debian-12.zip": "sha256-yXbPxGIzlTmc/IS0dJ1kMrtbNyjFUubKb5DUm45q7JY=", 111 | "builds/webkit/2140/webkit-mac-13-arm64.zip": "sha256-El5K5oAoyI9jv/4bJCUSXIMfhEGUipvtZZJ8LDQg5V0=", 112 | "builds/webkit/2140/webkit-mac-13.zip": "sha256-Ey7qjUHibCifUk9gEQA5ErFms2J7/y92HqkVnW5HAv4=", 113 | "builds/webkit/2140/webkit-mac-14-arm64.zip": "sha256-XRpAhomjjaMf3EoNKcbS7UwHQY0QNOrqHT0CycwTGnI=", 114 | "builds/webkit/2140/webkit-mac-14.zip": "sha256-ALHk7lmUkaj8gBzjdyf2Ae4bKCP7I8HfhpaZYzCuR+I=", 115 | "builds/webkit/2140/webkit-mac-15-arm64.zip": "sha256-7A1jgSa4UX/VFKHvjHiP7vX17+HSjfYANF2qgzRD20s=", 116 | "builds/webkit/2140/webkit-mac-15.zip": "sha256-iWwrNMX4NJITn+3dfYVP8O/3ML2PKRxCVjMaCXiJqfc=", 117 | "builds/webkit/2140/webkit-ubuntu-22.04-arm64.zip": "sha256-uWVpUL3rKgFOmOLrGrlIg0Cp5Axk/abYsHk/5gGGGTg=", 118 | "builds/webkit/2140/webkit-ubuntu-22.04.zip": "sha256-iKxb9mhEjj5YtRFIXz6nxAmG9TmCqzKhFtOLuoR29NM=", 119 | "builds/webkit/2140/webkit-ubuntu-24.04-arm64.zip": "sha256-hAJXXnH6z+fLUYPI/IHkqyjcjtZp3Cj9qL92+hRUxHg=", 120 | "builds/webkit/2140/webkit-ubuntu-24.04.zip": "sha256-U2xgNUbr2R1MzqAw3QYhgISF4XswtOhGeSKpu1uR8q8=", 121 | } 122 | -------------------------------------------------------------------------------- /playwright/private/select_exec.bzl: -------------------------------------------------------------------------------- 1 | """Small rule which unzip a browser to a tree artifact using bash. 2 | """ 3 | 4 | def _select_exec_impl(ctx): 5 | return [ 6 | DefaultInfo(files = depset(ctx.files.src)), 7 | platform_common.TemplateVariableInfo( 8 | { 9 | "PLAYWRIGHT_BROWSERS_PATH": ctx.files.src[0].short_path + "/..", 10 | }, 11 | ), 12 | ] 13 | 14 | select_exec = rule( 15 | implementation = _select_exec_impl, 16 | attrs = { 17 | "src": attr.label( 18 | doc = "The executable to select", 19 | mandatory = True, 20 | allow_files = True, 21 | cfg = "exec", 22 | ), 23 | }, 24 | ) 25 | -------------------------------------------------------------------------------- /playwright/private/unzip_browser.bzl: -------------------------------------------------------------------------------- 1 | """Small rule which unzip a browser to a tree artifact using bash. 2 | """ 3 | 4 | UnzippedBrowserInfo = provider( 5 | doc = "Provides the sources of an npm package along with the package name and version", 6 | fields = { 7 | "http_file_path": "name of the http_file rule used to download the browser", 8 | "browser_archive": "file from the http_file used to download the browser", 9 | "output_path": "where the browser was unzipped to", 10 | }, 11 | ) 12 | 13 | def _unzip_browser_impl(ctx): 14 | output_dir = ctx.actions.declare_directory(ctx.attr.output_dir) 15 | ctx.actions.run( 16 | inputs = [ctx.file.browser], 17 | outputs = [output_dir], 18 | executable = ctx.executable._cli, 19 | arguments = ["unzip", "--output-path", output_dir.path, "--input-path", ctx.file.browser.path], 20 | ) 21 | return [ 22 | DefaultInfo(files = depset([output_dir])), 23 | UnzippedBrowserInfo( 24 | http_file_path = ctx.attr.http_file_path, 25 | browser_archive = ctx.file.browser, 26 | output_path = output_dir.path, 27 | ), 28 | ] 29 | 30 | unzip_browser = rule( 31 | implementation = _unzip_browser_impl, 32 | attrs = { 33 | "http_file_path": attr.string(mandatory = True), 34 | "browser": attr.label( 35 | allow_single_file = True, 36 | mandatory = True, 37 | ), 38 | "output_dir": attr.string(mandatory = True), 39 | "_cli": attr.label( 40 | default = "//tools/release:cli", 41 | allow_single_file = True, 42 | executable = True, 43 | cfg = "exec", 44 | ), 45 | }, 46 | ) 47 | -------------------------------------------------------------------------------- /playwright/private/util.bzl: -------------------------------------------------------------------------------- 1 | """Utility functions used across multiple Starlark rules.""" 2 | 3 | def get_browsers_json_path(ctx, playwright_version, browsers_json): 4 | """Retruns the path to a browsers.json file 5 | 6 | Args: 7 | ctx: The Starlark context object 8 | playwright_version: Optional playwright version 9 | browsers_json: Optional browsers json label 10 | 11 | Returns: 12 | Path to the browsers.json file 13 | """ 14 | if browsers_json: 15 | return ctx.path(browsers_json) 16 | 17 | browsers_json_path = ctx.path("playwright-core") 18 | ctx.download_and_extract( 19 | url = "https://registry.npmjs.org/playwright-core/-/playwright-core-{}.tgz".format( 20 | playwright_version, 21 | ), 22 | output = browsers_json_path, 23 | ) 24 | return ctx.path("playwright-core/package/browsers.json") 25 | 26 | def get_cli_path(ctx): 27 | """Returns the platform-specific path to the browser workspace generator binary. 28 | 29 | Args: 30 | ctx: The Starlark context object containing OS information 31 | 32 | Returns: 33 | Path to the browser workspace generator binary for the current platform 34 | """ 35 | arch = "arm64" 36 | if ctx.os.arch == "amd64" or ctx.os.arch == "x86_64": 37 | arch = "x86_64" 38 | 39 | platform = "unknown-linux-musl" 40 | if "mac" in ctx.os.name: 41 | platform = "apple-darwin" 42 | 43 | return ctx.path(Label("//tools/release:artifacts/cli-{arch}-{platform}".format(platform = platform, arch = arch))) 44 | -------------------------------------------------------------------------------- /playwright/repositories.bzl: -------------------------------------------------------------------------------- 1 | """Declare runtime dependencies 2 | 3 | These are needed for local dev, and users must install them as well. 4 | See https://docs.bazel.build/versions/main/skylark/deploying.html#dependencies 5 | """ 6 | 7 | load("//playwright/private:known_browsers.bzl", "KNOWN_BROWSER_INTEGRITY") 8 | load("//playwright/private:util.bzl", "get_browsers_json_path", "get_cli_path") 9 | 10 | _PLAYWRIGHT_PACKAGE = "playwright" 11 | _PLAYWRIGHT_TEST_PACKAGE = "@playwright/test" 12 | _PLAYWRIGHT_PACKAGES = [_PLAYWRIGHT_PACKAGE, _PLAYWRIGHT_TEST_PACKAGE] 13 | 14 | def _find_playwright_version_in_deps(deps_dict): 15 | for package in _PLAYWRIGHT_PACKAGES: 16 | if package in deps_dict: 17 | return deps_dict[package] 18 | return None 19 | 20 | def _extract_playwright_version(package_json_data): 21 | version = _find_playwright_version_in_deps(package_json_data.get("dependencies", {})) 22 | if not version: 23 | version = _find_playwright_version_in_deps(package_json_data.get("devDependencies", {})) 24 | 25 | return version 26 | 27 | def _playwright_repo_impl(ctx): 28 | if ctx.attr.playwright_version and ctx.attr.playwright_version_from: 29 | fail("playwright_version and playwright_version_from cannot both be set") 30 | 31 | if not ctx.attr.playwright_version and not ctx.attr.playwright_version_from and not ctx.attr.browsers_json: 32 | fail("one of playwright_version or playwright_version_from or browsers_json must be set") 33 | 34 | playwright_version = ctx.attr.playwright_version 35 | 36 | if ctx.attr.playwright_version_from: 37 | package_json_content = ctx.read(ctx.attr.playwright_version_from) 38 | package_json_data = json.decode(package_json_content) 39 | playwright_version = _extract_playwright_version(package_json_data) 40 | if not playwright_version: 41 | fail("playwright not found in dependencies or devDependencies") 42 | 43 | ctx.watch(get_cli_path(ctx)) 44 | 45 | result = ctx.execute( 46 | [ 47 | get_cli_path(ctx), 48 | "workspace", 49 | "--browser-json-path", 50 | get_browsers_json_path(ctx, playwright_version, ctx.attr.browsers_json), 51 | "--browsers-workspace-name-prefix", 52 | ctx.attr.browsers_workspace_name_prefix, 53 | "--rules-playwright-cannonical-name", 54 | ctx.attr.rules_playwright_cannonical_name, 55 | ], 56 | ) 57 | 58 | if result.return_code != 0: 59 | fail(ctx.attr.name, "workspace command failed", result.stdout, result.stderr) 60 | 61 | playwright_repository = repository_rule( 62 | _playwright_repo_impl, 63 | doc = "Fetch external tools needed for playwright toolchain", 64 | attrs = { 65 | "playwright_version": attr.string( 66 | mandatory = False, 67 | doc = "The version of playwright to install", 68 | ), 69 | "playwright_version_from": attr.label( 70 | mandatory = False, 71 | allow_single_file = [".json"], 72 | doc = "The package.json file to use to find the version of playwright to install", 73 | ), 74 | "browsers_json": attr.label( 75 | allow_single_file = True, 76 | doc = "The browsers.json file to use. For example https://unpkg.com/playwright-core@1.51.0/browsers.json", 77 | ), 78 | "browsers_workspace_name_prefix": attr.string( 79 | mandatory = True, 80 | doc = "The namespace prefix used when defining browser workspace repositories.", 81 | ), 82 | "rules_playwright_cannonical_name": attr.string( 83 | mandatory = True, 84 | doc = "The cannonical name given to the rules_playwright repository. See https://bazel.build/external/module", 85 | ), 86 | }, 87 | ) 88 | 89 | def _define_browsers_impl(rctx): 90 | result = rctx.execute( 91 | [ 92 | get_cli_path(rctx), 93 | "http-files", 94 | "--browser-json-path", 95 | rctx.path(rctx.attr.browsers_json), 96 | "--browsers-workspace-name-prefix", 97 | rctx.attr.name, 98 | ], 99 | ) 100 | if result.return_code != 0: 101 | fail("http-files command failed", result.stdout, result.stderr) 102 | 103 | result_build = [ 104 | """load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")""", 105 | "def fetch_browsers():", 106 | ] 107 | 108 | # Create a new dictionary by merging the known browser integrity with the user-provided integrity 109 | # User-provided integrity takes precedence 110 | integrity_map = dict(KNOWN_BROWSER_INTEGRITY) 111 | for key, value in rctx.attr.browser_integrity.items(): 112 | integrity_map[key] = value 113 | 114 | for http_file_json in json.decode(result.stdout): 115 | path = http_file_json["path"] 116 | integrity_attr = "" 117 | if path in integrity_map: 118 | integrity_attr = 'integrity = "{}",\n'.format(integrity_map[path]) 119 | 120 | result_build.append("""\ 121 | http_file( 122 | name = "{name}", 123 | {integrity} 124 | urls = [ 125 | "https://playwright.azureedge.net/{path}", 126 | "https://playwright-akamai.azureedge.net/{path}", 127 | "https://playwright-verizon.azureedge.net/{path}", 128 | ], 129 | ) 130 | """.format( 131 | name = http_file_json["name"], 132 | path = path, 133 | integrity = integrity_attr, 134 | )) 135 | rctx.file("browsers.bzl", "\n".join(result_build)) 136 | rctx.file("BUILD", "# no targets") 137 | 138 | define_browsers = repository_rule( 139 | implementation = _define_browsers_impl, 140 | attrs = { 141 | "browsers_json": attr.label(allow_single_file = True), 142 | "browser_integrity": attr.string_dict( 143 | doc = "A dictionary of browser names to their integrity hashes", 144 | default = {}, 145 | ), 146 | }, 147 | ) 148 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | ":dependencyDashboard", 5 | ":enablePreCommit", 6 | ":semanticPrefixFixDepsChoreOthers", 7 | "group:monorepos", 8 | "group:recommended", 9 | "replacements:all", 10 | "workarounds:all" 11 | ], 12 | "packageRules": [ 13 | { 14 | "matchFiles": ["MODULE.bazel"], 15 | "enabled": false 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tools/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_binary( 4 | name = "stardoc_renderer", 5 | main_class = "com/google/devtools/build/stardoc/renderer/RendererMain", 6 | runtime_deps = ["@stardoc-prebuilt//jar"], 7 | ) 8 | -------------------------------------------------------------------------------- /tools/config_setting/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//playwright:defs.bzl", "LINUX_DISTROS", "MACOS_VERSIONS") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | config_setting( 6 | name = "linux_x86_64", 7 | constraint_values = [ 8 | "@platforms//cpu:x86_64", 9 | "@platforms//os:linux", 10 | ], 11 | ) 12 | 13 | config_setting( 14 | name = "linux_arm64", 15 | constraint_values = [ 16 | "@platforms//cpu:arm64", 17 | "@platforms//os:linux", 18 | ], 19 | ) 20 | 21 | config_setting( 22 | name = "macos_x86_64", 23 | constraint_values = [ 24 | "@platforms//cpu:x86_64", 25 | "@platforms//os:macos", 26 | ], 27 | ) 28 | 29 | config_setting( 30 | name = "macos_arm64", 31 | constraint_values = [ 32 | "@platforms//cpu:arm64", 33 | "@platforms//os:macos", 34 | ], 35 | ) 36 | 37 | config_setting( 38 | name = "mac{}".format(version), 39 | values = { 40 | "//:macos_version": version, 41 | }, 42 | ) 43 | 44 | [ 45 | 46 | for version in MACOS_VERSIONS 47 | ] 48 | 49 | [ 50 | config_setting( 51 | name = distro, 52 | values = { 53 | "//:linux_distro": distro, 54 | }, 55 | ) 56 | for distro in LINUX_DISTROS 57 | ] 58 | -------------------------------------------------------------------------------- /tools/linkers/BUILD.bazel: -------------------------------------------------------------------------------- 1 | constraint_setting( 2 | name = "linker", 3 | default_constraint_value = ":unknown", 4 | visibility = ["//visibility:public"], 5 | ) 6 | 7 | constraint_value( 8 | name = "musl", 9 | constraint_setting = ":linker", 10 | visibility = ["//visibility:public"], 11 | ) 12 | 13 | # Default linker for anyone not setting the linker to `musl`. 14 | # You shouldn't ever need to set this value manually. 15 | constraint_value( 16 | name = "unknown", 17 | constraint_setting = ":linker", 18 | visibility = ["//visibility:public"], 19 | ) 20 | 21 | platform( 22 | name = "linux_x86_64_musl", 23 | constraint_values = [ 24 | ":musl", 25 | "@platforms//cpu:x86_64", 26 | "@platforms//os:linux", 27 | ], 28 | visibility = ["//visibility:public"], 29 | ) 30 | -------------------------------------------------------------------------------- /tools/platforms/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//playwright:defs.bzl", "LINUX_DISTROS", "MACOS_VERSIONS") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | config_setting( 6 | name = "linux_x86_64", 7 | constraint_values = [ 8 | "@platforms//cpu:x86_64", 9 | "@platforms//os:linux", 10 | ], 11 | ) 12 | 13 | config_setting( 14 | name = "linux_arm64", 15 | constraint_values = [ 16 | "@platforms//cpu:arm64", 17 | "@platforms//os:linux", 18 | ], 19 | ) 20 | 21 | config_setting( 22 | name = "macos_x86_64", 23 | constraint_values = [ 24 | "@platforms//cpu:x86_64", 25 | "@platforms//os:macos", 26 | ], 27 | ) 28 | 29 | config_setting( 30 | name = "macos_arm64", 31 | constraint_values = [ 32 | "@platforms//cpu:arm64", 33 | "@platforms//os:macos", 34 | ], 35 | ) 36 | 37 | [ 38 | config_setting( 39 | name = "mac{}".format(version), 40 | flag_values = { 41 | "//:macos_version": version, 42 | }, 43 | ) 44 | for version in MACOS_VERSIONS 45 | ] 46 | 47 | [ 48 | config_setting( 49 | name = distro, 50 | flag_values = { 51 | "//:linux_distro": distro, 52 | }, 53 | ) 54 | for distro in LINUX_DISTROS 55 | ] 56 | -------------------------------------------------------------------------------- /tools/release/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") 2 | load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_file") 3 | load("@bazel_skylib//rules:native_binary.bzl", "native_binary") 4 | 5 | package(default_visibility = ["//visibility:public"]) 6 | 7 | config_setting( 8 | name = "release", 9 | values = { 10 | "compilation_mode": "opt", 11 | }, 12 | ) 13 | 14 | [ 15 | platform( 16 | name = "{}_{}".format(os, cpu), 17 | constraint_values = [ 18 | "@platforms//os:" + os, 19 | "@platforms//cpu:" + cpu, 20 | "//tools/linkers:musl" if os == "linux" else "//tools/linkers:unknown", 21 | ], 22 | ) 23 | for os in [ 24 | "linux", 25 | "macos", 26 | ] 27 | for cpu in [ 28 | "arm64", 29 | "x86_64", 30 | ] 31 | ] 32 | 33 | LINUX_ARTIFACTS = [ 34 | "//playwright/private/cli:cli_linux", 35 | ] 36 | 37 | MACOS_ARTIFACTS = [ 38 | "//playwright/private/cli:cli_macos", 39 | ] 40 | 41 | copy_to_directory( 42 | name = "release_artifacts", 43 | srcs = LINUX_ARTIFACTS + MACOS_ARTIFACTS, 44 | out = "artifacts", 45 | root_paths = ["playwright/private/cli"], 46 | tags = ["manual"], 47 | ) 48 | 49 | write_source_file( 50 | name = "copy_release_artifacts", 51 | diff_test = False, 52 | executable = True, 53 | in_file = ":release_artifacts", 54 | out_file = "artifacts", 55 | tags = ["manual"], 56 | ) 57 | 58 | native_binary( 59 | name = "cli", 60 | src = select( 61 | { 62 | "//tools/platforms:macos_x86_64": ":artifacts/cli-x86_64-apple-darwin", 63 | "//tools/platforms:macos_arm64": ":artifacts/cli-arm64-apple-darwin", 64 | "//tools/platforms:linux_x86_64": ":artifacts/cli-x86_64-unknown-linux-musl", 65 | "//tools/platforms:linux_arm64": ":artifacts/cli-arm64-unknown-linux-musl", 66 | }, 67 | ), 68 | out = "cli", 69 | ) 70 | -------------------------------------------------------------------------------- /tools/release/artifacts/cli-arm64-apple-darwin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmeku/rules_playwright/1a866baba4f7ff795fc46a8fb12a18b9070ae640/tools/release/artifacts/cli-arm64-apple-darwin -------------------------------------------------------------------------------- /tools/release/artifacts/cli-arm64-unknown-linux-musl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmeku/rules_playwright/1a866baba4f7ff795fc46a8fb12a18b9070ae640/tools/release/artifacts/cli-arm64-unknown-linux-musl -------------------------------------------------------------------------------- /tools/release/artifacts/cli-x86_64-apple-darwin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmeku/rules_playwright/1a866baba4f7ff795fc46a8fb12a18b9070ae640/tools/release/artifacts/cli-x86_64-apple-darwin -------------------------------------------------------------------------------- /tools/release/artifacts/cli-x86_64-unknown-linux-musl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmeku/rules_playwright/1a866baba4f7ff795fc46a8fb12a18b9070ae640/tools/release/artifacts/cli-x86_64-unknown-linux-musl -------------------------------------------------------------------------------- /tools/release/copy_release_artifacts.sh: -------------------------------------------------------------------------------- 1 | bazel run //tools/release:copy_release_artifacts --config=release -------------------------------------------------------------------------------- /tools/release/defs.bzl: -------------------------------------------------------------------------------- 1 | "Make releases for platforms supported by rules_playwright" 2 | 3 | load("@aspect_bazel_lib//lib:copy_file.bzl", "copy_file") 4 | load("@rules_rust//rust:defs.bzl", _rust_binary = "rust_binary") 5 | 6 | DEFAULT_OS = ["linux", "macos"] 7 | DEFAULT_ARCHS = ["arm64", "x86_64"] 8 | 9 | def _map_os_to_triple(os): 10 | if os == "linux": 11 | return "unknown-linux-musl" 12 | if os == "macos": 13 | return "apple-darwin" 14 | fail("Unrecognized os", os) 15 | 16 | # buildozer: disable=function-docstring 17 | def rust_binary(name, visibility = [], **kwargs): 18 | selection = {} 19 | for os in DEFAULT_OS: 20 | outs = [] 21 | 22 | target_suffix = "{}_{}".format(name, os) 23 | target_compatible_with = ["@platforms//os:{}".format(os)] 24 | 25 | for arch in DEFAULT_ARCHS: 26 | arch_target_suffix = "{}_{}".format(target_suffix, arch) 27 | binary_name = "{}_build".format(arch_target_suffix) 28 | platform = "//tools/platforms:{}_{}".format(os, arch) 29 | release_platform = "//tools/release:{}_{}".format(os, arch) 30 | 31 | # Artifact naming follows typical Rust "triples" convention. 32 | artifact = "{}-{}-{}".format(name, arch, _map_os_to_triple(os)) 33 | outs.append(artifact) 34 | 35 | selection.update([[platform, binary_name]]) 36 | 37 | _rust_binary( 38 | name = binary_name, 39 | crate_name = name, 40 | platform = release_platform, 41 | target_compatible_with = target_compatible_with, 42 | rustc_flags = select({ 43 | "//tools/release": [ 44 | "-Ccodegen-units=1", 45 | "-Cpanic=abort", 46 | "-Copt-level=z", 47 | "-Cstrip=symbols", 48 | ], 49 | "//conditions:default": [ 50 | "-Copt-level=0", 51 | ], 52 | }), 53 | tags = ["manual"], 54 | **kwargs 55 | ) 56 | 57 | copy_file( 58 | name = "copy_{}".format(arch_target_suffix), 59 | src = binary_name, 60 | out = artifact, 61 | tags = ["manual"], 62 | ) 63 | 64 | native.filegroup( 65 | name = target_suffix, 66 | srcs = outs, 67 | tags = ["manual"], 68 | visibility = ["//tools/release:__pkg__"], 69 | ) 70 | 71 | native.alias( 72 | name = name, 73 | actual = select(selection), 74 | visibility = visibility, 75 | ) 76 | -------------------------------------------------------------------------------- /tools/version.bzl: -------------------------------------------------------------------------------- 1 | "version information. replaced with stamped info with each release" 2 | 3 | # Automagically "stamped" by git during `git archive` thanks to `export-subst` line in .gitattributes. 4 | # See https://git-scm.com/docs/git-archive#Documentation/git-archive.txt-export-subst 5 | _VERSION_PRIVATE = "$Format:%(describe:tags=true)$" 6 | 7 | VERSION = "0.0.0" if _VERSION_PRIVATE.startswith("$Format") else _VERSION_PRIVATE.replace("v", "", 1) 8 | 9 | # Whether rules_playwright is a pre-release, and therefore has no release artifacts to download. 10 | # NB: When GitHub runs `git archive` to serve a source archive file, 11 | # it honors our .gitattributes and stamps this file, e.g. 12 | # _VERSION_PRIVATE = "v2.0.3-7-g57bfe2c1" 13 | # From https://git-scm.com/docs/git-describe: 14 | # > The "g" prefix stands for "git" 15 | IS_PRERELEASE = VERSION == "0.0.0" or VERSION.find("g") >= 0 16 | --------------------------------------------------------------------------------