├── .gitattributes
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ └── feature-request.md
└── workflows
│ ├── build.yml
│ ├── docs.yml
│ └── verify_type_generation.yml
├── .gitignore
├── .gitmodules
├── .golangci.yaml
├── .nojekyll
├── 404.html
├── CONTRIBUTING.md
├── Dockerfile.example
├── LICENSE
├── README.md
├── _config.yml
├── apiresponse_assertions.go
├── artifact.go
├── assertions.go
├── binding_call.go
├── browser.go
├── browser_context.go
├── browser_type.go
├── cdp_session.go
├── channel.go
├── channel_owner.go
├── clock.go
├── cmd
└── playwright
│ └── main.go
├── connection.go
├── console_message.go
├── dialog.go
├── docs
├── .gitignore
├── Gemfile
└── Gemfile.lock
├── download.go
├── element_handle.go
├── errors.go
├── event_emitter.go
├── event_emitter_test.go
├── examples
├── download
│ └── main.go
├── end-to-end-testing
│ └── main.go
├── javascript
│ └── main.go
├── mobile-and-geolocation
│ └── main.go
├── network-monitoring
│ └── main.go
├── parallel-scraping
│ └── main.go
├── pdf
│ └── main.go
├── scraping
│ └── main.go
├── screenshot
│ └── main.go
├── use-local-chrome
│ └── main.go
└── video
│ └── main.go
├── fetch.go
├── file_chooser.go
├── frame.go
├── frame_locator.go
├── generated-enums.go
├── generated-interfaces.go
├── generated-structs.go
├── glob.go
├── glob_test.go
├── go.mod
├── go.sum
├── har_router.go
├── helpers.go
├── helpers_test.go
├── input.go
├── input_files_helper.go
├── internal
└── safe
│ └── map.go
├── js_handle.go
├── jsonPipe.go
├── local_utils.go
├── locator.go
├── locator_assertions.go
├── locator_helpers.go
├── network.go
├── objectFactory.go
├── page.go
├── page_assertions.go
├── patches
└── main.patch
├── playwright.go
├── request.go
├── response.go
├── route.go
├── run.go
├── run_test.go
├── run_unix.go
├── run_win.go
├── scripts
├── apply-patch.sh
├── generate-api.sh
├── install-browsers
│ └── main.go
├── update-patch.sh
└── update-readme-versions
│ └── main.go
├── selectors.go
├── stream.go
├── tests
├── apiresponse_assertions_test.go
├── assets
│ ├── background-color.html
│ ├── beforeunload.html
│ ├── button-overlay-oopif.html
│ ├── cached
│ │ ├── one-style.css
│ │ └── one-style.html
│ ├── callback.js
│ ├── checkerboard.html
│ ├── chromium-linux.zip
│ ├── client-certificates
│ │ ├── README.md
│ │ ├── client
│ │ │ ├── self-signed
│ │ │ │ ├── cert.pem
│ │ │ │ ├── csr.pem
│ │ │ │ └── key.pem
│ │ │ └── trusted
│ │ │ │ ├── cert-legacy.pfx
│ │ │ │ ├── cert.pem
│ │ │ │ ├── cert.pfx
│ │ │ │ ├── csr.pem
│ │ │ │ └── key.pem
│ │ └── server
│ │ │ ├── server_cert.pem
│ │ │ └── server_key.pem
│ ├── consolelog.html
│ ├── csp.html
│ ├── css-transition.html
│ ├── csscoverage
│ │ ├── Dosis-Regular.ttf
│ │ ├── OFL.txt
│ │ ├── involved.html
│ │ ├── media.html
│ │ ├── multiple.html
│ │ ├── simple.html
│ │ ├── sourceurl.html
│ │ ├── stylesheet1.css
│ │ ├── stylesheet2.css
│ │ └── unused.html
│ ├── deep-shadow.html
│ ├── detect-touch.html
│ ├── digits
│ │ ├── 0.png
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ └── 9.png
│ ├── dom.html
│ ├── download-blob.html
│ ├── drag-n-drop.html
│ ├── dummy_bad_browser_executable.js
│ ├── dynamic-oopif.html
│ ├── empty-standard-mode.html
│ ├── empty.html
│ ├── empty.pdf
│ ├── error.html
│ ├── es6
│ │ ├── .eslintrc
│ │ ├── es6import.js
│ │ ├── es6module.js
│ │ └── es6pathimport.js
│ ├── example.mp3
│ ├── file-to-upload-2.txt
│ ├── file-to-upload.txt
│ ├── formatted-number.html
│ ├── frames
│ │ ├── child-redirect.html
│ │ ├── frame.html
│ │ ├── frameset.html
│ │ ├── lazy-frame.html
│ │ ├── nested-frames.html
│ │ ├── one-frame.html
│ │ ├── redirect-my-parent.html
│ │ ├── script.js
│ │ ├── style.css
│ │ └── two-frames.html
│ ├── geolocation.html
│ ├── global-var.html
│ ├── grid-iframe-in-shadow.html
│ ├── grid.html
│ ├── har-fulfill.har
│ ├── har-redirect.har
│ ├── har-sha1-main-response.txt
│ ├── har-sha1.har
│ ├── har.html
│ ├── headings.html
│ ├── highdpi.html
│ ├── historyapi.html
│ ├── injectedfile.js
│ ├── injectedstyle.css
│ ├── input
│ │ ├── animating-button.html
│ │ ├── button.html
│ │ ├── checkbox.html
│ │ ├── drag-n-drop-manual.html
│ │ ├── fileupload.html
│ │ ├── folderupload.html
│ │ ├── handle-locator.html
│ │ ├── keyboard.html
│ │ ├── mouse-helper.js
│ │ ├── rotatedButton.html
│ │ ├── scrollable.html
│ │ ├── scrollable2.html
│ │ ├── select.html
│ │ ├── textarea.html
│ │ └── touches.html
│ ├── jscoverage
│ │ ├── eval.html
│ │ ├── involved.html
│ │ ├── multiple.html
│ │ ├── ranges.html
│ │ ├── script1.js
│ │ ├── script2.js
│ │ ├── simple.html
│ │ ├── sourceurl.html
│ │ └── unused.html
│ ├── load-event
│ │ ├── load-event.html
│ │ └── module.js
│ ├── media-query-prefers-color-scheme.svg
│ ├── mobile.html
│ ├── modernizr.html
│ ├── modernizr.js
│ ├── modernizr
│ │ ├── mobile-safari-14-1.json
│ │ └── safari-14-1.json
│ ├── movie.mp4
│ ├── movie.ogv
│ ├── mui.html
│ ├── networkidle-frame.html
│ ├── networkidle.html
│ ├── networkidle.js
│ ├── offscreenbuttons.html
│ ├── one-style.css
│ ├── one-style.html
│ ├── overflow-large.html
│ ├── overflow.html
│ ├── player.html
│ ├── playground.html
│ ├── popup
│ │ ├── popup.html
│ │ └── window-open.html
│ ├── pptr.png
│ ├── redirectloop1.html
│ ├── redirectloop2.html
│ ├── resetcss.html
│ ├── rotate-pseudo.html
│ ├── rotate-z-shadow-dom.html
│ ├── rotate-z.html
│ ├── screenshots
│ │ ├── canvas.html
│ │ ├── controls.html
│ │ ├── translateZ.html
│ │ └── webgl.html
│ ├── sectionselectorengine.js
│ ├── selenium-grid
│ │ ├── broken-selenium-driver.js
│ │ └── selenium-config-standalone.json
│ ├── self-request.html
│ ├── serviceworkers
│ │ ├── empty
│ │ │ ├── sw.html
│ │ │ └── sw.js
│ │ ├── fetch
│ │ │ ├── style.css
│ │ │ ├── sw.html
│ │ │ └── sw.js
│ │ └── fetchdummy
│ │ │ ├── sw.html
│ │ │ └── sw.js
│ ├── shadow-dom-link.html
│ ├── shadow.html
│ ├── simple-extension
│ │ ├── content-script.js
│ │ ├── index.js
│ │ └── manifest.json
│ ├── simple.json
│ ├── simplezip.json
│ ├── tamperable.html
│ ├── title.html
│ ├── video.html
│ ├── video_mp4.html
│ ├── wasm
│ │ ├── table2.html
│ │ └── table2.wasm
│ ├── web-animation.html
│ ├── webfont
│ │ ├── README.md
│ │ ├── iconfont.svg
│ │ ├── iconfont.woff2
│ │ └── webfont.html
│ ├── window-stop.html
│ ├── worker
│ │ ├── import-me.js
│ │ ├── worker-http-import.html
│ │ ├── worker-http-import.js
│ │ ├── worker.html
│ │ └── worker.js
│ └── wrappedlink.html
├── binding_test.go
├── browser_context_client_certificates_test.go
├── browser_context_storage_state_test.go
├── browser_context_test.go
├── browser_test.go
├── browser_type_test.go
├── cdp_session_test.go
├── console_message_test.go
├── dialog_test.go
├── download_test.go
├── element_handle_test.go
├── fetch_test.go
├── file_chooser_test.go
├── frame_locator_test.go
├── frame_test.go
├── har_test.go
├── helper_test.go
├── input_test.go
├── js_handle_test.go
├── locator_assertions_test.go
├── locator_get_by_test.go
├── locator_test.go
├── network_test.go
├── page_add_locator_handler_test.go
├── page_aria_snapshot_test.go
├── page_assertions_test.go
├── page_clock_test.go
├── page_request_gc_test.go
├── page_test.go
├── remote_server_test.go
├── route_test.go
├── route_web_socket_test.go
├── screenshot-snapshots
│ ├── mask-should-work-chromium.png
│ ├── mask-should-work-firefox.png
│ ├── mask-should-work-webkit.png
│ ├── mask-should-work-with-elementhandle-chromium.png
│ ├── mask-should-work-with-elementhandle-firefox.png
│ ├── mask-should-work-with-elementhandle-webkit.png
│ ├── mask-should-work-with-locator-chromium.png
│ ├── mask-should-work-with-locator-firefox.png
│ ├── mask-should-work-with-locator-webkit.png
│ ├── screenshot-element-bounding-box-chromium.png
│ ├── screenshot-element-bounding-box-firefox.png
│ └── screenshot-element-bounding-box-webkit.png
├── screenshot_test.go
├── selectors_get_by_test.go
├── selectors_test.go
├── selectors_text_test.go
├── tracing_test.go
├── unroute_behavior_test.go
├── utils_test.go
├── video_test.go
├── websocket_test.go
└── worker_test.go
├── tracing.go
├── transport.go
├── type_helpers.go
├── video.go
├── waiter.go
├── waiter_test.go
├── web_error.go
├── websocket.go
├── websocket_route.go
├── worker.go
└── writable_stream.go
/.gitattributes:
--------------------------------------------------------------------------------
1 | # text files must be lf for golden file tests to work
2 | * text=auto eol=lf
3 |
4 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: mxschmitt
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "Bug report \U0001FAB2"
3 | about: Create a bug report to help us improve
4 | title: "[Bug]: "
5 | labels: p2-bug
6 | assignees: ''
7 |
8 | ---
9 |
10 |
17 | **Environments**
18 |
19 | - playwright-go Version: [e.g. v0.4201.1]
20 | - Browser: [e.g. firefox]
21 | - OS and version: [e.g. macOS / Windows 11/ Ubuntu 22.04]
22 |
23 | **Bug description**
24 |
26 |
27 | **To Reproduce**
28 | Please provide a mini reproduction rather than just a description. For example:
29 |
30 | ```go
31 | package main
32 |
33 | import "github.com/playwright-community/playwright-go"
34 |
35 | func main() {
36 | // ignore unnecessary error handling code
37 | pw, _ := playwright.Run()
38 | browser, _ := pw.Chromium.Launch()
39 | context, _ := browser.NewContext()
40 | page, _ := context.NewPage()
41 |
42 | _, _ = page.Goto("https://playwright.dev")
43 |
44 | _, err := page.PDF(playwright.PagePdfOptions{
45 | Path: playwright.String("playwright-example.pdf"),
46 | })
47 | // should no error
48 | if err != nil {
49 | panic(err)
50 | }
51 | }
52 |
53 | ```
54 |
55 | **Additional context**
56 | Add any other context about the problem here.
57 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "Feature request \U0001F680"
3 | about: Suggest an idea for this project
4 | title: "[Feature]: "
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Does the upstream have similar features?**
11 | `playwright-go` is just a community-driven client for [playwright](https://playwright.dev). For feature requests, please refer to other officially supported clients first. [Nodejs](https://github.com/microsoft/playwright) / [Java](https://github.com/microsoft/playwright-java) / [.Net](https://github.com/microsoft/playwright-dotnet) / [Python](https://github.com/microsoft/playwright-python)
12 |
13 | **Is your feature request related to a problem? Please describe.**
14 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
15 |
16 | **Describe the solution you'd like**
17 | A clear and concise description of what you want to happen.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Docs
2 | on:
3 | push:
4 | branches: [ main ]
5 | jobs:
6 | deploy:
7 | name: Deploy docs
8 | runs-on: ubuntu-20.04
9 | steps:
10 | - uses: actions/checkout@v4
11 | - uses: ruby/setup-ruby@v1
12 | with:
13 | ruby-version: '3.3'
14 | - name: Install Ruby dependencies
15 | run: bundler install
16 | working-directory: docs
17 | - name: Build site
18 | run: jekyll build
19 | - name: Deploy
20 | uses: peaceiris/actions-gh-pages@v4
21 | with:
22 | github_token: ${{ secrets.GITHUB_TOKEN }}
23 | publish_dir: ./_site
--------------------------------------------------------------------------------
/.github/workflows/verify_type_generation.yml:
--------------------------------------------------------------------------------
1 | name: Verify Types
2 | on:
3 | push:
4 | branches: [ main ]
5 | pull_request:
6 | branches: [ main ]
7 | jobs:
8 | verify:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v4
12 | with:
13 | submodules: true
14 | - name: Set up Go
15 | uses: actions/setup-go@v5
16 | with:
17 | go-version: oldstable
18 | - name: Install gofumpt
19 | run: go install mvdan.cc/gofumpt@latest
20 | - name: Install Browsers
21 | run: |
22 | go install ./...
23 | playwright install --with-deps
24 | - name: Regenerate APIs
25 | run: |
26 | git config --global user.email "no-reply@github.com"
27 | git config --global user.name "Github Actions"
28 | go generate
29 | - name: Verify API is up to date
30 | run: git diff --exit-code --ignore-submodules --ignore-cr-at-eol --ignore-space-at-eol
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/go
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=go
3 |
4 | ### Go ###
5 | # Binaries for programs and plugins
6 | *.exe
7 | *.exe~
8 | *.dll
9 | *.so
10 | *.dylib
11 |
12 | # Test binary, built with `go test -c`
13 | *.test
14 |
15 | # Output of the go coverage tool, specifically when used with LiteIDE
16 | *.out
17 |
18 | # Dependency directories (remove the comment below to include it)
19 | # vendor/
20 |
21 | ### Go Patch ###
22 | /vendor/
23 | /Godeps/
24 |
25 | # End of https://www.toptal.com/developers/gitignore/api/go
26 | covprofile
27 | .idea/
28 | .DS_Store
29 |
30 | api.json
31 | _site/
32 | .jekyll-cache/
33 |
34 | .vscode/settings.json
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "playwright"]
2 | path = playwright
3 | url = https://github.com/microsoft/playwright
4 |
--------------------------------------------------------------------------------
/.golangci.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | linters:
3 | enable-all: false
4 | disable-all: false
5 | enable:
6 | - gofumpt
--------------------------------------------------------------------------------
/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/.nojekyll
--------------------------------------------------------------------------------
/404.html:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: /404.html
3 | layout: default
4 | ---
5 |
6 |
19 |
20 |
21 |
404
22 |
23 |
Page not found :(
24 |
The requested page could not be found.
25 |
26 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Code style
4 | The Go code is linted with [golangci-lint](https://golangci-lint.run/) and formatted with [gofumpt](https://github.com/mvdan/gofumpt). Please configure your editor to run the tools while developing and make sure to run the tools before committing any code.
5 |
6 | ## Tests
7 |
8 | ### Test coverage
9 |
10 | For every Pull Request on GitHub and on the main branch the coverage data will get sent over to [Coveralls](https://coveralls.io/github/playwright-community/playwright-go), this is helpful for finding functions that aren't covered by tests.
11 |
12 | ### Running tests
13 |
14 | You can use the `BROWSER` environment variable to use a different browser than Chromium for the tests and use the `HEADLESS` environment variable which is useful for debugging.
15 |
16 | ```
17 | BROWSER=chromium HEADLESS=1 go test -v --race ./...
18 | ```
19 |
20 | ### Roll
21 |
22 | 1. Find out to which upstream version you want to roll, and change the value of `playwrightCliVersion` in the **run.go** to the new version.
23 | 1. Download current version of Playwright driver `go run scripts/install-browsers/main.go`
24 | 1. Apply patch `bash scripts/apply-patch.sh`
25 | 1. Fix merge conflicts if any, otherwise ignore this step. Once you are happy you can commit the changes `cd playwright; git commit -am "apply patch" && cd ..`
26 | 1. Regenerate a new patch `bash scripts/update-patch.sh`
27 | 1. Generate go code `go generate ./...`
28 |
29 | To adapt to the new version of Playwright's protocol and feature updates, you may need to modify the patch. Refer to the following steps:
30 |
31 | 1. Apply patch `bash scripts/apply-patch.sh`
32 | 1. `cd playwright`
33 | 1. Revert the patch`git reset HEAD~1`
34 | 1. Modify the files under `docs/src/api`, etc. as needed. Available references:
35 | - Protocol `packages/protocol/src/protocol.yml`
36 | - [Playwright python](https://github.com/microsoft/playwright-python)
37 | 1. Commit the changes `git commit -am "apply patch"`
38 | 1. Regenerate a new patch `bash scripts/update-patch.sh`
39 | 1. Generate go code `go generate ./...`.
40 |
--------------------------------------------------------------------------------
/Dockerfile.example:
--------------------------------------------------------------------------------
1 | # Stage 1: Modules caching
2 | FROM golang:1.22 as modules
3 | COPY go.mod go.sum /modules/
4 | WORKDIR /modules
5 | RUN go mod download
6 |
7 | # Stage 2: Build
8 | FROM golang:1.22 as builder
9 | COPY --from=modules /go/pkg /go/pkg
10 | COPY . /workdir
11 | WORKDIR /workdir
12 | # Install playwright cli with right version for later use
13 | RUN PWGO_VER=$(grep -oE "playwright-go v\S+" /workdir/go.mod | sed 's/playwright-go //g') \
14 | && go install github.com/playwright-community/playwright-go/cmd/playwright@${PWGO_VER}
15 | # Build your app
16 | RUN GOOS=linux GOARCH=amd64 go build -o /bin/myapp
17 |
18 | # Stage 3: Final
19 | FROM ubuntu:noble
20 | COPY --from=builder /go/bin/playwright /bin/myapp /
21 | RUN apt-get update && apt-get install -y ca-certificates tzdata \
22 | # Install dependencies and all browsers (or specify one)
23 | && /playwright install --with-deps \
24 | && rm -rf /var/lib/apt/lists/*
25 | CMD ["/myapp"]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Max Schmitt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | title: Playwright for Go
2 | email: max@schmitt.mx
3 | description: >- # this means to ignore newlines until "baseurl:"
4 | Playwright is a Node.js library to automate Chromium, Firefox and WebKit with a single API.
5 | Playwright is built to enable cross-browser web automation that is ever-green, capable, reliable and fast.
6 | baseurl: "/playwright-go"
7 | url: "https://playwright-community.github.io/"
8 | twitter_username: maxibanki
9 | github_username: playwright-community
10 | remote_theme: pages-themes/cayman@v0.2.0
11 | plugins:
12 | - jekyll-remote-theme
13 | - jekyll-optional-front-matter
14 | - jekyll-readme-index
15 | exclude:
16 | - playwright/
17 | defaults:
18 | - scope:
19 | path: "" # an empty string here means all files in the project
20 | values:
21 | layout: "default"
22 |
--------------------------------------------------------------------------------
/apiresponse_assertions.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "regexp"
7 | "strings"
8 | )
9 |
10 | type apiResponseAssertionsImpl struct {
11 | actual APIResponse
12 | isNot bool
13 | }
14 |
15 | func newAPIResponseAssertions(actual APIResponse, isNot bool) *apiResponseAssertionsImpl {
16 | return &apiResponseAssertionsImpl{
17 | actual: actual,
18 | isNot: isNot,
19 | }
20 | }
21 |
22 | func (ar *apiResponseAssertionsImpl) Not() APIResponseAssertions {
23 | return newAPIResponseAssertions(ar.actual, true)
24 | }
25 |
26 | func (ar *apiResponseAssertionsImpl) ToBeOK() error {
27 | if ar.isNot != ar.actual.Ok() {
28 | return nil
29 | }
30 | message := fmt.Sprintf(`Response status expected to be within [200..299] range, was %v`, ar.actual.Status())
31 | if ar.isNot {
32 | message = strings.ReplaceAll(message, "expected to", "expected not to")
33 | }
34 | logList, err := ar.actual.(*apiResponseImpl).fetchLog()
35 | if err != nil {
36 | return err
37 | }
38 | log := strings.Join(logList, "\n")
39 | if log != "" {
40 | message += "\nCall log:\n" + log
41 | }
42 |
43 | isTextEncoding := false
44 | contentType, ok := ar.actual.Headers()["content-type"]
45 | if ok {
46 | isTextEncoding = isTexualMimeType(contentType)
47 | }
48 | if isTextEncoding {
49 | text, err := ar.actual.Text()
50 | if err == nil {
51 | message += fmt.Sprintf(`\n Response Text:\n %s`, subString(text, 0, 1000))
52 | }
53 | }
54 | return errors.New(message)
55 | }
56 |
57 | func isTexualMimeType(mimeType string) bool {
58 | re := regexp.MustCompile(`^(text\/.*?|application\/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image\/svg(\+xml)?|application\/.*?(\+json|\+xml))(;\s*charset=.*)?$`)
59 | return re.MatchString(mimeType)
60 | }
61 |
62 | func subString(s string, start, length int) string {
63 | if start < 0 {
64 | start = 0
65 | }
66 | if length < 0 {
67 | length = 0
68 | }
69 | rs := []rune(s)
70 | end := start + length
71 | if end > len(rs) {
72 | end = len(rs)
73 | }
74 | return string(rs[start:end])
75 | }
76 |
--------------------------------------------------------------------------------
/artifact.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | )
7 |
8 | type artifactImpl struct {
9 | channelOwner
10 | }
11 |
12 | func (a *artifactImpl) AbsolutePath() string {
13 | return a.initializer["absolutePath"].(string)
14 | }
15 |
16 | func (a *artifactImpl) PathAfterFinished() (string, error) {
17 | if a.connection.isRemote {
18 | return "", errors.New("Path is not available when connecting remotely. Use SaveAs() to save a local copy")
19 | }
20 | path, err := a.channel.Send("pathAfterFinished")
21 | return path.(string), err
22 | }
23 |
24 | func (a *artifactImpl) SaveAs(path string) error {
25 | if !a.connection.isRemote {
26 | _, err := a.channel.Send("saveAs", map[string]interface{}{
27 | "path": path,
28 | })
29 | return err
30 | }
31 | streamChannel, err := a.channel.Send("saveAsStream")
32 | if err != nil {
33 | return err
34 | }
35 | stream := fromChannel(streamChannel).(*streamImpl)
36 | return stream.SaveAs(path)
37 | }
38 |
39 | func (a *artifactImpl) Failure() error {
40 | reason, err := a.channel.Send("failure")
41 | if reason == nil {
42 | return err
43 | }
44 | return fmt.Errorf("%w: %v", ErrPlaywright, reason)
45 | }
46 |
47 | func (a *artifactImpl) Delete() error {
48 | _, err := a.channel.Send("delete")
49 | return err
50 | }
51 |
52 | func (a *artifactImpl) Cancel() error {
53 | _, err := a.channel.Send("cancel")
54 | return err
55 | }
56 |
57 | func (a *artifactImpl) ReadIntoBuffer() ([]byte, error) {
58 | streamChannel, err := a.channel.Send("stream")
59 | if err != nil {
60 | return nil, err
61 | }
62 | stream := fromChannel(streamChannel)
63 | return stream.(*streamImpl).ReadAll()
64 | }
65 |
66 | func newArtifact(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *artifactImpl {
67 | artifact := &artifactImpl{}
68 | artifact.createChannelOwner(artifact, parent, objectType, guid, initializer)
69 | return artifact
70 | }
71 |
--------------------------------------------------------------------------------
/binding_call.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/go-stack/stack"
8 | )
9 |
10 | type BindingCall interface {
11 | Call(f BindingCallFunction)
12 | }
13 |
14 | type bindingCallImpl struct {
15 | channelOwner
16 | }
17 |
18 | // BindingSource is the value passed to a binding call execution
19 | type BindingSource struct {
20 | Context BrowserContext
21 | Page Page
22 | Frame Frame
23 | }
24 |
25 | // ExposedFunction represents the func signature of an exposed function
26 | type ExposedFunction = func(args ...interface{}) interface{}
27 |
28 | // BindingCallFunction represents the func signature of an exposed binding call func
29 | type BindingCallFunction func(source *BindingSource, args ...interface{}) interface{}
30 |
31 | func (b *bindingCallImpl) Call(f BindingCallFunction) {
32 | defer func() {
33 | if r := recover(); r != nil {
34 | if _, err := b.channel.Send("reject", map[string]interface{}{
35 | "error": serializeError(r.(error)),
36 | }); err != nil {
37 | logger.Error("could not reject BindingCall", "error", err)
38 | }
39 | }
40 | }()
41 |
42 | frame := fromChannel(b.initializer["frame"]).(*frameImpl)
43 | source := &BindingSource{
44 | Context: frame.Page().Context(),
45 | Page: frame.Page(),
46 | Frame: frame,
47 | }
48 | var result interface{}
49 | if handle, ok := b.initializer["handle"]; ok {
50 | result = f(source, fromChannel(handle))
51 | } else {
52 | initializerArgs := b.initializer["args"].([]interface{})
53 | funcArgs := []interface{}{}
54 | for i := 0; i < len(initializerArgs); i++ {
55 | funcArgs = append(funcArgs, parseResult(initializerArgs[i]))
56 | }
57 | result = f(source, funcArgs...)
58 | }
59 | _, err := b.channel.Send("resolve", map[string]interface{}{
60 | "result": serializeArgument(result),
61 | })
62 | if err != nil {
63 | logger.Error("could not resolve BindingCall", "error", err)
64 | }
65 | }
66 |
67 | func serializeError(err error) map[string]interface{} {
68 | st := stack.Trace().TrimRuntime()
69 | if len(st) == 0 { // https://github.com/go-stack/stack/issues/27
70 | st = stack.Trace()
71 | }
72 | return map[string]interface{}{
73 | "error": &Error{
74 | Name: "Playwright for Go Error",
75 | Message: err.Error(),
76 | Stack: strings.ReplaceAll(strings.TrimFunc(fmt.Sprintf("%+v", st), func(r rune) bool {
77 | return r == '[' || r == ']'
78 | }), " ", "\n"),
79 | },
80 | }
81 | }
82 |
83 | func newBindingCall(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *bindingCallImpl {
84 | bt := &bindingCallImpl{}
85 | bt.createChannelOwner(bt, parent, objectType, guid, initializer)
86 | return bt
87 | }
88 |
--------------------------------------------------------------------------------
/cdp_session.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | type cdpSessionImpl struct {
4 | channelOwner
5 | }
6 |
7 | func (c *cdpSessionImpl) Detach() error {
8 | _, err := c.channel.Send("detach")
9 | return err
10 | }
11 |
12 | func (c *cdpSessionImpl) Send(method string, params map[string]interface{}) (interface{}, error) {
13 | result, err := c.channel.Send("send", map[string]interface{}{
14 | "method": method,
15 | "params": params,
16 | })
17 | if err != nil {
18 | return nil, err
19 | }
20 |
21 | return result, err
22 | }
23 |
24 | func (c *cdpSessionImpl) onEvent(params map[string]interface{}) {
25 | c.Emit(params["method"].(string), params["params"])
26 | }
27 |
28 | func newCDPSession(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *cdpSessionImpl {
29 | bt := &cdpSessionImpl{}
30 |
31 | bt.createChannelOwner(bt, parent, objectType, guid, initializer)
32 |
33 | bt.channel.On("event", func(params map[string]interface{}) {
34 | bt.onEvent(params)
35 | })
36 |
37 | return bt
38 | }
39 |
--------------------------------------------------------------------------------
/channel.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | type channel struct {
9 | eventEmitter
10 | guid string
11 | connection *connection
12 | owner *channelOwner // to avoid type conversion
13 | object interface{} // retain type info (for fromChannel needed)
14 | }
15 |
16 | func (c *channel) MarshalJSON() ([]byte, error) {
17 | return json.Marshal(map[string]string{
18 | "guid": c.guid,
19 | })
20 | }
21 |
22 | // for catch errors of route handlers etc.
23 | func (c *channel) CreateTask(fn func()) {
24 | go func() {
25 | defer func() {
26 | if e := recover(); e != nil {
27 | err, ok := e.(error)
28 | if ok {
29 | c.connection.err.Set(err)
30 | } else {
31 | c.connection.err.Set(fmt.Errorf("%v", e))
32 | }
33 | }
34 | }()
35 | fn()
36 | }()
37 | }
38 |
39 | func (c *channel) Send(method string, options ...interface{}) (interface{}, error) {
40 | return c.connection.WrapAPICall(func() (interface{}, error) {
41 | return c.innerSend(method, options...).GetResultValue()
42 | }, c.owner.isInternalType)
43 | }
44 |
45 | func (c *channel) SendReturnAsDict(method string, options ...interface{}) (map[string]interface{}, error) {
46 | ret, err := c.connection.WrapAPICall(func() (interface{}, error) {
47 | return c.innerSend(method, options...).GetResult()
48 | }, c.owner.isInternalType)
49 | return ret.(map[string]interface{}), err
50 | }
51 |
52 | func (c *channel) innerSend(method string, options ...interface{}) *protocolCallback {
53 | if err := c.connection.err.Get(); err != nil {
54 | c.connection.err.Set(nil)
55 | pc := newProtocolCallback(false, c.connection.abort)
56 | pc.SetError(err)
57 | return pc
58 | }
59 | params := transformOptions(options...)
60 | return c.connection.sendMessageToServer(c.owner, method, params, false)
61 | }
62 |
63 | // SendNoReply ignores return value and errors
64 | // almost equivalent to `send(...).catch(() => {})`
65 | func (c *channel) SendNoReply(method string, options ...interface{}) {
66 | c.innerSendNoReply(method, c.owner.isInternalType, options...)
67 | }
68 |
69 | func (c *channel) SendNoReplyInternal(method string, options ...interface{}) {
70 | c.innerSendNoReply(method, true, options...)
71 | }
72 |
73 | func (c *channel) innerSendNoReply(method string, isInternal bool, options ...interface{}) {
74 | params := transformOptions(options...)
75 | _, err := c.connection.WrapAPICall(func() (interface{}, error) {
76 | return c.connection.sendMessageToServer(c.owner, method, params, true).GetResult()
77 | }, isInternal)
78 | if err != nil {
79 | // ignore error actively, log only for debug
80 | logger.Error("SendNoReply failed", "error", err)
81 | }
82 | }
83 |
84 | func newChannel(owner *channelOwner, object interface{}) *channel {
85 | channel := &channel{
86 | connection: owner.connection,
87 | guid: owner.guid,
88 | owner: owner,
89 | object: object,
90 | }
91 | return channel
92 | }
93 |
--------------------------------------------------------------------------------
/clock.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | import (
4 | "errors"
5 | "time"
6 | )
7 |
8 | type clockImpl struct {
9 | browserCtx *browserContextImpl
10 | }
11 |
12 | func newClock(bCtx *browserContextImpl) Clock {
13 | return &clockImpl{
14 | browserCtx: bCtx,
15 | }
16 | }
17 |
18 | func (c *clockImpl) FastForward(ticks interface{}) error {
19 | params, err := parseTicks(ticks)
20 | if err != nil {
21 | return err
22 | }
23 |
24 | _, err = c.browserCtx.channel.Send("clockFastForward", params)
25 | return err
26 | }
27 |
28 | func (c *clockImpl) Install(options ...ClockInstallOptions) (err error) {
29 | params := map[string]any{}
30 | if len(options) == 1 {
31 | if options[0].Time != nil {
32 | params, err = parseTime(options[0].Time)
33 | if err != nil {
34 | return err
35 | }
36 | }
37 | }
38 |
39 | _, err = c.browserCtx.channel.Send("clockInstall", params)
40 |
41 | return err
42 | }
43 |
44 | func (c *clockImpl) PauseAt(time interface{}) error {
45 | params, err := parseTime(time)
46 | if err != nil {
47 | return err
48 | }
49 |
50 | _, err = c.browserCtx.channel.Send("clockPauseAt", params)
51 | return err
52 | }
53 |
54 | func (c *clockImpl) Resume() error {
55 | _, err := c.browserCtx.channel.Send("clockResume")
56 | return err
57 | }
58 |
59 | func (c *clockImpl) RunFor(ticks interface{}) error {
60 | params, err := parseTicks(ticks)
61 | if err != nil {
62 | return err
63 | }
64 |
65 | _, err = c.browserCtx.channel.Send("clockRunFor", params)
66 | return err
67 | }
68 |
69 | func (c *clockImpl) SetFixedTime(time interface{}) error {
70 | params, err := parseTime(time)
71 | if err != nil {
72 | return err
73 | }
74 |
75 | _, err = c.browserCtx.channel.Send("clockSetFixedTime", params)
76 | return err
77 | }
78 |
79 | func (c *clockImpl) SetSystemTime(time interface{}) error {
80 | params, err := parseTime(time)
81 | if err != nil {
82 | return err
83 | }
84 |
85 | _, err = c.browserCtx.channel.Send("clockSetSystemTime", params)
86 | return err
87 | }
88 |
89 | func parseTime(t interface{}) (map[string]any, error) {
90 | switch v := t.(type) {
91 | case int, int64:
92 | return map[string]any{"timeNumber": v}, nil
93 | case string:
94 | return map[string]any{"timeString": v}, nil
95 | case time.Time:
96 | return map[string]any{"timeNumber": v.UnixMilli()}, nil
97 | default:
98 | return nil, errors.New("time should be one of: int, int64, string, time.Time")
99 | }
100 | }
101 |
102 | func parseTicks(ticks interface{}) (map[string]any, error) {
103 | switch v := ticks.(type) {
104 | case int, int64:
105 | return map[string]any{"ticksNumber": v}, nil
106 | case string:
107 | return map[string]any{"ticksString": v}, nil
108 | default:
109 | return nil, errors.New("ticks should be one of: int, int64, string")
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/cmd/playwright/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 |
7 | "github.com/playwright-community/playwright-go"
8 | )
9 |
10 | func main() {
11 | driver, err := playwright.NewDriver(&playwright.RunOptions{})
12 | if err != nil {
13 | log.Fatalf("could not start driver: %v", err)
14 | }
15 | if err = driver.DownloadDriver(); err != nil {
16 | log.Fatalf("could not download driver: %v", err)
17 | }
18 | cmd := driver.Command(os.Args[1:]...)
19 | cmd.Stdout = os.Stdout
20 | cmd.Stderr = os.Stderr
21 | if err := cmd.Run(); err != nil {
22 | log.Fatalf("could not run driver: %v", err)
23 | }
24 | os.Exit(cmd.ProcessState.ExitCode())
25 | }
26 |
--------------------------------------------------------------------------------
/console_message.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | type consoleMessageImpl struct {
4 | event map[string]interface{}
5 | page Page
6 | }
7 |
8 | func (c *consoleMessageImpl) Type() string {
9 | return c.event["type"].(string)
10 | }
11 |
12 | func (c *consoleMessageImpl) Text() string {
13 | return c.event["text"].(string)
14 | }
15 |
16 | func (c *consoleMessageImpl) String() string {
17 | return c.Text()
18 | }
19 |
20 | func (c *consoleMessageImpl) Args() []JSHandle {
21 | args := c.event["args"].([]interface{})
22 | out := []JSHandle{}
23 | for idx := range args {
24 | out = append(out, fromChannel(args[idx]).(*jsHandleImpl))
25 | }
26 | return out
27 | }
28 |
29 | func (c *consoleMessageImpl) Location() *ConsoleMessageLocation {
30 | location := &ConsoleMessageLocation{}
31 | remapMapToStruct(c.event["location"], location)
32 | return location
33 | }
34 |
35 | func (c *consoleMessageImpl) Page() Page {
36 | return c.page
37 | }
38 |
39 | func newConsoleMessage(event map[string]interface{}) *consoleMessageImpl {
40 | bt := &consoleMessageImpl{}
41 | bt.event = event
42 | page := fromNullableChannel(event["page"])
43 | if page != nil {
44 | bt.page = page.(*pageImpl)
45 | }
46 | return bt
47 | }
48 |
--------------------------------------------------------------------------------
/dialog.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | type dialogImpl struct {
4 | channelOwner
5 | page Page
6 | }
7 |
8 | func (d *dialogImpl) Type() string {
9 | return d.initializer["type"].(string)
10 | }
11 |
12 | func (d *dialogImpl) Message() string {
13 | return d.initializer["message"].(string)
14 | }
15 |
16 | func (d *dialogImpl) DefaultValue() string {
17 | return d.initializer["defaultValue"].(string)
18 | }
19 |
20 | func (d *dialogImpl) Accept(promptTextInput ...string) error {
21 | var promptText *string
22 | if len(promptTextInput) == 1 {
23 | promptText = &promptTextInput[0]
24 | }
25 | _, err := d.channel.Send("accept", map[string]interface{}{
26 | "promptText": promptText,
27 | })
28 | return err
29 | }
30 |
31 | func (d *dialogImpl) Dismiss() error {
32 | _, err := d.channel.Send("dismiss")
33 | return err
34 | }
35 |
36 | func (d *dialogImpl) Page() Page {
37 | return d.page
38 | }
39 |
40 | func newDialog(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *dialogImpl {
41 | bt := &dialogImpl{}
42 | bt.createChannelOwner(bt, parent, objectType, guid, initializer)
43 | page := fromNullableChannel(initializer["page"])
44 | if page != nil {
45 | bt.page = page.(*pageImpl)
46 | }
47 | return bt
48 | }
49 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | _site/
2 | .jekyll-cache/
3 |
--------------------------------------------------------------------------------
/docs/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "jekyll", "~> 4.3.0"
4 | gem "jekyll-remote-theme"
5 | group :jekyll_plugins do
6 | gem 'jekyll-optional-front-matter'
7 | gem "jekyll-readme-index"
8 | gem "jekyll-seo-tag"
9 | end
--------------------------------------------------------------------------------
/docs/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | addressable (2.8.7)
5 | public_suffix (>= 2.0.2, < 7.0)
6 | bigdecimal (3.1.8)
7 | colorator (1.1.0)
8 | concurrent-ruby (1.3.4)
9 | em-websocket (0.5.3)
10 | eventmachine (>= 0.12.9)
11 | http_parser.rb (~> 0)
12 | eventmachine (1.2.7)
13 | ffi (1.17.0-arm64-darwin)
14 | ffi (1.17.0-x86_64-darwin)
15 | ffi (1.17.0-x86_64-linux-gnu)
16 | forwardable-extended (2.6.0)
17 | google-protobuf (4.28.3-arm64-darwin)
18 | bigdecimal
19 | rake (>= 13)
20 | google-protobuf (4.28.3-x86_64-darwin)
21 | bigdecimal
22 | rake (>= 13)
23 | google-protobuf (4.28.3-x86_64-linux)
24 | bigdecimal
25 | rake (>= 13)
26 | http_parser.rb (0.8.0)
27 | i18n (1.14.6)
28 | concurrent-ruby (~> 1.0)
29 | jekyll (4.3.4)
30 | addressable (~> 2.4)
31 | colorator (~> 1.0)
32 | em-websocket (~> 0.5)
33 | i18n (~> 1.0)
34 | jekyll-sass-converter (>= 2.0, < 4.0)
35 | jekyll-watch (~> 2.0)
36 | kramdown (~> 2.3, >= 2.3.1)
37 | kramdown-parser-gfm (~> 1.0)
38 | liquid (~> 4.0)
39 | mercenary (>= 0.3.6, < 0.5)
40 | pathutil (~> 0.9)
41 | rouge (>= 3.0, < 5.0)
42 | safe_yaml (~> 1.0)
43 | terminal-table (>= 1.8, < 4.0)
44 | webrick (~> 1.7)
45 | jekyll-optional-front-matter (0.3.2)
46 | jekyll (>= 3.0, < 5.0)
47 | jekyll-readme-index (0.3.0)
48 | jekyll (>= 3.0, < 5.0)
49 | jekyll-remote-theme (0.4.3)
50 | addressable (~> 2.0)
51 | jekyll (>= 3.5, < 5.0)
52 | jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
53 | rubyzip (>= 1.3.0, < 3.0)
54 | jekyll-sass-converter (3.0.0)
55 | sass-embedded (~> 1.54)
56 | jekyll-seo-tag (2.8.0)
57 | jekyll (>= 3.8, < 5.0)
58 | jekyll-watch (2.2.1)
59 | listen (~> 3.0)
60 | kramdown (2.4.0)
61 | rexml
62 | kramdown-parser-gfm (1.1.0)
63 | kramdown (~> 2.0)
64 | liquid (4.0.4)
65 | listen (3.9.0)
66 | rb-fsevent (~> 0.10, >= 0.10.3)
67 | rb-inotify (~> 0.9, >= 0.9.10)
68 | mercenary (0.4.0)
69 | pathutil (0.16.2)
70 | forwardable-extended (~> 2.6)
71 | public_suffix (6.0.1)
72 | rake (13.2.1)
73 | rb-fsevent (0.11.2)
74 | rb-inotify (0.11.1)
75 | ffi (~> 1.0)
76 | rexml (3.3.9)
77 | rouge (4.4.0)
78 | rubyzip (2.3.2)
79 | safe_yaml (1.0.5)
80 | sass-embedded (1.80.4-arm64-darwin)
81 | google-protobuf (~> 4.28)
82 | sass-embedded (1.80.4-x86_64-darwin)
83 | google-protobuf (~> 4.28)
84 | sass-embedded (1.80.4-x86_64-linux-gnu)
85 | google-protobuf (~> 4.28)
86 | terminal-table (3.0.2)
87 | unicode-display_width (>= 1.1.1, < 3)
88 | unicode-display_width (2.6.0)
89 | webrick (1.8.2)
90 |
91 | PLATFORMS
92 | universal-darwin-20
93 | x86_64-darwin-24
94 | x86_64-linux
95 |
96 | DEPENDENCIES
97 | jekyll (~> 4.3.0)
98 | jekyll-optional-front-matter
99 | jekyll-readme-index
100 | jekyll-remote-theme
101 | jekyll-seo-tag
102 |
103 | BUNDLED WITH
104 | 2.5.16
105 |
--------------------------------------------------------------------------------
/download.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | type downloadImpl struct {
4 | page *pageImpl
5 | url string
6 | suggestedFilename string
7 | artifact *artifactImpl
8 | }
9 |
10 | func (d *downloadImpl) String() string {
11 | return d.SuggestedFilename()
12 | }
13 |
14 | func (d *downloadImpl) Page() Page {
15 | return d.page
16 | }
17 |
18 | func (d *downloadImpl) URL() string {
19 | return d.url
20 | }
21 |
22 | func (d *downloadImpl) SuggestedFilename() string {
23 | return d.suggestedFilename
24 | }
25 |
26 | func (d *downloadImpl) Delete() error {
27 | err := d.artifact.Delete()
28 | return err
29 | }
30 |
31 | func (d *downloadImpl) Failure() error {
32 | return d.artifact.Failure()
33 | }
34 |
35 | func (d *downloadImpl) Path() (string, error) {
36 | path, err := d.artifact.PathAfterFinished()
37 | return path, err
38 | }
39 |
40 | func (d *downloadImpl) SaveAs(path string) error {
41 | err := d.artifact.SaveAs(path)
42 | return err
43 | }
44 |
45 | func (d *downloadImpl) Cancel() error {
46 | return d.artifact.Cancel()
47 | }
48 |
49 | func newDownload(page *pageImpl, url string, suggestedFilename string, artifact *artifactImpl) *downloadImpl {
50 | return &downloadImpl{
51 | page: page,
52 | url: url,
53 | suggestedFilename: suggestedFilename,
54 | artifact: artifact,
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/errors.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | )
7 |
8 | var (
9 | // ErrPlaywright wraps all Playwright errors.
10 | // - Use errors.Is to check if the error is a Playwright error.
11 | // - Use errors.As to cast an error to [Error] if you want to access "Stack".
12 | ErrPlaywright = errors.New("playwright")
13 | // ErrTargetClosed usually wraps a reason.
14 | ErrTargetClosed = errors.New("target closed")
15 | // ErrTimeout wraps timeout errors. It can be either Playwright TimeoutError or client timeout.
16 | ErrTimeout = errors.New("timeout")
17 | )
18 |
19 | // Error represents a Playwright error
20 | type Error struct {
21 | Name string `json:"name"`
22 | Message string `json:"message"`
23 | Stack string `json:"stack"`
24 | }
25 |
26 | func (e *Error) Error() string {
27 | return e.Message
28 | }
29 |
30 | func (e *Error) Is(target error) bool {
31 | err, ok := target.(*Error)
32 | if !ok {
33 | return false
34 | }
35 | if err.Name != e.Name {
36 | return false
37 | }
38 | if e.Name != "Error" {
39 | return true // same name and not normal error
40 | }
41 | return e.Message == err.Message
42 | }
43 |
44 | func parseError(err Error) error {
45 | if err.Name == "TimeoutError" {
46 | return fmt.Errorf("%w: %w: %w", ErrPlaywright, ErrTimeout, &err)
47 | } else if err.Name == "TargetClosedError" {
48 | return fmt.Errorf("%w: %w: %w", ErrPlaywright, ErrTargetClosed, &err)
49 | }
50 | return fmt.Errorf("%w: %w", ErrPlaywright, &err)
51 | }
52 |
53 | func targetClosedError(reason *string) error {
54 | if reason == nil {
55 | return ErrTargetClosed
56 | }
57 | return fmt.Errorf("%w: %s", ErrTargetClosed, *reason)
58 | }
59 |
--------------------------------------------------------------------------------
/examples/download/main.go:
--------------------------------------------------------------------------------
1 | //go:build ignore
2 | // +build ignore
3 |
4 | package main
5 |
6 | import (
7 | "fmt"
8 | "log"
9 | "net/http"
10 |
11 | "github.com/playwright-community/playwright-go"
12 | )
13 |
14 | func assertErrorToNilf(message string, err error) {
15 | if err != nil {
16 | log.Fatalf(message, err)
17 | }
18 | }
19 |
20 | func main() {
21 | startHttpServer()
22 |
23 | pw, err := playwright.Run()
24 | assertErrorToNilf("could not launch playwright: %w", err)
25 | browser, err := pw.Chromium.Launch(playwright.BrowserTypeLaunchOptions{
26 | Headless: playwright.Bool(true),
27 | })
28 | assertErrorToNilf("could not launch Chromium: %w", err)
29 | context, err := browser.NewContext()
30 | assertErrorToNilf("could not create context: %w", err)
31 | page, err := context.NewPage()
32 | assertErrorToNilf("could not create page: %w", err)
33 | _, err = page.Goto("http://localhost:1234")
34 | assertErrorToNilf("could not goto: %w", err)
35 | assertErrorToNilf("could not set content: %w", page.SetContent(`download`))
36 | download, err := page.ExpectDownload(func() error {
37 | return page.Locator("text=download").Click()
38 | })
39 | assertErrorToNilf("could not download: %w", err)
40 | fmt.Println(download.SuggestedFilename())
41 | assertErrorToNilf("could not close browser: %w", browser.Close())
42 | assertErrorToNilf("could not stop Playwright: %w", pw.Stop())
43 | }
44 |
45 | func startHttpServer() {
46 | http.HandleFunc("/download", func(w http.ResponseWriter, r *http.Request) {
47 | w.Header().Add("Content-Type", "application/octet-stream")
48 | w.Header().Add("Content-Disposition", "attachment; filename=file.txt")
49 | if _, err := w.Write([]byte("foobar")); err != nil {
50 | log.Printf("could not write: %v", err)
51 | }
52 | })
53 | go func() {
54 | log.Fatal(http.ListenAndServe(":1234", nil))
55 | }()
56 | }
57 |
--------------------------------------------------------------------------------
/examples/javascript/main.go:
--------------------------------------------------------------------------------
1 | //go:build ignore
2 | // +build ignore
3 |
4 | package main
5 |
6 | import (
7 | "fmt"
8 | "log"
9 |
10 | "github.com/playwright-community/playwright-go"
11 | )
12 |
13 | func main() {
14 | pw, err := playwright.Run()
15 | if err != nil {
16 | log.Fatal(err)
17 | }
18 | browser, err := pw.Chromium.Launch()
19 | if err != nil {
20 | log.Fatalf("could not launch browser: %v\n", err)
21 | }
22 | page, err := browser.NewPage()
23 | if err != nil {
24 | log.Fatalf("could not create page: %v\n", err)
25 | }
26 | if _, err = page.Goto("https://en.wikipedia.org/wiki/JavaScript"); err != nil {
27 | log.Fatalf("could not goto: %v\n", err)
28 | }
29 | // mw.config.values is the JS object where Wikipedia stores wiki metadata
30 | handle, err := page.EvaluateHandle("mw.config.values", struct{}{})
31 | if err != nil {
32 | log.Fatalf("could not acquire JSHandle: %v\n", err)
33 | }
34 | // mw.config.values.wgPageName is the name of the current page
35 | pageName, err := handle.GetProperty("wgPageName")
36 | if err != nil {
37 | log.Fatalf("could not get Wikipedia page name: %v\n", err)
38 | }
39 |
40 | fmt.Printf("Lots of type casting, brought to you by %s\n", pageName)
41 |
42 | if err := browser.Close(); err != nil {
43 | log.Fatalf("could not close browser: %v\n", err)
44 | }
45 | if err := pw.Stop(); err != nil {
46 | log.Fatalf("could not stop Playwright: %v\n", err)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/examples/mobile-and-geolocation/main.go:
--------------------------------------------------------------------------------
1 | //go:build ignore
2 | // +build ignore
3 |
4 | package main
5 |
6 | import (
7 | "log"
8 |
9 | "github.com/playwright-community/playwright-go"
10 | )
11 |
12 | func main() {
13 | pw, err := playwright.Run()
14 | if err != nil {
15 | log.Fatalf("could not start playwright: %v", err)
16 | }
17 | browser, err := pw.Chromium.Launch()
18 | if err != nil {
19 | log.Fatalf("could not launch browser: %v", err)
20 | }
21 | device := pw.Devices["Pixel 5"]
22 | context, err := browser.NewContext(playwright.BrowserNewContextOptions{
23 | Geolocation: &playwright.Geolocation{
24 | Longitude: 12.492507,
25 | Latitude: 41.889938,
26 | },
27 | Permissions: []string{"geolocation"},
28 | Viewport: device.Viewport,
29 | UserAgent: playwright.String(device.UserAgent),
30 | DeviceScaleFactor: playwright.Float(device.DeviceScaleFactor),
31 | IsMobile: playwright.Bool(device.IsMobile),
32 | HasTouch: playwright.Bool(device.HasTouch),
33 | })
34 | if err != nil {
35 | log.Fatalf("could not create context: %v", err)
36 | }
37 | page, err := context.NewPage()
38 | if err != nil {
39 | log.Fatalf("could not create page: %v", err)
40 | }
41 | if _, err = page.Goto("https://www.openstreetmap.org"); err != nil {
42 | log.Fatalf("could not goto: %v", err)
43 | }
44 | if err = page.Locator("a[data-bs-original-title='Show My Location']").Click(); err != nil {
45 | log.Fatalf("could not click on location: %v", err)
46 | }
47 | if _, err = page.Screenshot(playwright.PageScreenshotOptions{
48 | Path: playwright.String("colosseum-iphone.png"),
49 | }); err != nil {
50 | log.Fatalf("could not make screenshot: %v", err)
51 | }
52 | if err = browser.Close(); err != nil {
53 | log.Fatalf("could not close browser: %v", err)
54 | }
55 | if err = pw.Stop(); err != nil {
56 | log.Fatalf("could not stop Playwright: %v", err)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/examples/network-monitoring/main.go:
--------------------------------------------------------------------------------
1 | //go:build ignore
2 | // +build ignore
3 |
4 | package main
5 |
6 | import (
7 | "fmt"
8 | "log"
9 |
10 | "github.com/playwright-community/playwright-go"
11 | )
12 |
13 | func main() {
14 | pw, err := playwright.Run()
15 | if err != nil {
16 | log.Fatalf("could not launch playwright: %v", err)
17 | }
18 | browser, err := pw.Chromium.Launch()
19 | if err != nil {
20 | log.Fatalf("could not launch Chromium: %v", err)
21 | }
22 | page, err := browser.NewPage()
23 | if err != nil {
24 | log.Fatalf("could not create page: %v", err)
25 | }
26 | page.OnRequest(func(request playwright.Request) {
27 | fmt.Printf(">> %s %s\n", request.Method(), request.URL())
28 | })
29 | page.OnResponse(func(response playwright.Response) {
30 | fmt.Printf("<< %v %s\n", response.Status(), response.URL())
31 | })
32 | if _, err = page.Goto("http://todomvc.com/examples/react/", playwright.PageGotoOptions{
33 | WaitUntil: playwright.WaitUntilStateNetworkidle,
34 | }); err != nil {
35 | log.Fatalf("could not goto: %v", err)
36 | }
37 | if err = browser.Close(); err != nil {
38 | log.Fatalf("could not close browser: %v", err)
39 | }
40 | if err = pw.Stop(); err != nil {
41 | log.Fatalf("could not stop Playwright: %v", err)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/pdf/main.go:
--------------------------------------------------------------------------------
1 | //go:build ignore
2 | // +build ignore
3 |
4 | package main
5 |
6 | import (
7 | "log"
8 |
9 | "github.com/playwright-community/playwright-go"
10 | )
11 |
12 | func assertErrorToNilf(message string, err error) {
13 | if err != nil {
14 | log.Fatalf(message, err)
15 | }
16 | }
17 |
18 | func main() {
19 | pw, err := playwright.Run()
20 | assertErrorToNilf("could not launch playwright: %w", err)
21 | browser, err := pw.Chromium.Launch()
22 | assertErrorToNilf("could not launch Chromium: %w", err)
23 | context, err := browser.NewContext()
24 | assertErrorToNilf("could not create context: %w", err)
25 | page, err := context.NewPage()
26 | assertErrorToNilf("could not create page: %w", err)
27 | _, err = page.Goto("https://github.com/microsoft/playwright")
28 | assertErrorToNilf("could not goto: %w", err)
29 | _, err = page.PDF(playwright.PagePdfOptions{
30 | Path: playwright.String("playwright-example.pdf"),
31 | })
32 | assertErrorToNilf("could not create PDF: %w", err)
33 | assertErrorToNilf("could not close browser: %w", browser.Close())
34 | assertErrorToNilf("could not stop Playwright: %w", pw.Stop())
35 | }
36 |
--------------------------------------------------------------------------------
/examples/scraping/main.go:
--------------------------------------------------------------------------------
1 | //go:build ignore
2 | // +build ignore
3 |
4 | package main
5 |
6 | import (
7 | "fmt"
8 | "log"
9 |
10 | "github.com/playwright-community/playwright-go"
11 | )
12 |
13 | func main() {
14 | pw, err := playwright.Run()
15 | if err != nil {
16 | log.Fatalf("could not start playwright: %v", err)
17 | }
18 | browser, err := pw.Chromium.Launch()
19 | if err != nil {
20 | log.Fatalf("could not launch browser: %v", err)
21 | }
22 | page, err := browser.NewPage()
23 | if err != nil {
24 | log.Fatalf("could not create page: %v", err)
25 | }
26 | if _, err = page.Goto("https://news.ycombinator.com"); err != nil {
27 | log.Fatalf("could not goto: %v", err)
28 | }
29 | entries, err := page.Locator(".athing").All()
30 | if err != nil {
31 | log.Fatalf("could not get entries: %v", err)
32 | }
33 | for i, entry := range entries {
34 | title, err := entry.Locator("td.title > span > a").TextContent()
35 | if err != nil {
36 | log.Fatalf("could not get text content: %v", err)
37 | }
38 | fmt.Printf("%d: %s\n", i+1, title)
39 | }
40 | if err = browser.Close(); err != nil {
41 | log.Fatalf("could not close browser: %v", err)
42 | }
43 | if err = pw.Stop(); err != nil {
44 | log.Fatalf("could not stop Playwright: %v", err)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/examples/screenshot/main.go:
--------------------------------------------------------------------------------
1 | //go:build ignore
2 | // +build ignore
3 |
4 | package main
5 |
6 | import (
7 | "log"
8 |
9 | "github.com/playwright-community/playwright-go"
10 | )
11 |
12 | func main() {
13 | pw, err := playwright.Run()
14 | if err != nil {
15 | log.Fatalf("could not launch playwright: %v", err)
16 | }
17 | browser, err := pw.Chromium.Launch()
18 | if err != nil {
19 | log.Fatalf("could not launch Chromium: %v", err)
20 | }
21 | page, err := browser.NewPage()
22 | if err != nil {
23 | log.Fatalf("could not create page: %v", err)
24 | }
25 | if _, err = page.Goto("https://playwright.dev/", playwright.PageGotoOptions{
26 | WaitUntil: playwright.WaitUntilStateDomcontentloaded,
27 | }); err != nil {
28 | log.Fatalf("could not goto: %v", err)
29 | }
30 | if _, err = page.Screenshot(playwright.PageScreenshotOptions{
31 | Path: playwright.String("foo.png"),
32 | }); err != nil {
33 | log.Fatalf("could not create screenshot: %v", err)
34 | }
35 | if err = browser.Close(); err != nil {
36 | log.Fatalf("could not close browser: %v", err)
37 | }
38 | if err = pw.Stop(); err != nil {
39 | log.Fatalf("could not stop Playwright: %v", err)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/examples/use-local-chrome/main.go:
--------------------------------------------------------------------------------
1 | //go:build ignore
2 | // +build ignore
3 |
4 | package main
5 |
6 | import (
7 | "fmt"
8 | "log"
9 |
10 | "github.com/playwright-community/playwright-go"
11 | )
12 |
13 | func main() {
14 | runOption := &playwright.RunOptions{
15 | SkipInstallBrowsers: true,
16 | }
17 | err := playwright.Install(runOption)
18 | if err != nil {
19 | log.Fatalf("could not install playwright dependencies: %v", err)
20 | }
21 | pw, err := playwright.Run()
22 | if err != nil {
23 | log.Fatalf("could not start playwright: %v", err)
24 | }
25 | option := playwright.BrowserTypeLaunchOptions{
26 | Channel: playwright.String("chrome"),
27 | }
28 | browser, err := pw.Chromium.Launch(option)
29 | if err != nil {
30 | log.Fatalf("could not launch browser: %v", err)
31 | }
32 | page, err := browser.NewPage()
33 | if err != nil {
34 | log.Fatalf("could not create page: %v", err)
35 | }
36 | if _, err = page.Goto("https://news.ycombinator.com"); err != nil {
37 | log.Fatalf("could not goto: %v", err)
38 | }
39 | entries, err := page.Locator(".athing").All()
40 | if err != nil {
41 | log.Fatalf("could not get entries: %v", err)
42 | }
43 | for i, entry := range entries {
44 | title, err := entry.Locator("td.title > span > a").TextContent()
45 | if err != nil {
46 | log.Fatalf("could not get text content: %v", err)
47 | }
48 | fmt.Printf("%d: %s\n", i+1, title)
49 | }
50 | if err = browser.Close(); err != nil {
51 | log.Fatalf("could not close browser: %v", err)
52 | }
53 | if err = pw.Stop(); err != nil {
54 | log.Fatalf("could not stop Playwright: %v", err)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/examples/video/main.go:
--------------------------------------------------------------------------------
1 | //go:build ignore
2 | // +build ignore
3 |
4 | package main
5 |
6 | import (
7 | "fmt"
8 | "log"
9 |
10 | "github.com/playwright-community/playwright-go"
11 | )
12 |
13 | func main() {
14 | pw, err := playwright.Run()
15 | if err != nil {
16 | log.Fatalf("could not launch playwright: %v", err)
17 | }
18 | browser, err := pw.Chromium.Launch()
19 | if err != nil {
20 | log.Fatalf("could not launch Chromium: %v", err)
21 | }
22 | page, err := browser.NewPage(playwright.BrowserNewPageOptions{
23 | RecordVideo: &playwright.RecordVideo{
24 | Dir: "videos/",
25 | },
26 | })
27 | if err != nil {
28 | log.Fatalf("could not create page: %v", err)
29 | }
30 | gotoPage := func(url string) {
31 | fmt.Printf("Visiting %s\n", url)
32 | if _, err = page.Goto(url); err != nil {
33 | log.Fatalf("could not goto: %v", err)
34 | }
35 | fmt.Printf("Visited %s\n", url)
36 | }
37 | gotoPage("https://playwright.dev")
38 | gotoPage("https://github.com")
39 | gotoPage("https://microsoft.com")
40 | if err := page.Close(); err != nil {
41 | log.Fatalf("failed to close page: %v", err)
42 | }
43 | path, err := page.Video().Path()
44 | if err != nil {
45 | log.Fatalf("failed to get video path: %v", err)
46 | }
47 | fmt.Printf("Saved to %s\n", path)
48 | if err = browser.Close(); err != nil {
49 | log.Fatalf("could not close browser: %v", err)
50 | }
51 | if err = pw.Stop(); err != nil {
52 | log.Fatalf("could not stop Playwright: %v", err)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/file_chooser.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | type fileChooserImpl struct {
4 | page Page
5 | elementHandle ElementHandle
6 | isMultiple bool
7 | }
8 |
9 | func (f *fileChooserImpl) Page() Page {
10 | return f.page
11 | }
12 |
13 | func (f *fileChooserImpl) Element() ElementHandle {
14 | return f.elementHandle
15 | }
16 |
17 | func (f *fileChooserImpl) IsMultiple() bool {
18 | return f.isMultiple
19 | }
20 |
21 | // InputFile represents the input file for:
22 | // - FileChooser.SetFiles()
23 | // - ElementHandle.SetInputFiles()
24 | // - Page.SetInputFiles()
25 | type InputFile struct {
26 | Name string `json:"name"`
27 | MimeType string `json:"mimeType,omitempty"`
28 | Buffer []byte `json:"buffer"`
29 | }
30 |
31 | func (f *fileChooserImpl) SetFiles(files interface{}, options ...FileChooserSetFilesOptions) error {
32 | if len(options) == 1 {
33 | return f.elementHandle.SetInputFiles(files, ElementHandleSetInputFilesOptions(options[0]))
34 | }
35 | return f.elementHandle.SetInputFiles(files)
36 | }
37 |
38 | func newFileChooser(page Page, elementHandle ElementHandle, isMultiple bool) *fileChooserImpl {
39 | return &fileChooserImpl{
40 | page: page,
41 | elementHandle: elementHandle,
42 | isMultiple: isMultiple,
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/playwright-community/playwright-go
2 |
3 | go 1.22
4 |
5 | require (
6 | github.com/coder/websocket v1.8.12
7 | github.com/deckarep/golang-set/v2 v2.7.0
8 | github.com/go-jose/go-jose/v3 v3.0.4
9 | github.com/go-stack/stack v1.8.1
10 | github.com/h2non/filetype v1.1.3
11 | github.com/mitchellh/go-ps v1.0.0
12 | github.com/orisano/pixelmatch v0.0.0-20230914042517-fa304d1dc785
13 | github.com/stretchr/testify v1.8.4
14 | github.com/tidwall/gjson v1.17.0
15 | )
16 |
17 | require (
18 | github.com/davecgh/go-spew v1.1.1 // indirect
19 | github.com/kr/pretty v0.3.1 // indirect
20 | github.com/pmezard/go-difflib v1.0.0 // indirect
21 | github.com/rogpeppe/go-internal v1.11.0 // indirect
22 | github.com/tidwall/match v1.1.1 // indirect
23 | github.com/tidwall/pretty v1.2.1 // indirect
24 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
25 | gopkg.in/yaml.v3 v3.0.1 // indirect
26 | )
27 |
--------------------------------------------------------------------------------
/har_router.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | import (
4 | "errors"
5 | )
6 |
7 | type harRouter struct {
8 | localUtils *localUtilsImpl
9 | harId string
10 | notFoundAction HarNotFound
11 | urlOrPredicate interface{}
12 | err error
13 | }
14 |
15 | func (r *harRouter) addContextRoute(context BrowserContext) error {
16 | if r.err != nil {
17 | return r.err
18 | }
19 | err := context.Route(r.urlOrPredicate, func(route Route) {
20 | err := r.handle(route)
21 | if err != nil {
22 | logger.Error("Error handling context route", "error", err)
23 | }
24 | })
25 | if err != nil {
26 | return err
27 | }
28 | return r.err
29 | }
30 |
31 | func (r *harRouter) addPageRoute(page Page) error {
32 | if r.err != nil {
33 | return r.err
34 | }
35 | err := page.Route(r.urlOrPredicate, func(route Route) {
36 | err := r.handle(route)
37 | if err != nil {
38 | logger.Error("Error handling page route", "error", err)
39 | }
40 | })
41 | if err != nil {
42 | return err
43 | }
44 | return r.err
45 | }
46 |
47 | func (r *harRouter) dispose() {
48 | go r.localUtils.HarClose(r.harId)
49 | }
50 |
51 | func (r *harRouter) handle(route Route) error {
52 | if r.err != nil {
53 | return r.err
54 | }
55 | request := route.Request()
56 | postData, err := request.PostDataBuffer()
57 | if err != nil {
58 | return err
59 | }
60 | response, err := r.localUtils.HarLookup(harLookupOptions{
61 | HarId: r.harId,
62 | URL: request.URL(),
63 | Method: request.Method(),
64 | Headers: request.Headers(),
65 | IsNavigationRequest: request.IsNavigationRequest(),
66 | PostData: postData,
67 | })
68 | if err != nil {
69 | return err
70 | }
71 | switch response.Action {
72 | case "redirect":
73 | if response.RedirectURL == nil {
74 | return errors.New("redirect url is null")
75 | }
76 | return route.(*routeImpl).redirectedNavigationRequest(*response.RedirectURL)
77 | case "fulfill":
78 | if response.Body == nil {
79 | return errors.New("fulfill body is null")
80 | }
81 | return route.Fulfill(RouteFulfillOptions{
82 | Body: *response.Body,
83 | Status: response.Status,
84 | Headers: deserializeNameAndValueToMap(response.Headers),
85 | })
86 | case "error":
87 | logger.Error("har action error", "error", *response.Message)
88 | fallthrough
89 | case "noentry":
90 | }
91 | if r.notFoundAction == *HarNotFoundAbort {
92 | return route.Abort()
93 | }
94 | return route.Fallback()
95 | }
96 |
97 | func newHarRouter(localUtils *localUtilsImpl, file string, notFoundAction HarNotFound, urlOrPredicate interface{}) *harRouter {
98 | harId, err := localUtils.HarOpen(file)
99 | var url interface{} = "**/*"
100 | if urlOrPredicate != nil {
101 | url = urlOrPredicate
102 | }
103 | return &harRouter{
104 | localUtils: localUtils,
105 | harId: harId,
106 | notFoundAction: notFoundAction,
107 | urlOrPredicate: url,
108 | err: err,
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/input.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | type mouseImpl struct {
4 | channel *channel
5 | }
6 |
7 | func newMouse(channel *channel) *mouseImpl {
8 | return &mouseImpl{
9 | channel: channel,
10 | }
11 | }
12 |
13 | func (m *mouseImpl) Move(x float64, y float64, options ...MouseMoveOptions) error {
14 | _, err := m.channel.Send("mouseMove", map[string]interface{}{
15 | "x": x,
16 | "y": y,
17 | }, options)
18 | return err
19 | }
20 |
21 | func (m *mouseImpl) Down(options ...MouseDownOptions) error {
22 | _, err := m.channel.Send("mouseDown", options)
23 | return err
24 | }
25 |
26 | func (m *mouseImpl) Up(options ...MouseUpOptions) error {
27 | _, err := m.channel.Send("mouseUp", options)
28 | return err
29 | }
30 |
31 | func (m *mouseImpl) Click(x, y float64, options ...MouseClickOptions) error {
32 | _, err := m.channel.Send("mouseClick", map[string]interface{}{
33 | "x": x,
34 | "y": y,
35 | }, options)
36 | return err
37 | }
38 |
39 | func (m *mouseImpl) Dblclick(x, y float64, options ...MouseDblclickOptions) error {
40 | var option MouseDblclickOptions
41 | if len(options) == 1 {
42 | option = options[0]
43 | }
44 | return m.Click(x, y, MouseClickOptions{
45 | ClickCount: Int(2),
46 | Button: option.Button,
47 | Delay: option.Delay,
48 | })
49 | }
50 |
51 | func (m *mouseImpl) Wheel(deltaX, deltaY float64) error {
52 | _, err := m.channel.Send("mouseWheel", map[string]interface{}{
53 | "deltaX": deltaX,
54 | "deltaY": deltaY,
55 | })
56 | return err
57 | }
58 |
59 | type keyboardImpl struct {
60 | channel *channel
61 | }
62 |
63 | func newKeyboard(channel *channel) *keyboardImpl {
64 | return &keyboardImpl{
65 | channel: channel,
66 | }
67 | }
68 |
69 | func (m *keyboardImpl) Down(key string) error {
70 | _, err := m.channel.Send("keyboardDown", map[string]interface{}{
71 | "key": key,
72 | })
73 | return err
74 | }
75 |
76 | func (m *keyboardImpl) Up(key string) error {
77 | _, err := m.channel.Send("keyboardUp", map[string]interface{}{
78 | "key": key,
79 | })
80 | return err
81 | }
82 |
83 | func (m *keyboardImpl) InsertText(text string) error {
84 | _, err := m.channel.Send("keyboardInsertText", map[string]interface{}{
85 | "text": text,
86 | })
87 | return err
88 | }
89 |
90 | func (m *keyboardImpl) Type(text string, options ...KeyboardTypeOptions) error {
91 | _, err := m.channel.Send("keyboardInsertText", map[string]interface{}{
92 | "text": text,
93 | }, options)
94 | return err
95 | }
96 |
97 | func (m *keyboardImpl) Press(key string, options ...KeyboardPressOptions) error {
98 | _, err := m.channel.Send("keyboardPress", map[string]interface{}{
99 | "key": key,
100 | }, options)
101 | return err
102 | }
103 |
104 | type touchscreenImpl struct {
105 | channel *channel
106 | }
107 |
108 | func newTouchscreen(channel *channel) *touchscreenImpl {
109 | return &touchscreenImpl{
110 | channel: channel,
111 | }
112 | }
113 |
114 | func (t *touchscreenImpl) Tap(x int, y int) error {
115 | _, err := t.channel.Send("touchscreenTap", map[string]interface{}{"x": x, "y": y})
116 | return err
117 | }
118 |
--------------------------------------------------------------------------------
/internal/safe/map.go:
--------------------------------------------------------------------------------
1 | package safe
2 |
3 | import (
4 | "maps"
5 | "sync"
6 | )
7 |
8 | // SyncMap is a thread-safe map
9 | type SyncMap[K comparable, V any] struct {
10 | sync.RWMutex
11 | m map[K]V
12 | }
13 |
14 | // NewSyncMap creates a new thread-safe map
15 | func NewSyncMap[K comparable, V any]() *SyncMap[K, V] {
16 | return &SyncMap[K, V]{
17 | m: make(map[K]V),
18 | }
19 | }
20 |
21 | func (m *SyncMap[K, V]) Store(k K, v V) {
22 | m.Lock()
23 | defer m.Unlock()
24 | m.m[k] = v
25 | }
26 |
27 | func (m *SyncMap[K, V]) Load(k K) (v V, ok bool) {
28 | m.RLock()
29 | defer m.RUnlock()
30 | v, ok = m.m[k]
31 | return
32 | }
33 |
34 | // LoadOrStore returns the existing value for the key if present. Otherwise, it stores and returns the given value.
35 | func (m *SyncMap[K, V]) LoadOrStore(k K, v V) (actual V, loaded bool) {
36 | m.Lock()
37 | defer m.Unlock()
38 | actual, loaded = m.m[k]
39 | if loaded {
40 | return
41 | }
42 | m.m[k] = v
43 | return v, false
44 | }
45 |
46 | // LoadAndDelete deletes the value for a key, and returns the previous value if any.
47 | func (m *SyncMap[K, V]) LoadAndDelete(k K) (v V, loaded bool) {
48 | m.Lock()
49 | defer m.Unlock()
50 | v, loaded = m.m[k]
51 | if loaded {
52 | delete(m.m, k)
53 | }
54 | return
55 | }
56 |
57 | func (m *SyncMap[K, V]) Delete(k K) {
58 | m.Lock()
59 | defer m.Unlock()
60 | delete(m.m, k)
61 | }
62 |
63 | func (m *SyncMap[K, V]) Clear() {
64 | m.Lock()
65 | defer m.Unlock()
66 | clear(m.m)
67 | }
68 |
69 | func (m *SyncMap[K, V]) Len() int {
70 | m.RLock()
71 | defer m.RUnlock()
72 | return len(m.m)
73 | }
74 |
75 | func (m *SyncMap[K, V]) Clone() map[K]V {
76 | m.RLock()
77 | defer m.RUnlock()
78 | return maps.Clone(m.m)
79 | }
80 |
81 | func (m *SyncMap[K, V]) Range(f func(k K, v V) bool) {
82 | m.RLock()
83 | defer m.RUnlock()
84 |
85 | for k, v := range m.m {
86 | if !f(k, v) {
87 | break
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/jsonPipe.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | )
8 |
9 | type jsonPipe struct {
10 | channelOwner
11 | msgChan chan *message
12 | }
13 |
14 | func (j *jsonPipe) Send(message map[string]interface{}) error {
15 | _, err := j.channel.Send("send", map[string]interface{}{
16 | "message": message,
17 | })
18 | return err
19 | }
20 |
21 | func (j *jsonPipe) Close() error {
22 | _, err := j.channel.Send("close")
23 | return err
24 | }
25 |
26 | func (j *jsonPipe) Poll() (*message, error) {
27 | msg := <-j.msgChan
28 | if msg == nil {
29 | return nil, errors.New("jsonPipe closed")
30 | }
31 | return msg, nil
32 | }
33 |
34 | func newJsonPipe(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *jsonPipe {
35 | j := &jsonPipe{
36 | msgChan: make(chan *message, 2),
37 | }
38 | j.createChannelOwner(j, parent, objectType, guid, initializer)
39 | j.channel.On("message", func(ev map[string]interface{}) {
40 | var msg message
41 | m, err := json.Marshal(ev["message"])
42 | if err == nil {
43 | err = json.Unmarshal(m, &msg)
44 | }
45 | if err != nil {
46 | msg = message{
47 | Error: &struct {
48 | Error Error "json:\"error\""
49 | }{
50 | Error: Error{
51 | Name: "Error",
52 | Message: fmt.Sprintf("jsonPipe: could not decode message: %s", err.Error()),
53 | },
54 | },
55 | }
56 | }
57 | j.msgChan <- &msg
58 | })
59 | j.channel.Once("closed", func() {
60 | j.Emit("closed")
61 | close(j.msgChan)
62 | })
63 | return j
64 | }
65 |
--------------------------------------------------------------------------------
/network.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | type rawHeaders struct {
8 | headersArray []NameValue
9 | headersMap map[string][]string
10 | }
11 |
12 | func (r *rawHeaders) Get(name string) string {
13 | values := r.GetAll(name)
14 | if len(values) == 0 {
15 | return ""
16 | }
17 | sep := ", "
18 | if strings.ToLower(name) == "set-cookie" {
19 | sep = "\n"
20 | }
21 | return strings.Join(values, sep)
22 | }
23 |
24 | func (r *rawHeaders) GetAll(name string) []string {
25 | name = strings.ToLower(name)
26 | if _, ok := r.headersMap[name]; !ok {
27 | return []string{}
28 | }
29 | return r.headersMap[name]
30 | }
31 |
32 | func (r *rawHeaders) Headers() map[string]string {
33 | out := make(map[string]string)
34 | for key := range r.headersMap {
35 | out[key] = r.Get(key)
36 | }
37 | return out
38 | }
39 |
40 | func (r *rawHeaders) HeadersArray() []NameValue {
41 | return r.headersArray
42 | }
43 |
44 | func newRawHeaders(headers interface{}) *rawHeaders {
45 | r := &rawHeaders{}
46 | r.headersArray = make([]NameValue, 0)
47 | r.headersMap = make(map[string][]string)
48 | for _, header := range headers.([]interface{}) {
49 | entry := header.(map[string]interface{})
50 | name := entry["name"].(string)
51 | value := entry["value"].(string)
52 | r.headersArray = append(r.headersArray, NameValue{
53 | Name: name,
54 | Value: value,
55 | })
56 | if _, ok := r.headersMap[strings.ToLower(name)]; !ok {
57 | r.headersMap[strings.ToLower(name)] = make([]string, 0)
58 | }
59 | r.headersMap[strings.ToLower(name)] = append(r.headersMap[strings.ToLower(name)], value)
60 | }
61 | return r
62 | }
63 |
--------------------------------------------------------------------------------
/objectFactory.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | func createObjectFactory(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) interface{} {
4 | switch objectType {
5 | case "Android":
6 | return nil
7 | case "AndroidSocket":
8 | return nil
9 | case "AndroidDevice":
10 | return nil
11 | case "APIRequestContext":
12 | return newAPIRequestContext(parent, objectType, guid, initializer)
13 | case "Artifact":
14 | return newArtifact(parent, objectType, guid, initializer)
15 | case "BindingCall":
16 | return newBindingCall(parent, objectType, guid, initializer)
17 | case "Browser":
18 | return newBrowser(parent, objectType, guid, initializer)
19 | case "BrowserType":
20 | return newBrowserType(parent, objectType, guid, initializer)
21 | case "BrowserContext":
22 | return newBrowserContext(parent, objectType, guid, initializer)
23 | case "CDPSession":
24 | return newCDPSession(parent, objectType, guid, initializer)
25 | case "Dialog":
26 | return newDialog(parent, objectType, guid, initializer)
27 | case "Electron":
28 | return nil
29 | case "ElectronApplication":
30 | return nil
31 | case "ElementHandle":
32 | return newElementHandle(parent, objectType, guid, initializer)
33 | case "Frame":
34 | return newFrame(parent, objectType, guid, initializer)
35 | case "JSHandle":
36 | return newJSHandle(parent, objectType, guid, initializer)
37 | case "JsonPipe":
38 | return newJsonPipe(parent, objectType, guid, initializer)
39 | case "LocalUtils":
40 | localUtils := newLocalUtils(parent, objectType, guid, initializer)
41 | if localUtils.connection.localUtils == nil {
42 | localUtils.connection.localUtils = localUtils
43 | }
44 | return localUtils
45 | case "Page":
46 | return newPage(parent, objectType, guid, initializer)
47 | case "Playwright":
48 | return newPlaywright(parent, objectType, guid, initializer)
49 | case "Request":
50 | return newRequest(parent, objectType, guid, initializer)
51 | case "Response":
52 | return newResponse(parent, objectType, guid, initializer)
53 | case "Route":
54 | return newRoute(parent, objectType, guid, initializer)
55 | case "Selectors":
56 | return newSelectorsOwner(parent, objectType, guid, initializer)
57 | case "SocksSupport":
58 | return nil
59 | case "Stream":
60 | return newStream(parent, objectType, guid, initializer)
61 | case "Tracing":
62 | return newTracing(parent, objectType, guid, initializer)
63 | case "WebSocket":
64 | return newWebsocket(parent, objectType, guid, initializer)
65 | case "WebSocketRoute":
66 | return newWebSocketRoute(parent, objectType, guid, initializer)
67 | case "Worker":
68 | return newWorker(parent, objectType, guid, initializer)
69 | case "WritableStream":
70 | return newWritableStream(parent, objectType, guid, initializer)
71 | default:
72 | panic(objectType)
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/page_assertions.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | import (
4 | "net/url"
5 | "path"
6 | )
7 |
8 | type pageAssertionsImpl struct {
9 | assertionsBase
10 | actualPage Page
11 | }
12 |
13 | func newPageAssertions(page Page, isNot bool, defaultTimeout *float64) *pageAssertionsImpl {
14 | return &pageAssertionsImpl{
15 | assertionsBase: assertionsBase{
16 | actualLocator: page.Locator(":root"),
17 | isNot: isNot,
18 | defaultTimeout: defaultTimeout,
19 | },
20 | actualPage: page,
21 | }
22 | }
23 |
24 | func (pa *pageAssertionsImpl) ToHaveTitle(titleOrRegExp interface{}, options ...PageAssertionsToHaveTitleOptions) error {
25 | var timeout *float64
26 | if len(options) == 1 {
27 | timeout = options[0].Timeout
28 | }
29 | expectedValues, err := toExpectedTextValues([]interface{}{titleOrRegExp}, false, true, nil)
30 | if err != nil {
31 | return err
32 | }
33 | return pa.expect(
34 | "to.have.title",
35 | frameExpectOptions{ExpectedText: expectedValues, Timeout: timeout},
36 | titleOrRegExp,
37 | "Page title expected to be",
38 | )
39 | }
40 |
41 | func (pa *pageAssertionsImpl) ToHaveURL(urlOrRegExp interface{}, options ...PageAssertionsToHaveURLOptions) error {
42 | var timeout *float64
43 | var ignoreCase *bool
44 | if len(options) == 1 {
45 | timeout = options[0].Timeout
46 | ignoreCase = options[0].IgnoreCase
47 | }
48 |
49 | baseURL := pa.actualPage.Context().(*browserContextImpl).options.BaseURL
50 | if urlPath, ok := urlOrRegExp.(string); ok && baseURL != nil {
51 | u, _ := url.Parse(*baseURL)
52 | u.Path = path.Join(u.Path, urlPath)
53 | urlOrRegExp = u.String()
54 | }
55 |
56 | expectedValues, err := toExpectedTextValues([]interface{}{urlOrRegExp}, false, false, ignoreCase)
57 | if err != nil {
58 | return err
59 | }
60 | return pa.expect(
61 | "to.have.url",
62 | frameExpectOptions{ExpectedText: expectedValues, Timeout: timeout},
63 | urlOrRegExp,
64 | "Page URL expected to be",
65 | )
66 | }
67 |
68 | func (pa *pageAssertionsImpl) Not() PageAssertions {
69 | return newPageAssertions(pa.actualPage, true, pa.defaultTimeout)
70 | }
71 |
--------------------------------------------------------------------------------
/playwright.go:
--------------------------------------------------------------------------------
1 | // Package playwright is a library to automate Chromium, Firefox and WebKit with
2 | // a single API. Playwright is built to enable cross-browser web automation that
3 | // is ever-green, capable, reliable and fast.
4 | package playwright
5 |
6 | // DeviceDescriptor represents a single device
7 | type DeviceDescriptor struct {
8 | UserAgent string `json:"userAgent"`
9 | Viewport *Size `json:"viewport"`
10 | Screen *Size `json:"screen"`
11 | DeviceScaleFactor float64 `json:"deviceScaleFactor"`
12 | IsMobile bool `json:"isMobile"`
13 | HasTouch bool `json:"hasTouch"`
14 | DefaultBrowserType string `json:"defaultBrowserType"`
15 | }
16 |
17 | // Playwright represents a Playwright instance
18 | type Playwright struct {
19 | channelOwner
20 | Selectors Selectors
21 | Chromium BrowserType
22 | Firefox BrowserType
23 | WebKit BrowserType
24 | Request APIRequest
25 | Devices map[string]*DeviceDescriptor
26 | }
27 |
28 | // Stop stops the Playwright instance
29 | func (p *Playwright) Stop() error {
30 | return p.connection.Stop()
31 | }
32 |
33 | func (p *Playwright) setSelectors(selectors Selectors) {
34 | selectorsOwner := fromChannel(p.initializer["selectors"]).(*selectorsOwnerImpl)
35 | p.Selectors.(*selectorsImpl).removeChannel(selectorsOwner)
36 | p.Selectors = selectors
37 | p.Selectors.(*selectorsImpl).addChannel(selectorsOwner)
38 | }
39 |
40 | func newPlaywright(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *Playwright {
41 | pw := &Playwright{
42 | Selectors: newSelectorsImpl(),
43 | Chromium: fromChannel(initializer["chromium"]).(*browserTypeImpl),
44 | Firefox: fromChannel(initializer["firefox"]).(*browserTypeImpl),
45 | WebKit: fromChannel(initializer["webkit"]).(*browserTypeImpl),
46 | Devices: make(map[string]*DeviceDescriptor),
47 | }
48 | pw.createChannelOwner(pw, parent, objectType, guid, initializer)
49 | pw.Request = newApiRequestImpl(pw)
50 | pw.Chromium.(*browserTypeImpl).playwright = pw
51 | pw.Firefox.(*browserTypeImpl).playwright = pw
52 | pw.WebKit.(*browserTypeImpl).playwright = pw
53 | selectorsOwner := fromChannel(initializer["selectors"]).(*selectorsOwnerImpl)
54 | pw.Selectors.(*selectorsImpl).addChannel(selectorsOwner)
55 | pw.connection.afterClose = func() {
56 | pw.Selectors.(*selectorsImpl).removeChannel(selectorsOwner)
57 | }
58 | if pw.connection.localUtils != nil {
59 | pw.Devices = pw.connection.localUtils.Devices
60 | }
61 | return pw
62 | }
63 |
64 | //go:generate bash scripts/generate-api.sh
65 |
--------------------------------------------------------------------------------
/run_unix.go:
--------------------------------------------------------------------------------
1 | //go:build !windows
2 |
3 | package playwright
4 |
5 | import "syscall"
6 |
7 | var defaultSysProcAttr = &syscall.SysProcAttr{}
8 |
9 | // for WritableStream.Copy
10 | const defaultCopyBufSize = 1024 * 1024
11 |
--------------------------------------------------------------------------------
/run_win.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 |
3 | package playwright
4 |
5 | import "syscall"
6 |
7 | var defaultSysProcAttr = &syscall.SysProcAttr{HideWindow: true}
8 |
9 | // for WritableStream.Copy
10 | const defaultCopyBufSize = 64 * 1024
11 |
--------------------------------------------------------------------------------
/scripts/apply-patch.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | BRANCH_NAME_BUILD="playwright-build"
6 | SCRIPTS_DIR="$(dirname "$0")"
7 |
8 | echo "Applying patches..."
9 | echo "==================="
10 |
11 | pushd "$SCRIPTS_DIR/.."
12 | PW_VERSION="$(grep -oE "playwrightCliVersion.+\"[0-9\.]+" ./run.go | sed 's/playwrightCliVersion = "/v/g')"
13 | git submodule update --init
14 | pushd playwright
15 |
16 | git checkout HEAD --detach
17 |
18 | if git show-ref -q --heads "$BRANCH_NAME_BUILD"; then
19 | git fetch --tags
20 | git checkout "$PW_VERSION"
21 | git branch -D "$BRANCH_NAME_BUILD"
22 | fi
23 |
24 | git checkout -b "$BRANCH_NAME_BUILD"
25 | git apply --3way --whitespace=nowarn ../patches/*
26 | git add -A
27 | git commit -m "Applied patches"
28 |
29 | popd
--------------------------------------------------------------------------------
/scripts/generate-api.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 | set +x
5 | PWD=$(pwd -P)
6 |
7 | "$PWD"/scripts/apply-patch.sh
8 |
9 | echo
10 | echo "Generating code"
11 | echo "=================="
12 | PLAYWRIGHT_DIR="playwright"
13 | node $PLAYWRIGHT_DIR/utils/doclint/generateGoApi.js
14 | errCode=$?
15 | if [ $errCode -ne 0 ]; then
16 | echo
17 | exit $errCode
18 | else
19 | echo "Done!"
20 | echo
21 | fi
22 | mv $PLAYWRIGHT_DIR/utils/doclint/generate_types/go/generated-{enums,interfaces,structs}.go .
23 | # fmt first or not, gofumpt's result will be different
24 | go fmt generated-{enums,interfaces,structs}.go > /dev/null
25 | gofumpt -w generated-{enums,interfaces,structs}.go > /dev/null
26 |
27 | echo "Updating README"
28 | echo "==============="
29 | go run scripts/install-browsers/main.go
30 | go run scripts/update-readme-versions/main.go
31 |
32 | git submodule update
33 |
--------------------------------------------------------------------------------
/scripts/install-browsers/main.go:
--------------------------------------------------------------------------------
1 | //go:build ignore
2 | // +build ignore
3 |
4 | package main
5 |
6 | import (
7 | "log"
8 |
9 | "github.com/playwright-community/playwright-go"
10 | )
11 |
12 | func main() {
13 | if err := playwright.Install(); err != nil {
14 | log.Fatalf("could not install playwright: %v", err)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/scripts/update-patch.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 | set +x
5 |
6 | SCRIPTS_DIR="$(dirname "$0")"
7 |
8 | pushd "$SCRIPTS_DIR/../playwright"
9 | SCRIPTS_DIR="$(dirname "$0")"
10 | echo "Creating patch..."
11 | git add .
12 | git diff playwright-build^1..playwright-build > ../patches/main.patch
13 | git reset --hard playwright-build^1
14 | cd ..
15 |
16 | popd
--------------------------------------------------------------------------------
/scripts/update-readme-versions/main.go:
--------------------------------------------------------------------------------
1 | //go:build ignore
2 | // +build ignore
3 |
4 | package main
5 |
6 | import (
7 | "fmt"
8 | "log"
9 | "os"
10 | "regexp"
11 |
12 | "github.com/playwright-community/playwright-go"
13 | )
14 |
15 | func main() {
16 | const readmePath = "README.md"
17 | readmeContent, err := os.ReadFile(readmePath)
18 | if err != nil {
19 | log.Fatalf("could not read readme: %v", err)
20 | }
21 | replaceValueInTemplate := func(name, value string) {
22 | re := regexp.MustCompile(fmt.Sprintf("([^<]+)", name))
23 | readmeContent = re.ReplaceAll(readmeContent, []byte(fmt.Sprintf("%s", name, value)))
24 | }
25 |
26 | pw, err := playwright.Run()
27 | if err != nil {
28 | log.Fatalf("could not run playwright: %v", err)
29 | }
30 | browserTypes := []playwright.BrowserType{pw.Chromium, pw.Firefox, pw.WebKit}
31 | for _, browserType := range browserTypes {
32 | browser, err := browserType.Launch()
33 | if err != nil {
34 | log.Fatalf("could not launch browser: %v", err)
35 | }
36 | replaceValueInTemplate(fmt.Sprintf("%s-version", browserType.Name()), browser.Version())
37 |
38 | var badgeFormat string
39 | switch browserType.Name() {
40 | case "chromium":
41 | badgeFormat = "[](https://www.chromium.org/Home)"
42 | case "firefox":
43 | badgeFormat = "[](https://www.mozilla.org/en-US/firefox/new/)"
44 | case "webkit":
45 | badgeFormat = "[](https://webkit.org/)"
46 | }
47 | replaceValueInTemplate(browserType.Name()+"-version-badge", fmt.Sprintf(badgeFormat, browser.Version()))
48 |
49 | if err := browser.Close(); err != nil {
50 | log.Fatalf("could not close browser: %v", err)
51 | }
52 | }
53 | if err := os.WriteFile(readmePath, readmeContent, 0o644); err != nil {
54 | log.Fatalf("could not write readme: %v", err)
55 | }
56 | if err := pw.Stop(); err != nil {
57 | log.Fatalf("could not stop Playwright: %v", err)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/selectors.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | import (
4 | "errors"
5 | "os"
6 | "sync"
7 | )
8 |
9 | type selectorsOwnerImpl struct {
10 | channelOwner
11 | }
12 |
13 | func (s *selectorsOwnerImpl) setTestIdAttributeName(name string) {
14 | s.channel.SendNoReply("setTestIdAttributeName", map[string]interface{}{
15 | "testIdAttributeName": name,
16 | })
17 | }
18 |
19 | func newSelectorsOwner(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *selectorsOwnerImpl {
20 | obj := &selectorsOwnerImpl{}
21 | obj.createChannelOwner(obj, parent, objectType, guid, initializer)
22 | return obj
23 | }
24 |
25 | type selectorsImpl struct {
26 | channels sync.Map
27 | registrations []map[string]interface{}
28 | }
29 |
30 | func (s *selectorsImpl) Register(name string, script Script, options ...SelectorsRegisterOptions) error {
31 | if script.Path == nil && script.Content == nil {
32 | return errors.New("Either source or path should be specified")
33 | }
34 | source := ""
35 | if script.Path != nil {
36 | content, err := os.ReadFile(*script.Path)
37 | if err != nil {
38 | return err
39 | }
40 | source = string(content)
41 | } else {
42 | source = *script.Content
43 | }
44 | params := map[string]interface{}{
45 | "name": name,
46 | "source": source,
47 | }
48 | if len(options) == 1 && options[0].ContentScript != nil {
49 | params["contentScript"] = *options[0].ContentScript
50 | }
51 | var err error
52 | s.channels.Range(func(key, value any) bool {
53 | _, err = value.(*selectorsOwnerImpl).channel.Send("register", params)
54 | return err == nil
55 | })
56 | if err != nil {
57 | return err
58 | }
59 | s.registrations = append(s.registrations, params)
60 | return nil
61 | }
62 |
63 | func (s *selectorsImpl) SetTestIdAttribute(name string) {
64 | setTestIdAttributeName(name)
65 | s.channels.Range(func(key, value any) bool {
66 | value.(*selectorsOwnerImpl).setTestIdAttributeName(name)
67 | return true
68 | })
69 | }
70 |
71 | func (s *selectorsImpl) addChannel(channel *selectorsOwnerImpl) {
72 | s.channels.Store(channel.guid, channel)
73 | for _, params := range s.registrations {
74 | channel.channel.SendNoReply("register", params)
75 | channel.setTestIdAttributeName(getTestIdAttributeName())
76 | }
77 | }
78 |
79 | func (s *selectorsImpl) removeChannel(channel *selectorsOwnerImpl) {
80 | s.channels.Delete(channel.guid)
81 | }
82 |
83 | func newSelectorsImpl() *selectorsImpl {
84 | return &selectorsImpl{
85 | channels: sync.Map{},
86 | registrations: make([]map[string]interface{}, 0),
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/stream.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | import (
4 | "bufio"
5 | "encoding/base64"
6 | "os"
7 | "path/filepath"
8 | )
9 |
10 | type streamImpl struct {
11 | channelOwner
12 | }
13 |
14 | func (s *streamImpl) SaveAs(path string) error {
15 | err := os.MkdirAll(filepath.Dir(path), 0o777)
16 | if err != nil {
17 | return err
18 | }
19 | file, err := os.Create(path)
20 | if err != nil {
21 | return err
22 | }
23 | defer file.Close()
24 | writer := bufio.NewWriter(file)
25 | for {
26 | binary, err := s.channel.Send("read", map[string]interface{}{"size": 1024 * 1024})
27 | if err != nil {
28 | return err
29 | }
30 | bytes, err := base64.StdEncoding.DecodeString(binary.(string))
31 | if err != nil {
32 | return err
33 | }
34 | if len(bytes) == 0 {
35 | break
36 | }
37 | _, err = writer.Write(bytes)
38 | if err != nil {
39 | return err
40 | }
41 | }
42 | return writer.Flush()
43 | }
44 |
45 | func (s *streamImpl) ReadAll() ([]byte, error) {
46 | var data []byte
47 | for {
48 | binary, err := s.channel.Send("read", map[string]interface{}{"size": 1024 * 1024})
49 | if err != nil {
50 | return nil, err
51 | }
52 | bytes, err := base64.StdEncoding.DecodeString(binary.(string))
53 | if err != nil {
54 | return nil, err
55 | }
56 | if len(bytes) == 0 {
57 | break
58 | }
59 | data = append(data, bytes...)
60 | }
61 | return data, nil
62 | }
63 |
64 | func newStream(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *streamImpl {
65 | stream := &streamImpl{}
66 | stream.createChannelOwner(stream, parent, objectType, guid, initializer)
67 | return stream
68 | }
69 |
--------------------------------------------------------------------------------
/tests/apiresponse_assertions_test.go:
--------------------------------------------------------------------------------
1 | package playwright_test
2 |
3 | import (
4 | "net/http"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestAssertionsResponseIsOKPass(t *testing.T) {
11 | BeforeEach(t)
12 |
13 | response, err := page.Request().Get(server.EMPTY_PAGE)
14 | require.NoError(t, err)
15 | require.NoError(t, expect.APIResponse(response).ToBeOK())
16 | require.Error(t, expect.APIResponse(response).Not().ToBeOK())
17 | }
18 |
19 | func TestAssertionsShouldPrintResponseWithTextContentTypeIfToBeOKFails(t *testing.T) {
20 | BeforeEach(t)
21 |
22 | server.SetRoute("/text-content-type", func(w http.ResponseWriter, r *http.Request) {
23 | w.Header().Set("Content-Type", "text/plain")
24 | w.WriteHeader(http.StatusNotFound)
25 | _, _ = w.Write([]byte("Text error"))
26 | })
27 | server.SetRoute("/no-content-type", func(w http.ResponseWriter, r *http.Request) {
28 | w.Header().Set("Content-Type", "")
29 | w.WriteHeader(http.StatusNotFound)
30 | _, _ = w.Write([]byte("No content type error"))
31 | })
32 | server.SetRoute("/binary-content-type", func(w http.ResponseWriter, r *http.Request) {
33 | w.Header().Set("Content-Type", "image/bmp")
34 | w.WriteHeader(http.StatusNotFound)
35 | _, _ = w.Write([]byte("Image content type error"))
36 | })
37 |
38 | response, err := page.Request().Get(server.PREFIX + "/text-content-type")
39 | require.NoError(t, err)
40 | require.NoError(t, expect.APIResponse(response).Not().ToBeOK())
41 | err = expect.APIResponse(response).ToBeOK()
42 | require.ErrorContains(t, err, "→ GET "+server.PREFIX+"/text-content-type")
43 | require.ErrorContains(t, err, "← 404 Not Found")
44 | require.ErrorContains(t, err, "Response Text:")
45 | require.ErrorContains(t, err, "Text error")
46 |
47 | response, err = page.Request().Get(server.PREFIX + "/no-content-type")
48 | require.NoError(t, err)
49 | require.NoError(t, expect.APIResponse(response).Not().ToBeOK())
50 | err = expect.APIResponse(response).ToBeOK()
51 | require.ErrorContains(t, err, "→ GET "+server.PREFIX+"/no-content-type")
52 | require.ErrorContains(t, err, "← 404 Not Found")
53 | require.NotContains(t, err.Error(), "Response Text:")
54 | require.NotContains(t, err.Error(), "No content type error")
55 |
56 | response, err = page.Request().Get(server.PREFIX + "/binary-content-type")
57 | require.NoError(t, err)
58 | err = expect.APIResponse(response).ToBeOK()
59 | require.ErrorContains(t, err, "→ GET "+server.PREFIX+"/binary-content-type")
60 | require.ErrorContains(t, err, "← 404 Not Found")
61 | require.NotContains(t, err.Error(), "Response Text:")
62 | require.NotContains(t, err.Error(), "Image content type error")
63 | }
64 |
--------------------------------------------------------------------------------
/tests/assets/background-color.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/assets/beforeunload.html:
--------------------------------------------------------------------------------
1 | beforeunload demo.
2 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/assets/button-overlay-oopif.html:
--------------------------------------------------------------------------------
1 |
23 |
40 |
--------------------------------------------------------------------------------
/tests/assets/cached/one-style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: pink;
3 | }
4 |
--------------------------------------------------------------------------------
/tests/assets/cached/one-style.html:
--------------------------------------------------------------------------------
1 |
2 | hello, world!
3 |
--------------------------------------------------------------------------------
/tests/assets/callback.js:
--------------------------------------------------------------------------------
1 | function callme(cb) {
2 | return cb();
3 | }
4 |
5 | module.exports = callme;
6 |
--------------------------------------------------------------------------------
/tests/assets/checkerboard.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
35 |
36 |
42 |
43 |
--------------------------------------------------------------------------------
/tests/assets/chromium-linux.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/chromium-linux.zip
--------------------------------------------------------------------------------
/tests/assets/client-certificates/README.md:
--------------------------------------------------------------------------------
1 | # Client Certificate test-certificates
2 |
3 | ## Server
4 |
5 | ```bash
6 | openssl req \
7 | -x509 \
8 | -newkey rsa:4096 \
9 | -keyout server/server_key.pem \
10 | -out server/server_cert.pem \
11 | -nodes \
12 | -days 365 \
13 | -subj "/CN=localhost/O=Client\ Certificate\ Demo" \
14 | -addext "subjectAltName=DNS:localhost,DNS:local.playwright"
15 | ```
16 |
17 | ## Trusted client-certificate (server signed/valid)
18 |
19 | ```
20 | mkdir -p client/trusted
21 | # generate server-signed (valid) certifcate
22 | openssl req \
23 | -newkey rsa:4096 \
24 | -keyout client/trusted/key.pem \
25 | -out client/trusted/csr.pem \
26 | -nodes \
27 | -days 365 \
28 | -subj "/CN=Alice"
29 |
30 | # sign with server_cert.pem
31 | openssl x509 \
32 | -req \
33 | -in client/trusted/csr.pem \
34 | -CA server/server_cert.pem \
35 | -CAkey server/server_key.pem \
36 | -out client/trusted/cert.pem \
37 | -set_serial 01 \
38 | -days 365
39 | # create pfx
40 | openssl pkcs12 -export -out client/trusted/cert.pfx -inkey client/trusted/key.pem -in client/trusted/cert.pem -passout pass:secure
41 | ```
42 |
43 | ## Self-signed certificate (invalid)
44 |
45 | ```
46 | mkdir -p client/self-signed
47 | openssl req \
48 | -newkey rsa:4096 \
49 | -keyout client/self-signed/key.pem \
50 | -out client/self-signed/csr.pem \
51 | -nodes \
52 | -days 365 \
53 | -subj "/CN=Bob"
54 |
55 | # sign with self-signed/key.pem
56 | openssl x509 \
57 | -req \
58 | -in client/self-signed/csr.pem \
59 | -signkey client/self-signed/key.pem \
60 | -out client/self-signed/cert.pem \
61 | -days 365
62 | ```
63 |
--------------------------------------------------------------------------------
/tests/assets/client-certificates/client/self-signed/cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIEyzCCArOgAwIBAgIUYps4gh4MqFYg8zqQhHYL7zYfbLkwDQYJKoZIhvcNAQEL
3 | BQAwDjEMMAoGA1UEAwwDQm9iMB4XDTI0MDcxOTEyNDc0MFoXDTI1MDcxOTEyNDc0
4 | MFowDjEMMAoGA1UEAwwDQm9iMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
5 | AgEA179eTsqcc1c3AOQHzCZEyYLPta2CCAscUFqcEZ9vWvjW0uzOv9TDlB33Unov
6 | jch4CElZOBhzTadVsbmnYKpxwyVU89WCuQKvedz4k1vu7S1YryfNbmS8PWbnQ4ds
7 | 9NB7SgJNHZILvx9DXuWeFEyzRIo1984z4HheBzrkf791LqpYKaKziANUo8h8t0dm
8 | TX/boOz8cEnQNwtTC0ZX3aD0obG/UAhr/22ZGPo/E659fh4ptyYX2LrIUHGy+Eux
9 | nJ9Y4cTqa88Ee6K6AkDiT/AoNQNxE4X++jqLuie8j/ZYpI1Oll38GwKVOyy1msRL
10 | toGmISNwkMIQDGABrJlxgpP4QQAQ+08v9srzXOlkdxdr7OCP81r+ccBXiSQEe7BA
11 | kdJ8l98l5dprJ++GJ+SZcV4+/iGR0dKU2IdAG5HiKZIFn6ch9Ux+UMqeGaYCpkHr
12 | TiietHwcXgtVBlE0jFmB/HspmI/O0abK+grMmueaH7XtTI8YHnw0mUpL8+yp7mfA
13 | 7zFusgFgyiBPXeD/NQgg8vja67k++d1VGoXm2xr+5WPQCSbgQoMkkOBMLHWJTefd
14 | 6F4Z5M+oI0VwYbf6eQW246wJgpCHSPR0Vdijd6MAGRWKUuLfDsA9+12iGbKvwJ2e
15 | nJlStft2V2LZcjBfdIMbigW1aSVNN5w6m6YVrQPry3WPkWcCAwEAAaMhMB8wHQYD
16 | VR0OBBYEFPxKWTFQJSg4HD2qjxL0dnXX/z4qMA0GCSqGSIb3DQEBCwUAA4ICAQBz
17 | 4H1d5eGRU9bekUvi7LbZ5CP/I6w6PL/9AlXqO3BZKxplK7fYGHd3uqyDorJEsvjV
18 | hxwvFlEnS0JIU3nRzhJU/h4Yaivf1WLRFwGZ4TPBjX9KFU27exFWD3rppazkWybJ
19 | i4WuEdP3TJMdKLcNTtXWUDroDOgPlS66u6oZ+mUyUROil+B+fgQgVDhjRc5fvRgZ
20 | Lng8wuejCo3ExQyxkwn2G5guyIimgHmOQghPtLO5xlc67Z4GPUZ1m4tC+BCiFO4D
21 | YIXl3QiIpmU7Pss39LLKMGXXAgLRqyMzqE52lsznu18v5vDLfTaRH4u/wjzULhXz
22 | SrV1IUJmhgEXta4EeDmPH0itgKtkbwjgCOD7drrFrJq/EnvIaJ5cpxiI1pFmYD8g
23 | VVD7/KT/CyT1Uz1dI8QaP/JX8XEgtMJaSkPfjPErIViN9rh9ECCNLgFyv7Y0Plar
24 | A6YlvdyV1Rta/BHndf5Hqz9QWNhbFCMQRGVQNEcoKwpFyjAE9SXoKJvFIK/w5WXu
25 | qKzIYA26QXE3p734Xu1n8QiFJIyltVHbyUlD0k06194t5a2WK+/eDeReIsk0QOI8
26 | FGqhyPZ7YjR5tSZTmgljtViqBO5AA23QOVFqtjOUrjXP5pTbPJel99Z/FTkqSwvB
27 | Rt4OX7HfuokWQDTT0TMn5jVtJyi54cH7f9MmsNJ23g==
28 | -----END CERTIFICATE-----
29 |
--------------------------------------------------------------------------------
/tests/assets/client-certificates/client/self-signed/csr.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE REQUEST-----
2 | MIIEUzCCAjsCAQAwDjEMMAoGA1UEAwwDQm9iMIICIjANBgkqhkiG9w0BAQEFAAOC
3 | Ag8AMIICCgKCAgEA179eTsqcc1c3AOQHzCZEyYLPta2CCAscUFqcEZ9vWvjW0uzO
4 | v9TDlB33Unovjch4CElZOBhzTadVsbmnYKpxwyVU89WCuQKvedz4k1vu7S1YryfN
5 | bmS8PWbnQ4ds9NB7SgJNHZILvx9DXuWeFEyzRIo1984z4HheBzrkf791LqpYKaKz
6 | iANUo8h8t0dmTX/boOz8cEnQNwtTC0ZX3aD0obG/UAhr/22ZGPo/E659fh4ptyYX
7 | 2LrIUHGy+EuxnJ9Y4cTqa88Ee6K6AkDiT/AoNQNxE4X++jqLuie8j/ZYpI1Oll38
8 | GwKVOyy1msRLtoGmISNwkMIQDGABrJlxgpP4QQAQ+08v9srzXOlkdxdr7OCP81r+
9 | ccBXiSQEe7BAkdJ8l98l5dprJ++GJ+SZcV4+/iGR0dKU2IdAG5HiKZIFn6ch9Ux+
10 | UMqeGaYCpkHrTiietHwcXgtVBlE0jFmB/HspmI/O0abK+grMmueaH7XtTI8YHnw0
11 | mUpL8+yp7mfA7zFusgFgyiBPXeD/NQgg8vja67k++d1VGoXm2xr+5WPQCSbgQoMk
12 | kOBMLHWJTefd6F4Z5M+oI0VwYbf6eQW246wJgpCHSPR0Vdijd6MAGRWKUuLfDsA9
13 | +12iGbKvwJ2enJlStft2V2LZcjBfdIMbigW1aSVNN5w6m6YVrQPry3WPkWcCAwEA
14 | AaAAMA0GCSqGSIb3DQEBCwUAA4ICAQCb07d2IjUy1PeHCj/2k/z9FrZSo6K3c8y6
15 | b/u/MZ0AXPKLPDSo7UYpOJ8Z2cBiJ8jQapjTSEL8POUYqcvCmP55R6u68KmvINHo
16 | +Ly7pP+xPrbA4Q0WmPnz37hQn+I1he0GuEQyjZZqUln9zwp67TsWNKxKtCH+1j8M
17 | Ltzx6kuHCdPtDUtv291yhVRqvbjiDs+gzdQYNJtAkUbHwHFxu8oZhg8QZGyXYMN8
18 | TGoQ1LTezFZXJtX69K7WnrDGrjsgB6EMvwkqAFSYNH0LFvI0xo13OOgXr9mrwohA
19 | 76uZtjXL9B15EqrMce6mdUZi46QJuQ2avTi57Lz+fqvsBYdQO89VcFSmqu2nfspN
20 | QZDrooyjHrlls8MpoBd8fde9oT4uA4/d9SJtuHUnjgGN7Qr7eTruWXL8wVMwFnvL
21 | igWE4detO9y2gpRLq6uEqzWYMGtN9PXJCGU8C8m9E2EBUKMrT/bpNbboatLcgRrW
22 | acj0BRVqoVzk1sRq7Sa6ejywqgARvIhTehg6DqdMdcENCPQ7rxDRu5PSDM8/mwIj
23 | 0KYl8d2PlECB4ofRyLcy17BZzjP6hSnkGzcFk0/bChZOSIRnwvKbvfXnB45hhPk8
24 | XwT/6UNSwC2STP3gtOmLqrWj+OE0gy0AkDMvP3UnQVGMUvgfYg+N4ROCVtlqzxe9
25 | W65c05Mm1g==
26 | -----END CERTIFICATE REQUEST-----
27 |
--------------------------------------------------------------------------------
/tests/assets/client-certificates/client/self-signed/key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | 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
51 | dg6m3MAyZ1IXjHlogOdmGvnq07/w
52 | -----END PRIVATE KEY-----
53 |
--------------------------------------------------------------------------------
/tests/assets/client-certificates/client/trusted/cert-legacy.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/client-certificates/client/trusted/cert-legacy.pfx
--------------------------------------------------------------------------------
/tests/assets/client-certificates/client/trusted/cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIFAzCCAuugAwIBAgIBATANBgkqhkiG9w0BAQsFADA2MRIwEAYDVQQDDAlsb2Nh
3 | bGhvc3QxIDAeBgNVBAoMF0NsaWVudCBDZXJ0aWZpY2F0ZSBEZW1vMB4XDTI0MDcx
4 | OTEyNDczN1oXDTI1MDcxOTEyNDczN1owEDEOMAwGA1UEAwwFQWxpY2UwggIiMA0G
5 | CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCac3+4rNmH4/N1s4HqR2X168tgS/aA
6 | 6sHW5at8mWRnq54Nm11RvnK55jHQYVAdBgJy5M07w0wakp8inxzlY95wqxBimYG6
7 | 3Un/1p7mX9FkB4LNISCc6j/s/Ufv85MXPbn0S5rm9UcQO9cINJb1RP1YgDDLN5cx
8 | Mz6X4nyofN8H6Lhvh4JDdBw4DfDEFERkVfF+bkZ7YW4XHEChgzm3RxCF0eeGzIXG
9 | rkkK9AsSdJAhOvTlHPFCQKXTYZhsL5+3Ma4RnWnDWvLTHx6KzoU+twTM2mYhhQuQ
10 | gQpnmDHxGge8kGeHGtfdgAjtVJTE57xF/shP0JU+tuIV8NNhQ/vEmhL0Wa093/Ev
11 | pTVp0EUEuDh9ORRH5K5M4bKJyU4XX5noiht6yOn00uaoJcWduUAWsU+cDSvDTMw8
12 | 1opWWm0QIAV3G2yuRSkumHAKqvQLeyeyiKz+OEhyEiZ7EZNExPD0TSpApSTU6aCT
13 | UAvPYGQ59VjsMHTuJ9r4wKIYaDvfL+t72vg2vTQma5cTOBJfIdxH9blFTjEnToH3
14 | LX8t0XndQ2RkiRnIze2p2jUShxo/lWCjCw+2Iaw0A0fNUK1BbOrFRPq1u7AnEuMJ
15 | t7HF50MloItM97R9vofDwgDIzlX/PzlVRcn1WCo8Fr/0EXxPPreX0YDIp1ANQ8fS
16 | v7bKb2vQIxWuCQIDAQABo0IwQDAdBgNVHQ4EFgQUVJVRJJ2k/Z4r0M1AXe6agyD4
17 | uCwwHwYDVR0jBBgwFoAUEHtrxWCk96Ehr60E0HBuwLk2i+IwDQYJKoZIhvcNAQEL
18 | BQADggIBAGEvSkxhxRKmlvKG8wCXop2OaUUAOG16+T96vd+aFYaJNlfGoPvqv4Lw
19 | qaHztVktnRrJ//fpNWOsdxkE1uPU4uyGjl2KbyH81JvkE6A3OX0P4B01n8lcimY2
20 | j3oje6KjORUouYVsypD1VcwfWJgsE3U2Txv5srD8BoemVWgWbWjfyim4kk8C5zlf
21 | tWEazVAaI4MWecqtU4P5gIEomCI7MG9ebxYp5oQhRxeOndOYdUbSzAkZj50gXFA1
22 | +TNkvuhTFlJF0F7qIFVJSJTmJ+6E5B4ddbkyUYwbOdO+P8mz5N5mSljE+EiIQTxo
23 | AwbG8cSivMy/jI3h048tCUONAJzcSWCF4k1r9Qr6xbyW2ud2GmKiFCEYJkYTsMWV
24 | fM/RujTHlGvJ2+bQK5HiNyW0tO9znW9kaoxolu1YBvTh2492v3agK7nALyGGgdo1
25 | /nN/ikgkQiyaCpZwFeooJv1YFU5aDhR9RjIIJ9UbJ8FdAv8Xd00E3viunLTvqqXK
26 | RVMokw+tFQTEzjKofKWYArPDjB9LUbN+vQbumKalis3+NlJ3WolYPrCg55tqt1o3
27 | zXi+xv7120cJFouilRFwrafNFV6F+pRMkMmiWopMnoVJPVXcoqyJRcsmO62uslhg
28 | BLFgAH4H/14drYrgWIMz0no78RInEz0z507zwLkWk5d9W9pJ/4Rf
29 | -----END CERTIFICATE-----
30 |
--------------------------------------------------------------------------------
/tests/assets/client-certificates/client/trusted/cert.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/client-certificates/client/trusted/cert.pfx
--------------------------------------------------------------------------------
/tests/assets/client-certificates/client/trusted/csr.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE REQUEST-----
2 | MIIEVTCCAj0CAQAwEDEOMAwGA1UEAwwFQWxpY2UwggIiMA0GCSqGSIb3DQEBAQUA
3 | A4ICDwAwggIKAoICAQCac3+4rNmH4/N1s4HqR2X168tgS/aA6sHW5at8mWRnq54N
4 | m11RvnK55jHQYVAdBgJy5M07w0wakp8inxzlY95wqxBimYG63Un/1p7mX9FkB4LN
5 | ISCc6j/s/Ufv85MXPbn0S5rm9UcQO9cINJb1RP1YgDDLN5cxMz6X4nyofN8H6Lhv
6 | h4JDdBw4DfDEFERkVfF+bkZ7YW4XHEChgzm3RxCF0eeGzIXGrkkK9AsSdJAhOvTl
7 | HPFCQKXTYZhsL5+3Ma4RnWnDWvLTHx6KzoU+twTM2mYhhQuQgQpnmDHxGge8kGeH
8 | GtfdgAjtVJTE57xF/shP0JU+tuIV8NNhQ/vEmhL0Wa093/EvpTVp0EUEuDh9ORRH
9 | 5K5M4bKJyU4XX5noiht6yOn00uaoJcWduUAWsU+cDSvDTMw81opWWm0QIAV3G2yu
10 | RSkumHAKqvQLeyeyiKz+OEhyEiZ7EZNExPD0TSpApSTU6aCTUAvPYGQ59VjsMHTu
11 | J9r4wKIYaDvfL+t72vg2vTQma5cTOBJfIdxH9blFTjEnToH3LX8t0XndQ2RkiRnI
12 | ze2p2jUShxo/lWCjCw+2Iaw0A0fNUK1BbOrFRPq1u7AnEuMJt7HF50MloItM97R9
13 | vofDwgDIzlX/PzlVRcn1WCo8Fr/0EXxPPreX0YDIp1ANQ8fSv7bKb2vQIxWuCQID
14 | AQABoAAwDQYJKoZIhvcNAQELBQADggIBAGgf3EC8WL3RGmuGA+d/4wd1jNfrfU6n
15 | xjnDwdEEX0TQZGGPjh5xvoCK76yZPkO6+z0IYSepEmWBS27HJKl7nuoOvS7MjQyJ
16 | C+3Bdk3ToCeQjmNBlRBKsUw5ftTU902oMl5BptHGj1KGjYBLAkPdXb44wXSVKJ8q
17 | ihFhWlovsva6GDoUorksU3vOwijdlGzTANQHJGFncgrRud9ATavpGS3KVxR73R3A
18 | aBbu3Qw+QIfu8Qx5eBJp8CbMrpAmjfuq17STvqr5bC10Fnn4NegrnHOQG9JcK02+
19 | 5Bn3+9X/n1mue7aohIdErLEiDMSqMOwFfrJeaH6YM1G4QkWyqGugtmHsWOUf0nlU
20 | nkH1krvfw9rb6b+03c4A6GSeHnbX5ufFDSf5gaR6Wy7c0jBnoxVbtBLH2zXlrd0k
21 | iRQG7C6XZzGMS7hb7GL7+bkRy9kWjmDL7z7Fp+EgzKhNmzuWII3E9X9va33HoQ/Q
22 | UdK3JVToxRQg6XRKOxL9+U/+8i6U8lxObLWkWh2cypZqbz5qJxa+2u5JYO/KEoHZ
23 | G963UX7XWezR98vZuTc1XHGZtBDMrjjDd7Kmb4/i/xBPeWwseeGtzFy9z2pnEnkL
24 | uKE4C8wUNpzUUlsn4LneZXObIoErE7FqAAlVFujVe7iaJBmXoUXZR36drbfiaODK
25 | vwAGyrYHaOlR
26 | -----END CERTIFICATE REQUEST-----
27 |
--------------------------------------------------------------------------------
/tests/assets/client-certificates/client/trusted/key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | 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
51 | Y+eu85cike5J4vnz8uG04yt7rXjIrUav
52 | -----END PRIVATE KEY-----
53 |
--------------------------------------------------------------------------------
/tests/assets/client-certificates/server/server_cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | 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
31 | 2Ct10prMWB/3
32 | -----END CERTIFICATE-----
33 |
--------------------------------------------------------------------------------
/tests/assets/client-certificates/server/server_key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | 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
51 | 8ynLaTs3oV3BjQya9pAUyzIX5L67
52 | -----END PRIVATE KEY-----
53 |
--------------------------------------------------------------------------------
/tests/assets/consolelog.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | console.log test
5 |
6 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/tests/assets/csp.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/assets/css-transition.html:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
22 |
--------------------------------------------------------------------------------
/tests/assets/csscoverage/Dosis-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/csscoverage/Dosis-Regular.ttf
--------------------------------------------------------------------------------
/tests/assets/csscoverage/involved.html:
--------------------------------------------------------------------------------
1 |
24 | woof!
25 | fancy text
26 |
27 |
--------------------------------------------------------------------------------
/tests/assets/csscoverage/media.html:
--------------------------------------------------------------------------------
1 |
3 | hello, world
4 |
5 |
--------------------------------------------------------------------------------
/tests/assets/csscoverage/multiple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/tests/assets/csscoverage/simple.html:
--------------------------------------------------------------------------------
1 |
5 | hello, world
6 |
7 |
--------------------------------------------------------------------------------
/tests/assets/csscoverage/sourceurl.html:
--------------------------------------------------------------------------------
1 |
7 |
8 |
--------------------------------------------------------------------------------
/tests/assets/csscoverage/stylesheet1.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/tests/assets/csscoverage/stylesheet2.css:
--------------------------------------------------------------------------------
1 | html {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
--------------------------------------------------------------------------------
/tests/assets/csscoverage/unused.html:
--------------------------------------------------------------------------------
1 |
7 |
8 |
--------------------------------------------------------------------------------
/tests/assets/deep-shadow.html:
--------------------------------------------------------------------------------
1 |
37 |
--------------------------------------------------------------------------------
/tests/assets/detect-touch.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Detect Touch Test
5 |
6 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/tests/assets/digits/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/digits/0.png
--------------------------------------------------------------------------------
/tests/assets/digits/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/digits/1.png
--------------------------------------------------------------------------------
/tests/assets/digits/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/digits/2.png
--------------------------------------------------------------------------------
/tests/assets/digits/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/digits/3.png
--------------------------------------------------------------------------------
/tests/assets/digits/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/digits/4.png
--------------------------------------------------------------------------------
/tests/assets/digits/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/digits/5.png
--------------------------------------------------------------------------------
/tests/assets/digits/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/digits/6.png
--------------------------------------------------------------------------------
/tests/assets/digits/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/digits/7.png
--------------------------------------------------------------------------------
/tests/assets/digits/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/digits/8.png
--------------------------------------------------------------------------------
/tests/assets/digits/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/digits/9.png
--------------------------------------------------------------------------------
/tests/assets/dom.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/tests/assets/download-blob.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Blob Download Example
5 |
6 |
7 |
27 | Download
28 |
29 |
30 |
--------------------------------------------------------------------------------
/tests/assets/drag-n-drop.html:
--------------------------------------------------------------------------------
1 |
31 |
32 |
50 |
51 |
52 |
53 |
54 | Select this element, drag it to the Drop Zone and then release the selection to move the element.
55 |
56 | Drop Zone
57 |
58 |
--------------------------------------------------------------------------------
/tests/assets/dummy_bad_browser_executable.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | process.exit(1);
4 |
--------------------------------------------------------------------------------
/tests/assets/dynamic-oopif.html:
--------------------------------------------------------------------------------
1 |
13 |
32 |
--------------------------------------------------------------------------------
/tests/assets/empty-standard-mode.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/assets/empty.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/empty.html
--------------------------------------------------------------------------------
/tests/assets/empty.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/empty.pdf
--------------------------------------------------------------------------------
/tests/assets/error.html:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/tests/assets/es6/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | "sourceType": "module"
4 | }
5 | }
--------------------------------------------------------------------------------
/tests/assets/es6/es6import.js:
--------------------------------------------------------------------------------
1 | import num from './es6module.js';
2 | window.__es6injected = num;
--------------------------------------------------------------------------------
/tests/assets/es6/es6module.js:
--------------------------------------------------------------------------------
1 | export default 42;
--------------------------------------------------------------------------------
/tests/assets/es6/es6pathimport.js:
--------------------------------------------------------------------------------
1 | import num from './es6/es6module.js';
2 | window.__es6injected = num;
--------------------------------------------------------------------------------
/tests/assets/example.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/example.mp3
--------------------------------------------------------------------------------
/tests/assets/file-to-upload-2.txt:
--------------------------------------------------------------------------------
1 | contents of the file
--------------------------------------------------------------------------------
/tests/assets/file-to-upload.txt:
--------------------------------------------------------------------------------
1 | contents of the file
--------------------------------------------------------------------------------
/tests/assets/formatted-number.html:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/tests/assets/frames/child-redirect.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/assets/frames/frame.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
19 | Hi, I'm frame
20 |
--------------------------------------------------------------------------------
/tests/assets/frames/frameset.html:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/tests/assets/frames/lazy-frame.html:
--------------------------------------------------------------------------------
1 | One
2 | Two
3 | Three
4 | Four
5 | Five
6 |
7 |
--------------------------------------------------------------------------------
/tests/assets/frames/nested-frames.html:
--------------------------------------------------------------------------------
1 |
19 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/tests/assets/frames/one-frame.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/assets/frames/redirect-my-parent.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/tests/assets/frames/script.js:
--------------------------------------------------------------------------------
1 | console.log('Cheers!');
2 |
--------------------------------------------------------------------------------
/tests/assets/frames/style.css:
--------------------------------------------------------------------------------
1 | div {
2 | color: blue;
3 | }
4 |
--------------------------------------------------------------------------------
/tests/assets/frames/two-frames.html:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/tests/assets/geolocation.html:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/tests/assets/global-var.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/assets/grid-iframe-in-shadow.html:
--------------------------------------------------------------------------------
1 |
6 |
17 |
--------------------------------------------------------------------------------
/tests/assets/grid.html:
--------------------------------------------------------------------------------
1 |
28 |
29 |
63 |
--------------------------------------------------------------------------------
/tests/assets/har-sha1-main-response.txt:
--------------------------------------------------------------------------------
1 | Hello, world
--------------------------------------------------------------------------------
/tests/assets/har-sha1.har:
--------------------------------------------------------------------------------
1 | {
2 | "log": {
3 | "version": "1.2",
4 | "creator": {
5 | "name": "Playwright",
6 | "version": "1.23.0-next"
7 | },
8 | "browser": {
9 | "name": "chromium",
10 | "version": "103.0.5060.33"
11 | },
12 | "pages": [
13 | {
14 | "startedDateTime": "2022-06-10T04:27:32.125Z",
15 | "id": "page@b17b177f1c2e66459db3dcbe44636ffd",
16 | "title": "Hey",
17 | "pageTimings": {
18 | "onContentLoad": 70,
19 | "onLoad": 70
20 | }
21 | }
22 | ],
23 | "entries": [
24 | {
25 | "_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea",
26 | "_monotonicTime": 270572145.898,
27 | "startedDateTime": "2022-06-10T04:27:32.146Z",
28 | "time": 8.286,
29 | "request": {
30 | "method": "GET",
31 | "url": "http://no.playwright/",
32 | "httpVersion": "HTTP/1.1",
33 | "cookies": [],
34 | "headers": [
35 | {
36 | "name": "Accept",
37 | "value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
38 | },
39 | {
40 | "name": "Upgrade-Insecure-Requests",
41 | "value": "1"
42 | },
43 | {
44 | "name": "User-Agent",
45 | "value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.33 Safari/537.36"
46 | }
47 | ],
48 | "queryString": [],
49 | "headersSize": 326,
50 | "bodySize": 0
51 | },
52 | "response": {
53 | "status": 200,
54 | "statusText": "OK",
55 | "httpVersion": "HTTP/1.1",
56 | "cookies": [],
57 | "headers": [
58 | {
59 | "name": "content-length",
60 | "value": "12"
61 | },
62 | {
63 | "name": "content-type",
64 | "value": "text/html"
65 | }
66 | ],
67 | "content": {
68 | "size": 12,
69 | "mimeType": "text/html",
70 | "compression": 0,
71 | "_file": "har-sha1-main-response.txt"
72 | },
73 | "headersSize": 64,
74 | "bodySize": 71,
75 | "redirectURL": "",
76 | "_transferSize": 71
77 | },
78 | "cache": {
79 | "beforeRequest": null,
80 | "afterRequest": null
81 | },
82 | "timings": {
83 | "dns": -1,
84 | "connect": -1,
85 | "ssl": -1,
86 | "send": 0,
87 | "wait": 8.286,
88 | "receive": -1
89 | },
90 | "pageref": "page@b17b177f1c2e66459db3dcbe44636ffd",
91 | "_securityDetails": {}
92 | }
93 | ]
94 | }
95 | }
--------------------------------------------------------------------------------
/tests/assets/har.html:
--------------------------------------------------------------------------------
1 | HAR Page
2 |
3 | hello, world!
4 |
--------------------------------------------------------------------------------
/tests/assets/headings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Headings
6 |
7 |
8 |
9 | Title
10 | Subtitle
11 | Subsubtitle
12 | Subtitle
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/assets/highdpi.html:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/tests/assets/historyapi.html:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/tests/assets/injectedfile.js:
--------------------------------------------------------------------------------
1 | window.__injected = 42;
2 | window.injected = 123;
3 | window.__injectedError = new Error('hi');
--------------------------------------------------------------------------------
/tests/assets/injectedstyle.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/tests/assets/input/animating-button.html:
--------------------------------------------------------------------------------
1 |
8 |
43 |
--------------------------------------------------------------------------------
/tests/assets/input/button.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Button test
5 |
6 |
7 |
8 |
9 |
31 |
32 |
--------------------------------------------------------------------------------
/tests/assets/input/checkbox.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Selection Test
5 |
6 |
7 |
8 |
9 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/tests/assets/input/drag-n-drop-manual.html:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 | Drop here
14 |
15 |
16 | Drag me
17 |
18 |
19 |
20 |
45 |
--------------------------------------------------------------------------------
/tests/assets/input/fileupload.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | File upload test
5 |
6 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/tests/assets/input/folderupload.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Folder upload test
5 |
6 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/tests/assets/input/handle-locator.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Interstitial test
5 |
6 |
7 |
37 |
38 | A place on the side to hover
39 |
40 |
This interstitial covers the button
41 |
42 |
43 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/tests/assets/input/keyboard.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Keyboard test
5 |
6 |
7 |
8 |
41 |
42 |
--------------------------------------------------------------------------------
/tests/assets/input/mouse-helper.js:
--------------------------------------------------------------------------------
1 | // This injects a box into the page that moves with the mouse;
2 | // Useful for debugging
3 | (function(){
4 | const box = document.createElement('div');
5 | box.classList.add('mouse-helper');
6 | const styleElement = document.createElement('style');
7 | styleElement.innerHTML = `
8 | .mouse-helper {
9 | pointer-events: none;
10 | position: absolute;
11 | top: 0;
12 | left: 0;
13 | width: 20px;
14 | height: 20px;
15 | background: rgba(0,0,0,.4);
16 | border: 1px solid white;
17 | border-radius: 10px;
18 | margin-left: -10px;
19 | margin-top: -10px;
20 | transition: background .2s, border-radius .2s, border-color .2s;
21 | }
22 | .mouse-helper.button-1 {
23 | transition: none;
24 | background: rgba(0,0,0,0.9);
25 | }
26 | .mouse-helper.button-2 {
27 | transition: none;
28 | border-color: rgba(0,0,255,0.9);
29 | }
30 | .mouse-helper.button-3 {
31 | transition: none;
32 | border-radius: 4px;
33 | }
34 | .mouse-helper.button-4 {
35 | transition: none;
36 | border-color: rgba(255,0,0,0.9);
37 | }
38 | .mouse-helper.button-5 {
39 | transition: none;
40 | border-color: rgba(0,255,0,0.9);
41 | }
42 | `;
43 | document.head.appendChild(styleElement);
44 | document.body.appendChild(box);
45 | document.addEventListener('mousemove', event => {
46 | box.style.left = event.pageX + 'px';
47 | box.style.top = event.pageY + 'px';
48 | updateButtons(event.buttons);
49 | }, true);
50 | document.addEventListener('mousedown', event => {
51 | updateButtons(event.buttons);
52 | box.classList.add('button-' + event.which);
53 | }, true);
54 | document.addEventListener('mouseup', event => {
55 | updateButtons(event.buttons);
56 | box.classList.remove('button-' + event.which);
57 | }, true);
58 | function updateButtons(buttons) {
59 | for (let i = 0; i < 5; i++)
60 | box.classList.toggle('button-' + i, buttons & (1 << i));
61 | }
62 | })();
63 |
--------------------------------------------------------------------------------
/tests/assets/input/rotatedButton.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Rotated button test
5 |
6 |
7 |
8 |
9 |
14 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tests/assets/input/scrollable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Scrollable test
5 |
6 |
7 |
8 |
22 |
23 |
--------------------------------------------------------------------------------
/tests/assets/input/scrollable2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Scrollable test
5 |
6 |
7 |
8 |
22 |
23 |
--------------------------------------------------------------------------------
/tests/assets/input/select.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Selection Test
5 |
6 |
7 |
24 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/tests/assets/input/textarea.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Textarea test
5 |
6 |
7 |
8 |
9 |
10 | Plain div
11 |
12 |
19 |
20 |
--------------------------------------------------------------------------------
/tests/assets/input/touches.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Touch test
5 |
6 |
7 |
8 |
9 |
34 |
35 |
--------------------------------------------------------------------------------
/tests/assets/jscoverage/eval.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/assets/jscoverage/involved.html:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/tests/assets/jscoverage/multiple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/tests/assets/jscoverage/ranges.html:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/tests/assets/jscoverage/script1.js:
--------------------------------------------------------------------------------
1 | console.log(3);
2 |
--------------------------------------------------------------------------------
/tests/assets/jscoverage/script2.js:
--------------------------------------------------------------------------------
1 | console.log(3);
2 |
--------------------------------------------------------------------------------
/tests/assets/jscoverage/simple.html:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/tests/assets/jscoverage/sourceurl.html:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/tests/assets/jscoverage/unused.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/assets/load-event/load-event.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Load Event Test
7 |
8 |
9 |
18 |
19 |
22 |
23 |
--------------------------------------------------------------------------------
/tests/assets/load-event/module.js:
--------------------------------------------------------------------------------
1 | import {foo} from '/slow.js';
2 | console.log('foo is', foo);
3 | window.results.push('module');
4 |
--------------------------------------------------------------------------------
/tests/assets/media-query-prefers-color-scheme.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/tests/assets/mobile.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/assets/modernizr.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
22 |
--------------------------------------------------------------------------------
/tests/assets/movie.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/movie.mp4
--------------------------------------------------------------------------------
/tests/assets/movie.ogv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/movie.ogv
--------------------------------------------------------------------------------
/tests/assets/mui.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/tests/assets/networkidle-frame.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/assets/networkidle.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/assets/networkidle.js:
--------------------------------------------------------------------------------
1 | async function main() {
2 | window.ws = new WebSocket('ws://localhost:' + window.location.port + '/ws');
3 | window.ws.addEventListener('message', message => {});
4 |
5 | fetch('fetch-request-a.js');
6 | window.top.fetchSecond = () => {
7 | // Do not return the promise here.
8 | fetch('fetch-request-b.js');
9 | };
10 | }
11 |
12 | main();
13 |
--------------------------------------------------------------------------------
/tests/assets/offscreenbuttons.html:
--------------------------------------------------------------------------------
1 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
56 |
--------------------------------------------------------------------------------
/tests/assets/one-style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: pink;
3 | }
4 |
--------------------------------------------------------------------------------
/tests/assets/one-style.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | hello, world!
4 |
--------------------------------------------------------------------------------
/tests/assets/overflow-large.html:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
23 |
--------------------------------------------------------------------------------
/tests/assets/overflow.html:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
23 |
--------------------------------------------------------------------------------
/tests/assets/player.html:
--------------------------------------------------------------------------------
1 |
55 |
56 |
60 |
61 |
--------------------------------------------------------------------------------
/tests/assets/playground.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Playground
5 |
6 |
7 |
8 |
9 | First div
10 |
11 | Second div
12 | Inner span
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/assets/popup/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Popup
5 |
8 |
9 |
10 | I am a popup
11 |
12 |
13 |
--------------------------------------------------------------------------------
/tests/assets/popup/window-open.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Popup test
5 |
6 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/tests/assets/pptr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/pptr.png
--------------------------------------------------------------------------------
/tests/assets/redirectloop1.html:
--------------------------------------------------------------------------------
1 | >
10 |
--------------------------------------------------------------------------------
/tests/assets/redirectloop2.html:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/tests/assets/resetcss.html:
--------------------------------------------------------------------------------
1 |
51 |
--------------------------------------------------------------------------------
/tests/assets/rotate-pseudo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/tests/assets/rotate-z-shadow-dom.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
31 |
32 |
--------------------------------------------------------------------------------
/tests/assets/rotate-z.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/tests/assets/screenshots/canvas.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
11 |
--------------------------------------------------------------------------------
/tests/assets/screenshots/controls.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tests/assets/screenshots/translateZ.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/tests/assets/screenshots/webgl.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
64 |
--------------------------------------------------------------------------------
/tests/assets/sectionselectorengine.js:
--------------------------------------------------------------------------------
1 | ({
2 | query(root, selector) {
3 | return root.querySelector('section');
4 | },
5 | queryAll(root, selector) {
6 | return Array.from(root.querySelectorAll('section'));
7 | }
8 | })
--------------------------------------------------------------------------------
/tests/assets/selenium-grid/broken-selenium-driver.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | process.exit(1);
4 |
--------------------------------------------------------------------------------
/tests/assets/selenium-grid/selenium-config-standalone.json:
--------------------------------------------------------------------------------
1 | {
2 | "capabilities":
3 | [
4 | {
5 | "browserName": "chrome",
6 | "maxInstances": 5,
7 | "seleniumProtocol": "WebDriver"
8 | }
9 | ],
10 | "role": "standalone",
11 | "port": 4444,
12 | "debug": false,
13 | "browserTimeout": 0,
14 | "timeout": 1800,
15 | "enablePassThrough": true,
16 | "server": {
17 | "port": 4444
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/assets/self-request.html:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/tests/assets/serviceworkers/empty/sw.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/tests/assets/serviceworkers/empty/sw.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/serviceworkers/empty/sw.js
--------------------------------------------------------------------------------
/tests/assets/serviceworkers/fetch/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: pink;
3 | }
4 |
--------------------------------------------------------------------------------
/tests/assets/serviceworkers/fetch/sw.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/tests/assets/serviceworkers/fetch/sw.js:
--------------------------------------------------------------------------------
1 | self.intercepted = [];
2 |
3 | self.addEventListener('fetch', event => {
4 | self.intercepted.push(event.request.url)
5 | event.respondWith(fetch(event.request));
6 | });
7 |
8 | self.addEventListener('activate', event => {
9 | event.waitUntil(clients.claim());
10 | });
11 |
12 | fetch('/request-from-within-worker.txt')
13 |
--------------------------------------------------------------------------------
/tests/assets/serviceworkers/fetchdummy/sw.html:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/tests/assets/serviceworkers/fetchdummy/sw.js:
--------------------------------------------------------------------------------
1 | self.addEventListener('fetch', event => {
2 | if (event.request.url.endsWith('.html') || event.request.url.includes('passthrough')) {
3 | event.respondWith(fetch(event.request));
4 | return;
5 | }
6 | if (event.request.url.includes('error')) {
7 | event.respondWith(Promise.reject(new Error('uh oh')));
8 | return;
9 | }
10 | const slash = event.request.url.lastIndexOf('/');
11 | const name = event.request.url.substring(slash + 1);
12 | const blob = new Blob(["responseFromServiceWorker:" + name], {type : 'text/css'});
13 | const response = new Response(blob, { "status" : 200 , "statusText" : "OK" });
14 | event.respondWith(response);
15 | });
16 |
17 | self.addEventListener('activate', event => {
18 | event.waitUntil(clients.claim());
19 | });
20 |
--------------------------------------------------------------------------------
/tests/assets/shadow-dom-link.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
21 |
22 |
23 |
24 |
25 |
26 | Sign up
27 |
28 |
29 |
--------------------------------------------------------------------------------
/tests/assets/shadow.html:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/tests/assets/simple-extension/content-script.js:
--------------------------------------------------------------------------------
1 | console.log('hey from the content-script');
2 | self.thisIsTheContentScript = true;
3 |
4 |
--------------------------------------------------------------------------------
/tests/assets/simple-extension/index.js:
--------------------------------------------------------------------------------
1 | // Mock script for background extension
2 | window.MAGIC = 42;
3 |
--------------------------------------------------------------------------------
/tests/assets/simple-extension/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Simple extension",
3 | "version": "0.1",
4 | "background": {
5 | "scripts": ["index.js"]
6 | },
7 | "content_scripts": [{
8 | "matches": [""],
9 | "css": [],
10 | "js": ["content-script.js"]
11 | }],
12 | "permissions": ["background", "activeTab"],
13 | "manifest_version": 2
14 | }
15 |
--------------------------------------------------------------------------------
/tests/assets/simple.json:
--------------------------------------------------------------------------------
1 | {"foo": "bar"}
2 |
--------------------------------------------------------------------------------
/tests/assets/tamperable.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/assets/title.html:
--------------------------------------------------------------------------------
1 |
2 | Woof-Woof
3 |
--------------------------------------------------------------------------------
/tests/assets/video.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 | Video courtesy of
12 | Big Buck Bunny.
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/tests/assets/video_mp4.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 | Video courtesy of
11 | Big Buck Bunny.
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/assets/wasm/table2.html:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/tests/assets/wasm/table2.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/wasm/table2.wasm
--------------------------------------------------------------------------------
/tests/assets/web-animation.html:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
24 |
--------------------------------------------------------------------------------
/tests/assets/webfont/README.md:
--------------------------------------------------------------------------------
1 | This icon font was generated:
2 | - using SVG icons from https://github.com/primer/octicons
3 | - bundling icons into webfonts using https://github.com/fontello/fontello
4 |
--------------------------------------------------------------------------------
/tests/assets/webfont/iconfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
--------------------------------------------------------------------------------
/tests/assets/webfont/iconfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/assets/webfont/iconfont.woff2
--------------------------------------------------------------------------------
/tests/assets/webfont/webfont.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 | +-
18 |
19 |
--------------------------------------------------------------------------------
/tests/assets/window-stop.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/tests/assets/worker/import-me.js:
--------------------------------------------------------------------------------
1 | console.log("hello from import-me.js");
2 |
--------------------------------------------------------------------------------
/tests/assets/worker/worker-http-import.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Remote importScripts Test
5 |
6 |
7 | -
8 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/assets/worker/worker-http-import.js:
--------------------------------------------------------------------------------
1 | console.log("hello from worker-http-import.js");
2 | importScripts("./import-me.js")
3 | console.log("successfully imported");
4 | self.postMessage("finished");
5 |
--------------------------------------------------------------------------------
/tests/assets/worker/worker.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Worker test
5 |
6 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/tests/assets/worker/worker.js:
--------------------------------------------------------------------------------
1 | console.log('hello from the worker');
2 |
3 | function workerFunction() {
4 | return 'worker function result';
5 | }
6 |
7 | self.addEventListener('message', event => {
8 | console.log('got this data: ' + event.data);
9 | });
10 |
11 | (async function() {
12 | while (true) {
13 | self.postMessage(workerFunction.toString());
14 | await new Promise(x => setTimeout(x, 100));
15 | }
16 | })();
--------------------------------------------------------------------------------
/tests/assets/wrappedlink.html:
--------------------------------------------------------------------------------
1 |
25 |
28 |
33 |
--------------------------------------------------------------------------------
/tests/cdp_session_test.go:
--------------------------------------------------------------------------------
1 | package playwright_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestCDPSessionSend(t *testing.T) {
10 | BeforeEach(t)
11 |
12 | cdpSession, err := browser.NewBrowserCDPSession()
13 | if isChromium {
14 | require.NoError(t, err)
15 | result, err := cdpSession.Send("Target.getTargets", nil)
16 | require.NoError(t, err)
17 | targetInfos := result.(map[string]interface{})["targetInfos"].([]interface{})
18 | require.GreaterOrEqual(t, len(targetInfos), 1)
19 | } else {
20 | require.Error(t, err)
21 | }
22 | }
23 |
24 | func TestCDPSessionOn(t *testing.T) {
25 | BeforeEach(t)
26 |
27 | cdpSession, err := page.Context().NewCDPSession(page)
28 | if isChromium {
29 | require.NoError(t, err)
30 | _, err = cdpSession.Send("Console.enable", nil)
31 | require.NoError(t, err)
32 | cdpSession.On("Console.messageAdded", func(params map[string]interface{}) {
33 | require.NotNil(t, params)
34 | })
35 | _, err = page.Evaluate(`console.log("hello")`)
36 | require.NoError(t, err)
37 | require.NoError(t, cdpSession.Detach())
38 | } else {
39 | require.Error(t, err)
40 | }
41 | }
42 |
43 | func TestCDPSessionDetach(t *testing.T) {
44 | BeforeEach(t)
45 |
46 | cdpSession, err := browser.NewBrowserCDPSession()
47 | if isChromium {
48 | require.NoError(t, err)
49 | require.NoError(t, cdpSession.Detach())
50 | } else {
51 | require.Error(t, err)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tests/dialog_test.go:
--------------------------------------------------------------------------------
1 | package playwright_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/playwright-community/playwright-go"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestDialog(t *testing.T) {
11 | BeforeEach(t)
12 |
13 | page.OnDialog(func(dialog playwright.Dialog) {
14 | require.Equal(t, "alert", dialog.Type())
15 | require.Equal(t, "", dialog.DefaultValue())
16 | require.Equal(t, "yo", dialog.Message())
17 | require.NoError(t, dialog.Accept())
18 | })
19 | _, err := page.Evaluate("alert('yo')")
20 | require.NoError(t, err)
21 | }
22 |
23 | func TestDialogDismiss(t *testing.T) {
24 | BeforeEach(t)
25 |
26 | page.OnDialog(func(dialog playwright.Dialog) {
27 | require.NoError(t, dialog.Dismiss())
28 | })
29 | result, err := page.Evaluate("prompt('question?')")
30 | require.NoError(t, err)
31 | require.Equal(t, result, nil)
32 | }
33 |
34 | func TestDialogAcceptWithText(t *testing.T) {
35 | BeforeEach(t)
36 |
37 | page.OnDialog(func(dialog playwright.Dialog) {
38 | require.NoError(t, dialog.Accept("hey foobar"))
39 | })
40 | result, err := page.Evaluate("prompt('question?')")
41 | require.NoError(t, err)
42 | require.Equal(t, result, "hey foobar")
43 | }
44 |
45 | func TestDialogShouldWorkInPopup(t *testing.T) {
46 | BeforeEach(t)
47 |
48 | var d playwright.Dialog
49 | context.OnDialog(func(dialog playwright.Dialog) {
50 | d = dialog
51 | require.NoError(t, dialog.Accept("hello"))
52 | })
53 |
54 | popup, err := page.ExpectPopup(func() error {
55 | ret, err := page.Evaluate("() => window.open('').prompt('hey?')")
56 | require.NoError(t, err)
57 | require.Equal(t, "hello", ret)
58 | return nil
59 | })
60 | require.NoError(t, err)
61 | require.Equal(t, "hey?", d.Message())
62 | require.Equal(t, d.Page(), popup)
63 | }
64 |
--------------------------------------------------------------------------------
/tests/download_test.go:
--------------------------------------------------------------------------------
1 | package playwright_test
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 | "path/filepath"
8 | "strings"
9 | "testing"
10 |
11 | "github.com/stretchr/testify/require"
12 | )
13 |
14 | func TestDownloadBasic(t *testing.T) {
15 | BeforeEach(t)
16 |
17 | server.SetRoute("/downloadWithFilename", func(w http.ResponseWriter, r *http.Request) {
18 | w.Header().Add("Content-Type", "application/octet-stream")
19 | w.Header().Add("Content-Disposition", "attachment; filename=file.txt")
20 | if _, err := w.Write([]byte("foobar")); err != nil {
21 | log.Printf("could not write: %v", err)
22 | }
23 | })
24 | require.NoError(t, page.SetContent(
25 | fmt.Sprintf(`download`, server.PREFIX),
26 | ))
27 |
28 | download, err := page.ExpectDownload(func() error {
29 | return page.Locator("a").Click()
30 | })
31 | require.NoError(t, err)
32 | require.Equal(t, page, download.Page())
33 | require.Equal(t, download.URL(), fmt.Sprintf("%s/downloadWithFilename", server.PREFIX))
34 | require.Equal(t, download.SuggestedFilename(), "file.txt")
35 | require.Equal(t, download.String(), "file.txt")
36 | require.NoError(t, download.Failure())
37 |
38 | file, err := download.Path()
39 | require.NoError(t, err)
40 | require.FileExists(t, file)
41 |
42 | tmpFile := filepath.Join(t.TempDir(), download.SuggestedFilename())
43 | require.NoFileExists(t, tmpFile)
44 | require.NoError(t, download.SaveAs(tmpFile))
45 | require.FileExists(t, tmpFile)
46 |
47 | require.NoError(t, download.Delete())
48 | require.NoFileExists(t, file)
49 | }
50 |
51 | func TestDownloadCancel(t *testing.T) {
52 | BeforeEach(t)
53 |
54 | server.SetRoute("/downloadWithDelay", func(w http.ResponseWriter, r *http.Request) {
55 | w.Header().Add("Content-Type", "application/octet-stream")
56 | w.Header().Add("Content-Disposition", "attachment")
57 | if _, err := w.Write([]byte(strings.Repeat("foobar", 8192))); err != nil {
58 | log.Printf("could not write: %v", err)
59 | }
60 | if h, ok := w.(http.Hijacker); ok {
61 | if _, _, err := h.Hijack(); err != nil {
62 | log.Printf("could not hijack connection: %v", err)
63 | }
64 | }
65 | })
66 | require.NoError(t, page.SetContent(
67 | fmt.Sprintf(`download`, server.PREFIX),
68 | ))
69 | download, err := page.ExpectDownload(func() error {
70 | return page.Locator("a").Click()
71 | })
72 | require.NoError(t, err)
73 | require.NoError(t, download.Cancel())
74 | require.Error(t, download.Failure(), "canceled")
75 | }
76 |
--------------------------------------------------------------------------------
/tests/file_chooser_test.go:
--------------------------------------------------------------------------------
1 | package playwright_test
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/playwright-community/playwright-go"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func TestFileChooser(t *testing.T) {
12 | BeforeEach(t)
13 |
14 | _, err := page.Goto(server.PREFIX + "/input/fileupload.html")
15 | require.NoError(t, err)
16 | //nolint:staticcheck
17 | input, err := page.QuerySelector("input")
18 | require.NoError(t, err)
19 | file, err := os.ReadFile(Asset("file-to-upload.txt"))
20 | require.NoError(t, err)
21 | //nolint:staticcheck
22 | require.NoError(t, input.SetInputFiles([]playwright.InputFile{
23 | {
24 | Name: "file-to-upload.txt",
25 | MimeType: "text/plain",
26 | Buffer: file,
27 | },
28 | }))
29 | fileName, err := page.Evaluate("e => e.files[0].name", input)
30 | require.NoError(t, err)
31 | require.Equal(t, "file-to-upload.txt", fileName)
32 | content, err := page.Evaluate(`e => {
33 | reader = new FileReader()
34 | promise = new Promise(fulfill => reader.onload = fulfill)
35 | reader.readAsText(e.files[0])
36 | return promise.then(() => reader.result)
37 | }`, input)
38 | require.NoError(t, err)
39 | require.Equal(t, "contents of the file", content)
40 | }
41 |
42 | func TestFileChooserShouldEmitEvent(t *testing.T) {
43 | BeforeEach(t)
44 |
45 | _, err := page.Goto(server.EMPTY_PAGE)
46 | require.NoError(t, err)
47 | require.NoError(t, page.SetContent(""))
48 |
49 | fileChooser, err := page.ExpectFileChooser(func() error {
50 | return page.Locator("input").Click()
51 | })
52 | require.NoError(t, err)
53 | require.False(t, fileChooser.IsMultiple())
54 | require.Equal(t, page, fileChooser.Page())
55 | //nolint:staticcheck
56 | elementHTML, err := fileChooser.Element().InnerHTML()
57 | require.NoError(t, err)
58 | //nolint:staticcheck
59 | inputElement, err := page.QuerySelector("input")
60 | require.NoError(t, err)
61 | //nolint:staticcheck
62 | inputElementHTML, err := inputElement.InnerHTML()
63 | require.NoError(t, err)
64 |
65 | require.Equal(t, elementHTML, inputElementHTML)
66 |
67 | require.NoError(t, fileChooser.SetFiles([]playwright.InputFile{
68 | {
69 | Name: "file-to-upload.txt",
70 | MimeType: "text/plain",
71 | Buffer: []byte("123"),
72 | },
73 | }))
74 | fileName, err := page.Evaluate("e => e.files[0].name", inputElement)
75 | require.NoError(t, err)
76 | require.Equal(t, "file-to-upload.txt", fileName)
77 | }
78 |
--------------------------------------------------------------------------------
/tests/page_request_gc_test.go:
--------------------------------------------------------------------------------
1 | package playwright_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestPageRequestGC(t *testing.T) {
10 | BeforeEach(t)
11 |
12 | _, err := page.Evaluate(`() => {
13 | globalThis.objectToDestroy = { hello: 'world' };
14 | globalThis.weakRef = new WeakRef(globalThis.objectToDestroy);
15 | }`)
16 | require.NoError(t, err)
17 |
18 | require.NoError(t, page.RequestGC())
19 | ret, err := page.Evaluate(`() => globalThis.weakRef.deref()`)
20 | require.NoError(t, err)
21 | require.Equal(t, map[string]interface{}{"hello": "world"}, ret)
22 |
23 | require.NoError(t, page.RequestGC())
24 | ret, err = page.Evaluate(`() => globalThis.weakRef.deref()`)
25 | require.NoError(t, err)
26 | require.Equal(t, map[string]interface{}{"hello": "world"}, ret)
27 |
28 | _, err = page.Evaluate(`() => globalThis.objectToDestroy = null`)
29 | require.NoError(t, err)
30 |
31 | require.NoError(t, page.RequestGC())
32 | ret, err = page.Evaluate(`() => globalThis.weakRef.deref()`)
33 | require.NoError(t, err)
34 | require.Nil(t, ret)
35 | }
36 |
--------------------------------------------------------------------------------
/tests/remote_server_test.go:
--------------------------------------------------------------------------------
1 | package playwright_test
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "os"
7 | "os/exec"
8 | "strings"
9 |
10 | "github.com/playwright-community/playwright-go"
11 | )
12 |
13 | type remoteServer struct {
14 | url string
15 | cmd *exec.Cmd
16 | }
17 |
18 | func newRemoteServer() (*remoteServer, error) {
19 | driver, err := playwright.NewDriver(&playwright.RunOptions{})
20 | if err != nil {
21 | return nil, fmt.Errorf("could not start Playwright: %v", err)
22 | }
23 | cmd := driver.Command("launch-server", "--browser", browserName)
24 | cmd.Stderr = os.Stderr
25 | stdout, err := cmd.StdoutPipe()
26 | if err != nil {
27 | return nil, fmt.Errorf("could not get stdout pipe: %v", err)
28 | }
29 | err = cmd.Start()
30 | if err != nil {
31 | return nil, fmt.Errorf("could not start server: %v", err)
32 | }
33 | scanner := bufio.NewReader(stdout)
34 | url, err := scanner.ReadString('\n')
35 | url = strings.TrimRight(url, "\n")
36 | if err != nil {
37 | return nil, fmt.Errorf("could not read url: %v", err)
38 | }
39 | return &remoteServer{
40 | url: url,
41 | cmd: cmd,
42 | }, nil
43 | }
44 |
45 | func (s *remoteServer) Close() {
46 | _ = s.cmd.Process.Kill()
47 | _ = s.cmd.Wait()
48 | }
49 |
--------------------------------------------------------------------------------
/tests/screenshot-snapshots/mask-should-work-chromium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/screenshot-snapshots/mask-should-work-chromium.png
--------------------------------------------------------------------------------
/tests/screenshot-snapshots/mask-should-work-firefox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/screenshot-snapshots/mask-should-work-firefox.png
--------------------------------------------------------------------------------
/tests/screenshot-snapshots/mask-should-work-webkit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/screenshot-snapshots/mask-should-work-webkit.png
--------------------------------------------------------------------------------
/tests/screenshot-snapshots/mask-should-work-with-elementhandle-chromium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/screenshot-snapshots/mask-should-work-with-elementhandle-chromium.png
--------------------------------------------------------------------------------
/tests/screenshot-snapshots/mask-should-work-with-elementhandle-firefox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/screenshot-snapshots/mask-should-work-with-elementhandle-firefox.png
--------------------------------------------------------------------------------
/tests/screenshot-snapshots/mask-should-work-with-elementhandle-webkit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/screenshot-snapshots/mask-should-work-with-elementhandle-webkit.png
--------------------------------------------------------------------------------
/tests/screenshot-snapshots/mask-should-work-with-locator-chromium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/screenshot-snapshots/mask-should-work-with-locator-chromium.png
--------------------------------------------------------------------------------
/tests/screenshot-snapshots/mask-should-work-with-locator-firefox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/screenshot-snapshots/mask-should-work-with-locator-firefox.png
--------------------------------------------------------------------------------
/tests/screenshot-snapshots/mask-should-work-with-locator-webkit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/screenshot-snapshots/mask-should-work-with-locator-webkit.png
--------------------------------------------------------------------------------
/tests/screenshot-snapshots/screenshot-element-bounding-box-chromium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/screenshot-snapshots/screenshot-element-bounding-box-chromium.png
--------------------------------------------------------------------------------
/tests/screenshot-snapshots/screenshot-element-bounding-box-firefox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/screenshot-snapshots/screenshot-element-bounding-box-firefox.png
--------------------------------------------------------------------------------
/tests/screenshot-snapshots/screenshot-element-bounding-box-webkit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/playwright-community/playwright-go/4d70e13a55649abcfe582deeecbeeaf6e0ba6427/tests/screenshot-snapshots/screenshot-element-bounding-box-webkit.png
--------------------------------------------------------------------------------
/tests/screenshot_test.go:
--------------------------------------------------------------------------------
1 | package playwright_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/playwright-community/playwright-go"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestLocatorScreenshotShouldWork(t *testing.T) {
11 | BeforeEach(t)
12 |
13 | require.NoError(t, page.SetViewportSize(500, 500))
14 | _, err := page.Goto(server.PREFIX + "/grid.html")
15 | require.NoError(t, err)
16 | _, err = page.Evaluate(`window.scrollBy(50, 100)`)
17 | require.NoError(t, err)
18 | screenshot, err := page.Locator(".box:nth-of-type(3)").Screenshot()
19 | require.NoError(t, err)
20 | require.NotEmpty(t, screenshot)
21 | AssertToBeGolden(t, screenshot, "screenshot-element-bounding-box.png")
22 | }
23 |
24 | func TestShouldScreenshotWithMask(t *testing.T) {
25 | BeforeEach(t)
26 |
27 | require.NoError(t, page.SetViewportSize(500, 500))
28 | _, err := page.Goto(server.PREFIX + "/grid.html")
29 | require.NoError(t, err)
30 |
31 | screenshot, err := page.Screenshot(playwright.PageScreenshotOptions{
32 | Mask: []playwright.Locator{
33 | page.Locator("div").Nth(5),
34 | },
35 | })
36 | require.NoError(t, err)
37 | AssertToBeGolden(t, screenshot, "mask-should-work.png")
38 |
39 | screenshot, err = page.Locator("body").Screenshot(playwright.LocatorScreenshotOptions{
40 | Mask: []playwright.Locator{
41 | page.Locator("div").Nth(5),
42 | },
43 | })
44 | require.NoError(t, err)
45 | AssertToBeGolden(t, screenshot, "mask-should-work-with-locator.png")
46 |
47 | //nolint:staticcheck
48 | element, err := page.QuerySelector("body")
49 | require.NoError(t, err)
50 | //nolint:staticcheck
51 | screenshot, err = element.Screenshot(playwright.ElementHandleScreenshotOptions{
52 | Mask: []playwright.Locator{
53 | page.Locator("div").Nth(5),
54 | },
55 | })
56 | require.NoError(t, err)
57 | AssertToBeGolden(t, screenshot, "mask-should-work-with-elementhandle.png")
58 | }
59 |
--------------------------------------------------------------------------------
/type_helpers.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | // String is a helper routine that allocates a new string value
4 | // to store v and returns a pointer to it.
5 | func String(v string) *string {
6 | return &v
7 | }
8 |
9 | // Bool is a helper routine that allocates a new bool value
10 | // to store v and returns a pointer to it.
11 | func Bool(v bool) *bool {
12 | return &v
13 | }
14 |
15 | // Int is a helper routine that allocates a new int32 value
16 | // to store v and returns a pointer to it.
17 | func Int(v int) *int {
18 | return &v
19 | }
20 |
21 | // Float is a helper routine that allocates a new float64 value
22 | // to store v and returns a pointer to it.
23 | func Float(v float64) *float64 {
24 | return &v
25 | }
26 |
27 | // Null will be used in certain scenarios where a strict nil pointer
28 | // check is not possible
29 | func Null() interface{} {
30 | return "PW_NULL"
31 | }
32 |
33 | // StringSlice is a helper routine that allocates a new StringSlice value
34 | // to store v and returns a pointer to it.
35 | func StringSlice(v ...string) *[]string {
36 | var o []string
37 | o = append(o, v...)
38 | return &o
39 | }
40 |
41 | // IntSlice is a helper routine that allocates a new IntSlice value
42 | // to store v and returns a pointer to it.
43 | func IntSlice(v ...int) *[]int {
44 | var o []int
45 | o = append(o, v...)
46 | return &o
47 | }
48 |
49 | // ToOptionalStorageState converts StorageState to OptionalStorageState for use directly in [Browser.NewContext]
50 | func (s StorageState) ToOptionalStorageState() *OptionalStorageState {
51 | cookies := make([]OptionalCookie, len(s.Cookies))
52 | for i, c := range s.Cookies {
53 | cookies[i] = c.ToOptionalCookie()
54 | }
55 | return &OptionalStorageState{
56 | Origins: s.Origins,
57 | Cookies: cookies,
58 | }
59 | }
60 |
61 | func (c Cookie) ToOptionalCookie() OptionalCookie {
62 | return OptionalCookie{
63 | Name: c.Name,
64 | Value: c.Value,
65 | Domain: String(c.Domain),
66 | Path: String(c.Path),
67 | Expires: Float(c.Expires),
68 | HttpOnly: Bool(c.HttpOnly),
69 | Secure: Bool(c.Secure),
70 | SameSite: c.SameSite,
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/video.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | import (
4 | "errors"
5 | "sync"
6 | )
7 |
8 | type videoImpl struct {
9 | page *pageImpl
10 | artifact *artifactImpl
11 | artifactChan chan *artifactImpl
12 | done chan struct{}
13 | closeOnce sync.Once
14 | isRemote bool
15 | }
16 |
17 | func (v *videoImpl) Path() (string, error) {
18 | if v.isRemote {
19 | return "", errors.New("Path is not available when connecting remotely. Use SaveAs() to save a local copy.")
20 | }
21 | v.getArtifact()
22 | if v.artifact == nil {
23 | return "", errors.New("Page did not produce any video frames")
24 | }
25 | return v.artifact.AbsolutePath(), nil
26 | }
27 |
28 | func (v *videoImpl) Delete() error {
29 | v.getArtifact()
30 | if v.artifact == nil {
31 | return nil
32 | }
33 | return v.artifact.Delete()
34 | }
35 |
36 | func (v *videoImpl) SaveAs(path string) error {
37 | if !v.page.IsClosed() {
38 | return errors.New("Page is not yet closed. Close the page prior to calling SaveAs")
39 | }
40 | v.getArtifact()
41 | if v.artifact == nil {
42 | return errors.New("Page did not produce any video frames")
43 | }
44 | return v.artifact.SaveAs(path)
45 | }
46 |
47 | func (v *videoImpl) artifactReady(artifact *artifactImpl) {
48 | v.artifactChan <- artifact
49 | }
50 |
51 | func (v *videoImpl) pageClosed(p Page) {
52 | v.closeOnce.Do(func() {
53 | close(v.done)
54 | })
55 | }
56 |
57 | func (v *videoImpl) getArtifact() {
58 | // prevent channel block if no video will be produced
59 | if v.page.browserContext.options == nil {
60 | v.pageClosed(v.page)
61 | } else {
62 | option := v.page.browserContext.options
63 | if option == nil || option.RecordVideo == nil { // no recordVideo option
64 | v.pageClosed(v.page)
65 | }
66 | }
67 | select {
68 | case artifact := <-v.artifactChan:
69 | if artifact != nil {
70 | v.artifact = artifact
71 | }
72 | case <-v.done: // page closed
73 | select { // make sure get artifact if it's ready before page closed
74 | case artifact := <-v.artifactChan:
75 | if artifact != nil {
76 | v.artifact = artifact
77 | }
78 | default:
79 | }
80 | }
81 | }
82 |
83 | func newVideo(page *pageImpl) *videoImpl {
84 | video := &videoImpl{
85 | page: page,
86 | artifactChan: make(chan *artifactImpl, 1),
87 | done: make(chan struct{}, 1),
88 | isRemote: page.connection.isRemote,
89 | }
90 |
91 | if page.isClosed {
92 | video.pageClosed(page)
93 | } else {
94 | page.OnClose(video.pageClosed)
95 | }
96 | return video
97 | }
98 |
--------------------------------------------------------------------------------
/web_error.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | type webErrorImpl struct {
4 | err error
5 | page Page
6 | }
7 |
8 | func (e *webErrorImpl) Page() Page {
9 | return e.page
10 | }
11 |
12 | func (e *webErrorImpl) Error() error {
13 | return e.err
14 | }
15 |
16 | func newWebError(page Page, err error) WebError {
17 | return &webErrorImpl{
18 | err: err,
19 | page: page,
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/worker.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | type workerImpl struct {
4 | channelOwner
5 | page *pageImpl
6 | context *browserContextImpl
7 | }
8 |
9 | func (w *workerImpl) URL() string {
10 | return w.initializer["url"].(string)
11 | }
12 |
13 | func (w *workerImpl) Evaluate(expression string, options ...interface{}) (interface{}, error) {
14 | var arg interface{}
15 | if len(options) == 1 {
16 | arg = options[0]
17 | }
18 | result, err := w.channel.Send("evaluateExpression", map[string]interface{}{
19 | "expression": expression,
20 | "arg": serializeArgument(arg),
21 | })
22 | if err != nil {
23 | return nil, err
24 | }
25 | return parseResult(result), nil
26 | }
27 |
28 | func (w *workerImpl) EvaluateHandle(expression string, options ...interface{}) (JSHandle, error) {
29 | var arg interface{}
30 | if len(options) == 1 {
31 | arg = options[0]
32 | }
33 | result, err := w.channel.Send("evaluateExpressionHandle", map[string]interface{}{
34 | "expression": expression,
35 | "arg": serializeArgument(arg),
36 | })
37 | if err != nil {
38 | return nil, err
39 | }
40 | return fromChannel(result).(*jsHandleImpl), nil
41 | }
42 |
43 | func (w *workerImpl) onClose() {
44 | if w.page != nil {
45 | w.page.Lock()
46 | workers := make([]Worker, 0)
47 | for i := 0; i < len(w.page.workers); i++ {
48 | if w.page.workers[i] != w {
49 | workers = append(workers, w.page.workers[i])
50 | }
51 | }
52 | w.page.workers = workers
53 | w.page.Unlock()
54 | }
55 | if w.context != nil {
56 | w.context.Lock()
57 | workers := make([]Worker, 0)
58 | for i := 0; i < len(w.context.serviceWorkers); i++ {
59 | if w.context.serviceWorkers[i] != w {
60 | workers = append(workers, w.context.serviceWorkers[i])
61 | }
62 | }
63 | w.context.serviceWorkers = workers
64 | w.context.Unlock()
65 | }
66 | w.Emit("close", w)
67 | }
68 |
69 | func (w *workerImpl) OnClose(fn func(Worker)) {
70 | w.On("close", fn)
71 | }
72 |
73 | func newWorker(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *workerImpl {
74 | bt := &workerImpl{}
75 | bt.createChannelOwner(bt, parent, objectType, guid, initializer)
76 | bt.channel.On("close", bt.onClose)
77 | return bt
78 | }
79 |
--------------------------------------------------------------------------------
/writable_stream.go:
--------------------------------------------------------------------------------
1 | package playwright
2 |
3 | import (
4 | "encoding/base64"
5 | "io"
6 | "os"
7 | )
8 |
9 | type writableStream struct {
10 | channelOwner
11 | }
12 |
13 | func (s *writableStream) Copy(file string) error {
14 | f, err := os.OpenFile(file, os.O_RDONLY, 0)
15 | if err != nil {
16 | return err
17 | }
18 | defer f.Close()
19 |
20 | for {
21 | buf := make([]byte, defaultCopyBufSize)
22 | n, err := f.Read(buf)
23 | if err != nil && err != io.EOF {
24 | return err
25 | }
26 | if n == 0 {
27 | break
28 | }
29 | _, err = s.channel.Send("write", map[string]interface{}{
30 | "binary": base64.StdEncoding.EncodeToString(buf[:n]),
31 | })
32 | if err != nil {
33 | return err
34 | }
35 | }
36 | _, err = s.channel.Send("close")
37 | return err
38 | }
39 |
40 | func newWritableStream(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *writableStream {
41 | stream := &writableStream{}
42 | stream.createChannelOwner(stream, parent, objectType, guid, initializer)
43 | return stream
44 | }
45 |
--------------------------------------------------------------------------------