Skip to content

Commit fcd06e1

Browse files
authored
chore: roll to Playwright v1.57.0 (#578)
* chore: roll to Playwright v1.57.0 Fix playwright submodule commit Rerun gofumpt Change ConsoleMessages to use newConsoleMessage() to properly deserialize event objects Add test coverage Update page_test.go Fix failing end-to-end test Update README.md Fix README Update README.md * Fix unit tests
1 parent 56e30d6 commit fcd06e1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1252
-371
lines changed

CONTRIBUTING.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,20 @@ BROWSER=chromium HEADLESS=1 go test -v --race ./...
2020
### Roll
2121

2222
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 ./...`
23+
2. Download current version of Playwright driver `go run scripts/install-browsers/main.go`
24+
3. Apply patch `bash scripts/apply-patch.sh`
25+
4. 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+
5. Regenerate a new patch `bash scripts/update-patch.sh`
27+
6. Generate go code `go generate ./...`
2828

2929
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:
3030

3131
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:
32+
2. `cd playwright`
33+
3. Revert the patch`git reset HEAD~1`
34+
4. Modify the files under `docs/src/api`, etc. as needed. Available references:
3535
- Protocol `packages/protocol/src/protocol.yml`
3636
- [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 ./...`.
37+
5. Commit the changes `git commit -am "apply patch"`
38+
6. Regenerate a new patch `bash scripts/update-patch.sh`
39+
7. Generate go code `go generate ./...`.

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@
55
[![PkgGoDev](https://pkg.go.dev/badge/github.com/playwright-community/playwright-go)](https://pkg.go.dev/github.com/playwright-community/playwright-go)
66
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](http://opensource.org/licenses/MIT)
77
[![Go Report Card](https://goreportcard.com/badge/github.com/playwright-community/playwright-go)](https://goreportcard.com/report/github.com/playwright-community/playwright-go) ![Build Status](https://github.com/playwright-community/playwright-go/workflows/Go/badge.svg)
8-
[![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://aka.ms/playwright-slack) [![Coverage Status](https://coveralls.io/repos/github/playwright-community/playwright-go/badge.svg?branch=main)](https://coveralls.io/github/playwright-community/playwright-go?branch=main) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-136.0.7103.25-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-137.0-blue.svg?logo=mozilla-firefox)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-18.4-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop -->
8+
[![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://aka.ms/playwright-slack) [![Coverage Status](https://coveralls.io/repos/github/playwright-community/playwright-go/badge.svg?branch=main)](https://coveralls.io/github/playwright-community/playwright-go?branch=main) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-143.0.7499.4-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-144.0.2-blue.svg?logo=mozilla-firefox)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-26.0-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop -->
99

1010
[API reference](https://playwright.dev/docs/api/class-playwright) | [Example recipes](https://github.com/playwright-community/playwright-go/tree/main/examples)
1111

1212
Playwright is a Go library to automate [Chromium](https://www.chromium.org/Home), [Firefox](https://www.mozilla.org/en-US/firefox/new/) and [WebKit](https://webkit.org/) with a single API. Playwright is built to enable cross-browser web automation that is **ever-green**, **capable**, **reliable** and **fast**.
1313

1414
| | Linux | macOS | Windows |
1515
| :--- | :---: | :---: | :---: |
16-
| Chromium <!-- GEN:chromium-version -->136.0.7103.25<!-- GEN:stop --> | | | |
17-
| WebKit <!-- GEN:webkit-version -->18.4<!-- GEN:stop --> | | | |
18-
| Firefox <!-- GEN:firefox-version -->137.0<!-- GEN:stop --> | | | |
16+
| Chromium <!-- GEN:chromium-version -->143.0.7499.4<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
17+
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
18+
| Firefox <!-- GEN:firefox-version -->144.0.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
1919

2020
Headless execution is supported for all the browsers on all platforms.
2121

browser.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ func (b *browserImpl) NewContext(options ...BrowserNewContextOptions) (BrowserCo
9393
context := fromChannel(channel).(*browserContextImpl)
9494
context.browser = b
9595
b.browserType.(*browserTypeImpl).didCreateContext(context, &option, nil)
96+
if err := context.initializeHarFromOptions(); err != nil {
97+
return nil, err
98+
}
9699
return context, nil
97100
}
98101

browser_context.go

Lines changed: 69 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ import (
99
"slices"
1010
"strings"
1111
"sync"
12+
"sync/atomic"
1213

1314
"github.com/playwright-community/playwright-go/internal/safe"
1415
)
1516

1617
type browserContextImpl struct {
1718
channelOwner
1819
timeoutSettings *timeoutSettings
19-
closeWasCalled bool
20+
closeWasCalled atomic.Bool
2021
options *BrowserNewContextOptions
2122
pages []Page
2223
routes []*routeHandlerEntry
@@ -91,7 +92,7 @@ func (b *browserContextImpl) NewCDPSession(page interface{}) (CDPSession, error)
9192
return nil, err
9293
}
9394

94-
cdpSession := fromChannel(channel).(*cdpSessionImpl)
95+
cdpSession := fromChannelWithConnection(channel, b.connection).(*cdpSessionImpl)
9596

9697
return cdpSession, nil
9798
}
@@ -104,7 +105,7 @@ func (b *browserContextImpl) NewPage() (Page, error) {
104105
if err != nil {
105106
return nil, err
106107
}
107-
return fromChannel(channel).(*pageImpl), nil
108+
return fromChannelWithConnection(channel, b.connection).(*pageImpl), nil
108109
}
109110

110111
func (b *browserContextImpl) Cookies(urls ...string) ([]Cookie, error) {
@@ -411,13 +412,13 @@ func (b *browserContextImpl) ExpectPage(cb func() error, options ...BrowserConte
411412
}
412413

413414
func (b *browserContextImpl) Close(options ...BrowserContextCloseOptions) error {
414-
if b.closeWasCalled {
415+
if b.closeWasCalled.Load() {
415416
return nil
416417
}
417418
if len(options) == 1 {
418419
b.closeReason = options[0].Reason
419420
}
420-
b.closeWasCalled = true
421+
b.closeWasCalled.Store(true)
421422

422423
_, err := b.channel.connection.WrapAPICall(func() (interface{}, error) {
423424
return nil, b.request.Dispose(APIRequestContextDisposeOptions{
@@ -438,7 +439,7 @@ func (b *browserContextImpl) Close(options ...BrowserContextCloseOptions) error
438439
if err != nil {
439440
return nil, err
440441
}
441-
artifact := fromChannel(response).(*artifactImpl)
442+
artifact := fromChannelWithConnection(response, b.connection).(*artifactImpl)
442443
// Server side will compress artifact if content is attach or if file is .zip.
443444
needCompressed := strings.HasSuffix(strings.ToLower(harMetaData.Path), ".zip")
444445
if !needCompressed && harMetaData.Content == HarContentPolicyAttach {
@@ -597,7 +598,7 @@ func (b *browserContextImpl) onRoute(route *routeImpl) {
597598
url := route.Request().URL()
598599
for _, handlerEntry := range routes {
599600
// If the page or the context was closed we stall all requests right away.
600-
if (page != nil && page.closeWasCalled) || b.closeWasCalled {
601+
if (page != nil && page.closeWasCalled.Load()) || b.closeWasCalled.Load() {
601602
return
602603
}
603604
if !handlerEntry.Matches(url) {
@@ -644,7 +645,7 @@ func (b *browserContextImpl) pause() <-chan error {
644645

645646
func (b *browserContextImpl) onBackgroundPage(ev map[string]interface{}) {
646647
b.Lock()
647-
p := fromChannel(ev["page"]).(*pageImpl)
648+
p := fromChannelWithConnection(ev["page"], b.connection).(*pageImpl)
648649
p.browserContext = b
649650
b.backgroundPages = append(b.backgroundPages, p)
650651
b.Unlock()
@@ -662,17 +663,41 @@ func (b *browserContextImpl) setOptions(options *BrowserNewContextOptions, trace
662663
options = &BrowserNewContextOptions{}
663664
}
664665
b.options = options
665-
if b.options != nil && b.options.RecordHarPath != nil {
666-
b.harRecorders[""] = harRecordingMetadata{
667-
Path: *b.options.RecordHarPath,
668-
Content: b.options.RecordHarContent,
669-
}
670-
}
671666
if tracesDir != nil {
672667
b.tracing.tracesDir = *tracesDir
673668
}
674669
}
675670

671+
// initializeHarFromOptions starts HAR recording if RecordHarPath is set in options.
672+
// This must be called after context creation to properly register the HAR recorder on the server.
673+
func (b *browserContextImpl) initializeHarFromOptions() error {
674+
if b.options == nil || b.options.RecordHarPath == nil {
675+
return nil
676+
}
677+
path := *b.options.RecordHarPath
678+
// Determine default content policy based on file extension
679+
var content *HarContentPolicy
680+
if strings.HasSuffix(strings.ToLower(path), ".zip") {
681+
content = HarContentPolicyAttach
682+
} else {
683+
content = HarContentPolicyEmbed
684+
}
685+
if b.options.RecordHarContent != nil {
686+
content = b.options.RecordHarContent
687+
} else if b.options.RecordHarOmitContent != nil && *b.options.RecordHarOmitContent {
688+
content = HarContentPolicyOmit
689+
}
690+
mode := HarModeFull
691+
if b.options.RecordHarMode != nil {
692+
mode = b.options.RecordHarMode
693+
}
694+
return b.recordIntoHar(path, browserContextRecordIntoHarOptions{
695+
URL: b.options.RecordHarURLFilter,
696+
UpdateContent: content,
697+
UpdateMode: mode,
698+
})
699+
}
700+
676701
func (b *browserContextImpl) BackgroundPages() []Page {
677702
b.Lock()
678703
defer b.Unlock()
@@ -784,33 +809,49 @@ func newBrowserContext(parent *channelOwner, objectType string, guid string, ini
784809
}
785810
bt.createChannelOwner(bt, parent, objectType, guid, initializer)
786811
if parent.objectType == "Browser" {
787-
bt.browser = fromChannel(parent.channel).(*browserImpl)
812+
bt.browser = fromChannelWithConnection(parent.channel, bt.connection).(*browserImpl)
788813
bt.browser.contexts = append(bt.browser.contexts, bt)
789814
}
790-
bt.tracing = fromChannel(initializer["tracing"]).(*tracingImpl)
791-
bt.request = fromChannel(initializer["requestContext"]).(*apiRequestContextImpl)
815+
bt.tracing = fromChannelWithConnection(initializer["tracing"], bt.connection).(*tracingImpl)
816+
bt.request = fromChannelWithConnection(initializer["requestContext"], bt.connection).(*apiRequestContextImpl)
792817
bt.clock = newClock(bt)
818+
819+
// Register this context with the selectors manager for custom selector engines
820+
if bt.browser != nil && bt.browser.browserType != nil {
821+
if browserType, ok := bt.browser.browserType.(*browserTypeImpl); ok && browserType.playwright != nil {
822+
browserType.playwright.Selectors.(*selectorsImpl).addContext(bt)
823+
}
824+
}
825+
793826
bt.channel.On("bindingCall", func(params map[string]interface{}) {
794-
bt.onBinding(fromChannel(params["binding"]).(*bindingCallImpl))
827+
bt.onBinding(fromChannelWithConnection(params["binding"], bt.connection).(*bindingCallImpl))
795828
})
796829

797-
bt.channel.On("close", bt.onClose)
830+
bt.channel.On("close", func() {
831+
// Unregister this context from the selectors manager
832+
if bt.browser != nil && bt.browser.browserType != nil {
833+
if browserType, ok := bt.browser.browserType.(*browserTypeImpl); ok && browserType.playwright != nil {
834+
browserType.playwright.Selectors.(*selectorsImpl).removeContext(bt)
835+
}
836+
}
837+
bt.onClose()
838+
})
798839
bt.channel.On("page", func(payload map[string]interface{}) {
799-
bt.onPage(fromChannel(payload["page"]).(*pageImpl))
840+
bt.onPage(fromChannelWithConnection(payload["page"], bt.connection).(*pageImpl))
800841
})
801842
bt.channel.On("route", func(params map[string]interface{}) {
802843
bt.channel.CreateTask(func() {
803-
bt.onRoute(fromChannel(params["route"]).(*routeImpl))
844+
bt.onRoute(fromChannelWithConnection(params["route"], bt.connection).(*routeImpl))
804845
})
805846
})
806847
bt.channel.On("webSocketRoute", func(params map[string]interface{}) {
807848
bt.channel.CreateTask(func() {
808-
bt.onWebSocketRoute(fromChannel(params["webSocketRoute"]).(*webSocketRouteImpl))
849+
bt.onWebSocketRoute(fromChannelWithConnection(params["webSocketRoute"], bt.connection).(*webSocketRouteImpl))
809850
})
810851
})
811852
bt.channel.On("backgroundPage", bt.onBackgroundPage)
812853
bt.channel.On("serviceWorker", func(params map[string]interface{}) {
813-
bt.onServiceWorker(fromChannel(params["worker"]).(*workerImpl))
854+
bt.onServiceWorker(fromChannelWithConnection(params["worker"], bt.connection).(*workerImpl))
814855
})
815856
bt.channel.On("console", func(ev map[string]interface{}) {
816857
message := newConsoleMessage(ev)
@@ -820,7 +861,7 @@ func newBrowserContext(parent *channelOwner, objectType string, guid string, ini
820861
}
821862
})
822863
bt.channel.On("dialog", func(params map[string]interface{}) {
823-
dialog := fromChannel(params["dialog"]).(*dialogImpl)
864+
dialog := fromChannelWithConnection(params["dialog"], bt.connection).(*dialogImpl)
824865
go func() {
825866
hasListeners := bt.Emit("dialog", dialog)
826867
page := dialog.page
@@ -857,15 +898,15 @@ func newBrowserContext(parent *channelOwner, objectType string, guid string, ini
857898
},
858899
)
859900
bt.channel.On("request", func(ev map[string]interface{}) {
860-
request := fromChannel(ev["request"]).(*requestImpl)
901+
request := fromChannelWithConnection(ev["request"], bt.connection).(*requestImpl)
861902
page := fromNullableChannel(ev["page"])
862903
bt.Emit("request", request)
863904
if page != nil {
864905
page.(*pageImpl).Emit("request", request)
865906
}
866907
})
867908
bt.channel.On("requestFailed", func(ev map[string]interface{}) {
868-
request := fromChannel(ev["request"]).(*requestImpl)
909+
request := fromChannelWithConnection(ev["request"], bt.connection).(*requestImpl)
869910
failureText := ev["failureText"]
870911
if failureText != nil {
871912
request.failureText = failureText.(string)
@@ -879,7 +920,7 @@ func newBrowserContext(parent *channelOwner, objectType string, guid string, ini
879920
})
880921

881922
bt.channel.On("requestFinished", func(ev map[string]interface{}) {
882-
request := fromChannel(ev["request"]).(*requestImpl)
923+
request := fromChannelWithConnection(ev["request"], bt.connection).(*requestImpl)
883924
response := fromNullableChannel(ev["response"])
884925
page := fromNullableChannel(ev["page"])
885926
request.setResponseEndTiming(ev["responseEndTiming"].(float64))
@@ -892,7 +933,7 @@ func newBrowserContext(parent *channelOwner, objectType string, guid string, ini
892933
}
893934
})
894935
bt.channel.On("response", func(ev map[string]interface{}) {
895-
response := fromChannel(ev["response"]).(*responseImpl)
936+
response := fromChannelWithConnection(ev["response"], bt.connection).(*responseImpl)
896937
page := fromNullableChannel(ev["page"])
897938
bt.Emit("response", response)
898939
if page != nil {

browser_type.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ func (b *browserTypeImpl) ExecutablePath() string {
1919

2020
func (b *browserTypeImpl) Launch(options ...BrowserTypeLaunchOptions) (Browser, error) {
2121
overrides := map[string]interface{}{}
22+
// timeout is required in Playwright v1.57+ protocol
23+
if len(options) == 0 || options[0].Timeout == nil {
24+
overrides["timeout"] = float64(30000) // default 30s
25+
}
2226
if len(options) == 1 && options[0].Env != nil {
2327
overrides["env"] = serializeMapToNameAndValue(options[0].Env)
2428
options[0].Env = nil
@@ -36,6 +40,10 @@ func (b *browserTypeImpl) LaunchPersistentContext(userDataDir string, options ..
3640
overrides := map[string]interface{}{
3741
"userDataDir": userDataDir,
3842
}
43+
// timeout is required in Playwright v1.57+ protocol
44+
if len(options) == 0 || options[0].Timeout == nil {
45+
overrides["timeout"] = float64(30000) // default 30s
46+
}
3947
option := &BrowserNewContextOptions{}
4048
var tracesDir *string = nil
4149
if len(options) == 1 {
@@ -87,22 +95,30 @@ func (b *browserTypeImpl) LaunchPersistentContext(userDataDir string, options ..
8795
options[0].RecordHarOmitContent = nil
8896
}
8997
}
90-
channel, err := b.channel.Send("launchPersistentContext", options, overrides)
98+
response, err := b.channel.SendReturnAsDict("launchPersistentContext", options, overrides)
9199
if err != nil {
92100
return nil, err
93101
}
94-
context := fromChannel(channel).(*browserContextImpl)
102+
context := fromChannel(response["context"]).(*browserContextImpl)
95103
b.didCreateContext(context, option, tracesDir)
104+
if err := context.initializeHarFromOptions(); err != nil {
105+
return nil, err
106+
}
96107
return context, nil
97108
}
98109

99110
func (b *browserTypeImpl) Connect(wsEndpoint string, options ...BrowserTypeConnectOptions) (Browser, error) {
100111
overrides := map[string]interface{}{
101112
"wsEndpoint": wsEndpoint,
102113
"headers": map[string]string{
103-
"x-playwright-browser": b.Name(),
114+
"x-playwright-browser": b.Name(),
115+
"x-playwright-launch-options": "{}",
104116
},
105117
}
118+
// timeout is required in Playwright v1.57+ protocol
119+
if len(options) == 0 || options[0].Timeout == nil {
120+
overrides["timeout"] = float64(0) // default no timeout
121+
}
106122
if len(options) == 1 {
107123
if options[0].Headers != nil {
108124
for k, v := range options[0].Headers {
@@ -147,6 +163,10 @@ func (b *browserTypeImpl) ConnectOverCDP(endpointURL string, options ...BrowserT
147163
overrides := map[string]interface{}{
148164
"endpointURL": endpointURL,
149165
}
166+
// timeout is required in Playwright v1.57+ protocol
167+
if len(options) == 0 || options[0].Timeout == nil {
168+
overrides["timeout"] = float64(30000) // default 30s
169+
}
150170
if len(options) == 1 {
151171
if options[0].Headers != nil {
152172
overrides["headers"] = serializeMapToNameAndValue(options[0].Headers)

channel.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,31 @@ func (c *channel) CreateTask(fn func()) {
3838

3939
func (c *channel) Send(method string, options ...interface{}) (interface{}, error) {
4040
return c.connection.WrapAPICall(func() (interface{}, error) {
41-
return c.innerSend(method, options...).GetResultValue()
41+
result, err := c.innerSend(method, options...).GetResultValue()
42+
if err != nil {
43+
return nil, err
44+
}
45+
// GUIDs are now always eagerly resolved in connection.Dispatch
46+
return result, nil
4247
}, c.owner.isInternalType)
4348
}
4449

4550
func (c *channel) SendReturnAsDict(method string, options ...interface{}) (map[string]interface{}, error) {
4651
ret, err := c.connection.WrapAPICall(func() (interface{}, error) {
47-
return c.innerSend(method, options...).GetResult()
52+
result, err := c.innerSend(method, options...).GetResult()
53+
if err != nil {
54+
return nil, err
55+
}
56+
// GUIDs are now always eagerly resolved in connection.Dispatch
57+
return result, nil
4858
}, c.owner.isInternalType)
49-
return ret.(map[string]interface{}), err
59+
if err != nil {
60+
return nil, err
61+
}
62+
if ret == nil {
63+
return make(map[string]interface{}), nil
64+
}
65+
return ret.(map[string]interface{}), nil
5066
}
5167

5268
func (c *channel) innerSend(method string, options ...interface{}) *protocolCallback {

0 commit comments

Comments
 (0)