mirror of
https://github.com/playwright-community/playwright-go.git
synced 2026-06-03 21:02:27 +08:00
chore: roll to Playwright v1.57.0 (#578)
Some checks failed
Go / Lint (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (chromium, oldstable, macos-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (chromium, oldstable, ubuntu-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (chromium, oldstable, windows-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (chromium, stable, macos-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (chromium, stable, ubuntu-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (chromium, stable, windows-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (firefox, oldstable, macos-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (firefox, oldstable, ubuntu-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (firefox, oldstable, windows-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (firefox, stable, macos-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (firefox, stable, ubuntu-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (firefox, stable, windows-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (webkit, oldstable, macos-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (webkit, oldstable, ubuntu-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (webkit, oldstable, windows-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (webkit, stable, macos-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (webkit, stable, ubuntu-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (webkit, stable, windows-latest) (push) Has been cancelled
Go / test-examples (push) Has been cancelled
Docs / Deploy docs (push) Has been cancelled
Verify Types / verify (push) Has been cancelled
Go / finish (push) Has been cancelled
Some checks failed
Go / Lint (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (chromium, oldstable, macos-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (chromium, oldstable, ubuntu-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (chromium, oldstable, windows-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (chromium, stable, macos-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (chromium, stable, ubuntu-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (chromium, stable, windows-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (firefox, oldstable, macos-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (firefox, oldstable, ubuntu-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (firefox, oldstable, windows-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (firefox, stable, macos-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (firefox, stable, ubuntu-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (firefox, stable, windows-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (webkit, oldstable, macos-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (webkit, oldstable, ubuntu-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (webkit, oldstable, windows-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (webkit, stable, macos-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (webkit, stable, ubuntu-latest) (push) Has been cancelled
Go / ${{ matrix.browser }} on ${{ matrix.os }}, go ${{ matrix.go }} (webkit, stable, windows-latest) (push) Has been cancelled
Go / test-examples (push) Has been cancelled
Docs / Deploy docs (push) Has been cancelled
Verify Types / verify (push) Has been cancelled
Go / finish (push) Has been cancelled
* 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
This commit is contained in:
parent
56e30d60f8
commit
fcd06e1fa6
@ -20,20 +20,20 @@ BROWSER=chromium HEADLESS=1 go test -v --race ./...
|
||||
### Roll
|
||||
|
||||
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.
|
||||
1. Download current version of Playwright driver `go run scripts/install-browsers/main.go`
|
||||
1. Apply patch `bash scripts/apply-patch.sh`
|
||||
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 ..`
|
||||
1. Regenerate a new patch `bash scripts/update-patch.sh`
|
||||
1. Generate go code `go generate ./...`
|
||||
2. Download current version of Playwright driver `go run scripts/install-browsers/main.go`
|
||||
3. Apply patch `bash scripts/apply-patch.sh`
|
||||
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 ..`
|
||||
5. Regenerate a new patch `bash scripts/update-patch.sh`
|
||||
6. Generate go code `go generate ./...`
|
||||
|
||||
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:
|
||||
|
||||
1. Apply patch `bash scripts/apply-patch.sh`
|
||||
1. `cd playwright`
|
||||
1. Revert the patch`git reset HEAD~1`
|
||||
1. Modify the files under `docs/src/api`, etc. as needed. Available references:
|
||||
2. `cd playwright`
|
||||
3. Revert the patch`git reset HEAD~1`
|
||||
4. Modify the files under `docs/src/api`, etc. as needed. Available references:
|
||||
- Protocol `packages/protocol/src/protocol.yml`
|
||||
- [Playwright python](https://github.com/microsoft/playwright-python)
|
||||
1. Commit the changes `git commit -am "apply patch"`
|
||||
1. Regenerate a new patch `bash scripts/update-patch.sh`
|
||||
1. Generate go code `go generate ./...`.
|
||||
5. Commit the changes `git commit -am "apply patch"`
|
||||
6. Regenerate a new patch `bash scripts/update-patch.sh`
|
||||
7. Generate go code `go generate ./...`.
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
[](https://pkg.go.dev/github.com/playwright-community/playwright-go)
|
||||
[](http://opensource.org/licenses/MIT)
|
||||
[](https://goreportcard.com/report/github.com/playwright-community/playwright-go) 
|
||||
[](https://aka.ms/playwright-slack) [](https://coveralls.io/github/playwright-community/playwright-go?branch=main) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](https://webkit.org/)<!-- GEN:stop -->
|
||||
[](https://aka.ms/playwright-slack) [](https://coveralls.io/github/playwright-community/playwright-go?branch=main) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](https://webkit.org/)<!-- GEN:stop -->
|
||||
|
||||
[API reference](https://playwright.dev/docs/api/class-playwright) | [Example recipes](https://github.com/playwright-community/playwright-go/tree/main/examples)
|
||||
|
||||
@ -13,9 +13,9 @@ Playwright is a Go library to automate [Chromium](https://www.chromium.org/Home)
|
||||
|
||||
| | Linux | macOS | Windows |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| Chromium <!-- GEN:chromium-version -->136.0.7103.25<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| WebKit <!-- GEN:webkit-version -->18.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->137.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Chromium <!-- GEN:chromium-version -->143.0.7499.4<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Firefox <!-- GEN:firefox-version -->144.0.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
Headless execution is supported for all the browsers on all platforms.
|
||||
|
||||
|
||||
@ -93,6 +93,9 @@ func (b *browserImpl) NewContext(options ...BrowserNewContextOptions) (BrowserCo
|
||||
context := fromChannel(channel).(*browserContextImpl)
|
||||
context.browser = b
|
||||
b.browserType.(*browserTypeImpl).didCreateContext(context, &option, nil)
|
||||
if err := context.initializeHarFromOptions(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return context, nil
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/playwright-community/playwright-go/internal/safe"
|
||||
)
|
||||
@ -16,7 +17,7 @@ import (
|
||||
type browserContextImpl struct {
|
||||
channelOwner
|
||||
timeoutSettings *timeoutSettings
|
||||
closeWasCalled bool
|
||||
closeWasCalled atomic.Bool
|
||||
options *BrowserNewContextOptions
|
||||
pages []Page
|
||||
routes []*routeHandlerEntry
|
||||
@ -91,7 +92,7 @@ func (b *browserContextImpl) NewCDPSession(page interface{}) (CDPSession, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cdpSession := fromChannel(channel).(*cdpSessionImpl)
|
||||
cdpSession := fromChannelWithConnection(channel, b.connection).(*cdpSessionImpl)
|
||||
|
||||
return cdpSession, nil
|
||||
}
|
||||
@ -104,7 +105,7 @@ func (b *browserContextImpl) NewPage() (Page, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fromChannel(channel).(*pageImpl), nil
|
||||
return fromChannelWithConnection(channel, b.connection).(*pageImpl), nil
|
||||
}
|
||||
|
||||
func (b *browserContextImpl) Cookies(urls ...string) ([]Cookie, error) {
|
||||
@ -411,13 +412,13 @@ func (b *browserContextImpl) ExpectPage(cb func() error, options ...BrowserConte
|
||||
}
|
||||
|
||||
func (b *browserContextImpl) Close(options ...BrowserContextCloseOptions) error {
|
||||
if b.closeWasCalled {
|
||||
if b.closeWasCalled.Load() {
|
||||
return nil
|
||||
}
|
||||
if len(options) == 1 {
|
||||
b.closeReason = options[0].Reason
|
||||
}
|
||||
b.closeWasCalled = true
|
||||
b.closeWasCalled.Store(true)
|
||||
|
||||
_, err := b.channel.connection.WrapAPICall(func() (interface{}, error) {
|
||||
return nil, b.request.Dispose(APIRequestContextDisposeOptions{
|
||||
@ -438,7 +439,7 @@ func (b *browserContextImpl) Close(options ...BrowserContextCloseOptions) error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
artifact := fromChannel(response).(*artifactImpl)
|
||||
artifact := fromChannelWithConnection(response, b.connection).(*artifactImpl)
|
||||
// Server side will compress artifact if content is attach or if file is .zip.
|
||||
needCompressed := strings.HasSuffix(strings.ToLower(harMetaData.Path), ".zip")
|
||||
if !needCompressed && harMetaData.Content == HarContentPolicyAttach {
|
||||
@ -597,7 +598,7 @@ func (b *browserContextImpl) onRoute(route *routeImpl) {
|
||||
url := route.Request().URL()
|
||||
for _, handlerEntry := range routes {
|
||||
// If the page or the context was closed we stall all requests right away.
|
||||
if (page != nil && page.closeWasCalled) || b.closeWasCalled {
|
||||
if (page != nil && page.closeWasCalled.Load()) || b.closeWasCalled.Load() {
|
||||
return
|
||||
}
|
||||
if !handlerEntry.Matches(url) {
|
||||
@ -644,7 +645,7 @@ func (b *browserContextImpl) pause() <-chan error {
|
||||
|
||||
func (b *browserContextImpl) onBackgroundPage(ev map[string]interface{}) {
|
||||
b.Lock()
|
||||
p := fromChannel(ev["page"]).(*pageImpl)
|
||||
p := fromChannelWithConnection(ev["page"], b.connection).(*pageImpl)
|
||||
p.browserContext = b
|
||||
b.backgroundPages = append(b.backgroundPages, p)
|
||||
b.Unlock()
|
||||
@ -662,17 +663,41 @@ func (b *browserContextImpl) setOptions(options *BrowserNewContextOptions, trace
|
||||
options = &BrowserNewContextOptions{}
|
||||
}
|
||||
b.options = options
|
||||
if b.options != nil && b.options.RecordHarPath != nil {
|
||||
b.harRecorders[""] = harRecordingMetadata{
|
||||
Path: *b.options.RecordHarPath,
|
||||
Content: b.options.RecordHarContent,
|
||||
}
|
||||
}
|
||||
if tracesDir != nil {
|
||||
b.tracing.tracesDir = *tracesDir
|
||||
}
|
||||
}
|
||||
|
||||
// initializeHarFromOptions starts HAR recording if RecordHarPath is set in options.
|
||||
// This must be called after context creation to properly register the HAR recorder on the server.
|
||||
func (b *browserContextImpl) initializeHarFromOptions() error {
|
||||
if b.options == nil || b.options.RecordHarPath == nil {
|
||||
return nil
|
||||
}
|
||||
path := *b.options.RecordHarPath
|
||||
// Determine default content policy based on file extension
|
||||
var content *HarContentPolicy
|
||||
if strings.HasSuffix(strings.ToLower(path), ".zip") {
|
||||
content = HarContentPolicyAttach
|
||||
} else {
|
||||
content = HarContentPolicyEmbed
|
||||
}
|
||||
if b.options.RecordHarContent != nil {
|
||||
content = b.options.RecordHarContent
|
||||
} else if b.options.RecordHarOmitContent != nil && *b.options.RecordHarOmitContent {
|
||||
content = HarContentPolicyOmit
|
||||
}
|
||||
mode := HarModeFull
|
||||
if b.options.RecordHarMode != nil {
|
||||
mode = b.options.RecordHarMode
|
||||
}
|
||||
return b.recordIntoHar(path, browserContextRecordIntoHarOptions{
|
||||
URL: b.options.RecordHarURLFilter,
|
||||
UpdateContent: content,
|
||||
UpdateMode: mode,
|
||||
})
|
||||
}
|
||||
|
||||
func (b *browserContextImpl) BackgroundPages() []Page {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
@ -784,33 +809,49 @@ func newBrowserContext(parent *channelOwner, objectType string, guid string, ini
|
||||
}
|
||||
bt.createChannelOwner(bt, parent, objectType, guid, initializer)
|
||||
if parent.objectType == "Browser" {
|
||||
bt.browser = fromChannel(parent.channel).(*browserImpl)
|
||||
bt.browser = fromChannelWithConnection(parent.channel, bt.connection).(*browserImpl)
|
||||
bt.browser.contexts = append(bt.browser.contexts, bt)
|
||||
}
|
||||
bt.tracing = fromChannel(initializer["tracing"]).(*tracingImpl)
|
||||
bt.request = fromChannel(initializer["requestContext"]).(*apiRequestContextImpl)
|
||||
bt.tracing = fromChannelWithConnection(initializer["tracing"], bt.connection).(*tracingImpl)
|
||||
bt.request = fromChannelWithConnection(initializer["requestContext"], bt.connection).(*apiRequestContextImpl)
|
||||
bt.clock = newClock(bt)
|
||||
|
||||
// Register this context with the selectors manager for custom selector engines
|
||||
if bt.browser != nil && bt.browser.browserType != nil {
|
||||
if browserType, ok := bt.browser.browserType.(*browserTypeImpl); ok && browserType.playwright != nil {
|
||||
browserType.playwright.Selectors.(*selectorsImpl).addContext(bt)
|
||||
}
|
||||
}
|
||||
|
||||
bt.channel.On("bindingCall", func(params map[string]interface{}) {
|
||||
bt.onBinding(fromChannel(params["binding"]).(*bindingCallImpl))
|
||||
bt.onBinding(fromChannelWithConnection(params["binding"], bt.connection).(*bindingCallImpl))
|
||||
})
|
||||
|
||||
bt.channel.On("close", bt.onClose)
|
||||
bt.channel.On("close", func() {
|
||||
// Unregister this context from the selectors manager
|
||||
if bt.browser != nil && bt.browser.browserType != nil {
|
||||
if browserType, ok := bt.browser.browserType.(*browserTypeImpl); ok && browserType.playwright != nil {
|
||||
browserType.playwright.Selectors.(*selectorsImpl).removeContext(bt)
|
||||
}
|
||||
}
|
||||
bt.onClose()
|
||||
})
|
||||
bt.channel.On("page", func(payload map[string]interface{}) {
|
||||
bt.onPage(fromChannel(payload["page"]).(*pageImpl))
|
||||
bt.onPage(fromChannelWithConnection(payload["page"], bt.connection).(*pageImpl))
|
||||
})
|
||||
bt.channel.On("route", func(params map[string]interface{}) {
|
||||
bt.channel.CreateTask(func() {
|
||||
bt.onRoute(fromChannel(params["route"]).(*routeImpl))
|
||||
bt.onRoute(fromChannelWithConnection(params["route"], bt.connection).(*routeImpl))
|
||||
})
|
||||
})
|
||||
bt.channel.On("webSocketRoute", func(params map[string]interface{}) {
|
||||
bt.channel.CreateTask(func() {
|
||||
bt.onWebSocketRoute(fromChannel(params["webSocketRoute"]).(*webSocketRouteImpl))
|
||||
bt.onWebSocketRoute(fromChannelWithConnection(params["webSocketRoute"], bt.connection).(*webSocketRouteImpl))
|
||||
})
|
||||
})
|
||||
bt.channel.On("backgroundPage", bt.onBackgroundPage)
|
||||
bt.channel.On("serviceWorker", func(params map[string]interface{}) {
|
||||
bt.onServiceWorker(fromChannel(params["worker"]).(*workerImpl))
|
||||
bt.onServiceWorker(fromChannelWithConnection(params["worker"], bt.connection).(*workerImpl))
|
||||
})
|
||||
bt.channel.On("console", func(ev map[string]interface{}) {
|
||||
message := newConsoleMessage(ev)
|
||||
@ -820,7 +861,7 @@ func newBrowserContext(parent *channelOwner, objectType string, guid string, ini
|
||||
}
|
||||
})
|
||||
bt.channel.On("dialog", func(params map[string]interface{}) {
|
||||
dialog := fromChannel(params["dialog"]).(*dialogImpl)
|
||||
dialog := fromChannelWithConnection(params["dialog"], bt.connection).(*dialogImpl)
|
||||
go func() {
|
||||
hasListeners := bt.Emit("dialog", dialog)
|
||||
page := dialog.page
|
||||
@ -857,7 +898,7 @@ func newBrowserContext(parent *channelOwner, objectType string, guid string, ini
|
||||
},
|
||||
)
|
||||
bt.channel.On("request", func(ev map[string]interface{}) {
|
||||
request := fromChannel(ev["request"]).(*requestImpl)
|
||||
request := fromChannelWithConnection(ev["request"], bt.connection).(*requestImpl)
|
||||
page := fromNullableChannel(ev["page"])
|
||||
bt.Emit("request", request)
|
||||
if page != nil {
|
||||
@ -865,7 +906,7 @@ func newBrowserContext(parent *channelOwner, objectType string, guid string, ini
|
||||
}
|
||||
})
|
||||
bt.channel.On("requestFailed", func(ev map[string]interface{}) {
|
||||
request := fromChannel(ev["request"]).(*requestImpl)
|
||||
request := fromChannelWithConnection(ev["request"], bt.connection).(*requestImpl)
|
||||
failureText := ev["failureText"]
|
||||
if failureText != nil {
|
||||
request.failureText = failureText.(string)
|
||||
@ -879,7 +920,7 @@ func newBrowserContext(parent *channelOwner, objectType string, guid string, ini
|
||||
})
|
||||
|
||||
bt.channel.On("requestFinished", func(ev map[string]interface{}) {
|
||||
request := fromChannel(ev["request"]).(*requestImpl)
|
||||
request := fromChannelWithConnection(ev["request"], bt.connection).(*requestImpl)
|
||||
response := fromNullableChannel(ev["response"])
|
||||
page := fromNullableChannel(ev["page"])
|
||||
request.setResponseEndTiming(ev["responseEndTiming"].(float64))
|
||||
@ -892,7 +933,7 @@ func newBrowserContext(parent *channelOwner, objectType string, guid string, ini
|
||||
}
|
||||
})
|
||||
bt.channel.On("response", func(ev map[string]interface{}) {
|
||||
response := fromChannel(ev["response"]).(*responseImpl)
|
||||
response := fromChannelWithConnection(ev["response"], bt.connection).(*responseImpl)
|
||||
page := fromNullableChannel(ev["page"])
|
||||
bt.Emit("response", response)
|
||||
if page != nil {
|
||||
|
||||
@ -19,6 +19,10 @@ func (b *browserTypeImpl) ExecutablePath() string {
|
||||
|
||||
func (b *browserTypeImpl) Launch(options ...BrowserTypeLaunchOptions) (Browser, error) {
|
||||
overrides := map[string]interface{}{}
|
||||
// timeout is required in Playwright v1.57+ protocol
|
||||
if len(options) == 0 || options[0].Timeout == nil {
|
||||
overrides["timeout"] = float64(30000) // default 30s
|
||||
}
|
||||
if len(options) == 1 && options[0].Env != nil {
|
||||
overrides["env"] = serializeMapToNameAndValue(options[0].Env)
|
||||
options[0].Env = nil
|
||||
@ -36,6 +40,10 @@ func (b *browserTypeImpl) LaunchPersistentContext(userDataDir string, options ..
|
||||
overrides := map[string]interface{}{
|
||||
"userDataDir": userDataDir,
|
||||
}
|
||||
// timeout is required in Playwright v1.57+ protocol
|
||||
if len(options) == 0 || options[0].Timeout == nil {
|
||||
overrides["timeout"] = float64(30000) // default 30s
|
||||
}
|
||||
option := &BrowserNewContextOptions{}
|
||||
var tracesDir *string = nil
|
||||
if len(options) == 1 {
|
||||
@ -87,12 +95,15 @@ func (b *browserTypeImpl) LaunchPersistentContext(userDataDir string, options ..
|
||||
options[0].RecordHarOmitContent = nil
|
||||
}
|
||||
}
|
||||
channel, err := b.channel.Send("launchPersistentContext", options, overrides)
|
||||
response, err := b.channel.SendReturnAsDict("launchPersistentContext", options, overrides)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
context := fromChannel(channel).(*browserContextImpl)
|
||||
context := fromChannel(response["context"]).(*browserContextImpl)
|
||||
b.didCreateContext(context, option, tracesDir)
|
||||
if err := context.initializeHarFromOptions(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return context, nil
|
||||
}
|
||||
|
||||
@ -100,9 +111,14 @@ func (b *browserTypeImpl) Connect(wsEndpoint string, options ...BrowserTypeConne
|
||||
overrides := map[string]interface{}{
|
||||
"wsEndpoint": wsEndpoint,
|
||||
"headers": map[string]string{
|
||||
"x-playwright-browser": b.Name(),
|
||||
"x-playwright-browser": b.Name(),
|
||||
"x-playwright-launch-options": "{}",
|
||||
},
|
||||
}
|
||||
// timeout is required in Playwright v1.57+ protocol
|
||||
if len(options) == 0 || options[0].Timeout == nil {
|
||||
overrides["timeout"] = float64(0) // default no timeout
|
||||
}
|
||||
if len(options) == 1 {
|
||||
if options[0].Headers != nil {
|
||||
for k, v := range options[0].Headers {
|
||||
@ -147,6 +163,10 @@ func (b *browserTypeImpl) ConnectOverCDP(endpointURL string, options ...BrowserT
|
||||
overrides := map[string]interface{}{
|
||||
"endpointURL": endpointURL,
|
||||
}
|
||||
// timeout is required in Playwright v1.57+ protocol
|
||||
if len(options) == 0 || options[0].Timeout == nil {
|
||||
overrides["timeout"] = float64(30000) // default 30s
|
||||
}
|
||||
if len(options) == 1 {
|
||||
if options[0].Headers != nil {
|
||||
overrides["headers"] = serializeMapToNameAndValue(options[0].Headers)
|
||||
|
||||
22
channel.go
22
channel.go
@ -38,15 +38,31 @@ func (c *channel) CreateTask(fn func()) {
|
||||
|
||||
func (c *channel) Send(method string, options ...interface{}) (interface{}, error) {
|
||||
return c.connection.WrapAPICall(func() (interface{}, error) {
|
||||
return c.innerSend(method, options...).GetResultValue()
|
||||
result, err := c.innerSend(method, options...).GetResultValue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// GUIDs are now always eagerly resolved in connection.Dispatch
|
||||
return result, nil
|
||||
}, c.owner.isInternalType)
|
||||
}
|
||||
|
||||
func (c *channel) SendReturnAsDict(method string, options ...interface{}) (map[string]interface{}, error) {
|
||||
ret, err := c.connection.WrapAPICall(func() (interface{}, error) {
|
||||
return c.innerSend(method, options...).GetResult()
|
||||
result, err := c.innerSend(method, options...).GetResult()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// GUIDs are now always eagerly resolved in connection.Dispatch
|
||||
return result, nil
|
||||
}, c.owner.isInternalType)
|
||||
return ret.(map[string]interface{}), err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ret == nil {
|
||||
return make(map[string]interface{}), nil
|
||||
}
|
||||
return ret.(map[string]interface{}), nil
|
||||
}
|
||||
|
||||
func (c *channel) innerSend(method string, options ...interface{}) *protocolCallback {
|
||||
|
||||
@ -111,7 +111,9 @@ func (r *rootChannelOwner) initialize() (*Playwright, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fromChannel(ret["playwright"]).(*Playwright), nil
|
||||
// GUIDs are now always eagerly resolved in connection.Dispatch
|
||||
playwrightValue := ret["playwright"]
|
||||
return fromChannel(playwrightValue).(*Playwright), nil
|
||||
}
|
||||
|
||||
func newRootChannelOwner(connection *connection) *rootChannelOwner {
|
||||
|
||||
@ -100,15 +100,27 @@ func (c *connection) Dispatch(msg *message) {
|
||||
if msg.Error != nil {
|
||||
cb.SetError(parseError(msg.Error.Error))
|
||||
} else {
|
||||
cb.SetResult(c.replaceGuidsWithChannels(msg.Result).(map[string]interface{}))
|
||||
// Always resolve GUIDs in responses, regardless of connection type
|
||||
// The protocol guarantees that __create__ events arrive before responses that reference those objects
|
||||
result, err := c.replaceGuidsWithChannels(msg.Result)
|
||||
if err != nil {
|
||||
cb.SetError(fmt.Errorf("failed to resolve response objects: %w", err))
|
||||
} else {
|
||||
cb.SetResult(result.(map[string]interface{}))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
object, _ := c.objects.Load(msg.GUID)
|
||||
if method == "__create__" {
|
||||
c.createRemoteObject(
|
||||
_, err := c.createRemoteObject(
|
||||
object, msg.Params["type"].(string), msg.Params["guid"].(string), msg.Params["initializer"],
|
||||
)
|
||||
if err != nil {
|
||||
// Critical: object creation failure indicates corrupted protocol state
|
||||
// Close connection to prevent cascade failures
|
||||
c.cleanup(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if object == nil {
|
||||
@ -134,7 +146,15 @@ func (c *connection) Dispatch(msg *message) {
|
||||
if object.objectType == "JsonPipe" {
|
||||
object.channel.Emit(method, msg.Params)
|
||||
} else {
|
||||
object.channel.Emit(method, c.replaceGuidsWithChannels(msg.Params))
|
||||
// Always resolve GUIDs in events, regardless of connection type
|
||||
// The protocol guarantees that __create__ events arrive before events that reference those objects
|
||||
params, err := c.replaceGuidsWithChannels(msg.Params)
|
||||
if err != nil {
|
||||
// Event parameters contain invalid references - connection is corrupted
|
||||
c.cleanup(fmt.Errorf("failed to resolve event parameters for %s: %w", method, err))
|
||||
return
|
||||
}
|
||||
object.channel.Emit(method, params)
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,10 +162,13 @@ func (c *connection) LocalUtils() *localUtilsImpl {
|
||||
return c.localUtils
|
||||
}
|
||||
|
||||
func (c *connection) createRemoteObject(parent *channelOwner, objectType string, guid string, initializer interface{}) interface{} {
|
||||
initializer = c.replaceGuidsWithChannels(initializer)
|
||||
result := createObjectFactory(parent, objectType, guid, initializer.(map[string]interface{}))
|
||||
return result
|
||||
func (c *connection) createRemoteObject(parent *channelOwner, objectType string, guid string, initializer interface{}) (interface{}, error) {
|
||||
resolved, err := c.replaceGuidsWithChannels(initializer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve initializer for %s (guid=%s): %w", objectType, guid, err)
|
||||
}
|
||||
result := createObjectFactory(parent, objectType, guid, resolved.(map[string]interface{}))
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *connection) WrapAPICall(cb func() (interface{}, error), isInternal bool) (interface{}, error) {
|
||||
@ -156,31 +179,48 @@ func (c *connection) WrapAPICall(cb func() (interface{}, error), isInternal bool
|
||||
return cb()
|
||||
}
|
||||
|
||||
func (c *connection) replaceGuidsWithChannels(payload interface{}) interface{} {
|
||||
func (c *connection) replaceGuidsWithChannels(payload interface{}) (interface{}, error) {
|
||||
if payload == nil {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
v := reflect.ValueOf(payload)
|
||||
if v.Kind() == reflect.Slice {
|
||||
listV := payload.([]interface{})
|
||||
for i := 0; i < len(listV); i++ {
|
||||
listV[i] = c.replaceGuidsWithChannels(listV[i])
|
||||
resolved, err := c.replaceGuidsWithChannels(listV[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve slice element at index %d: %w", i, err)
|
||||
}
|
||||
listV[i] = resolved
|
||||
}
|
||||
return listV
|
||||
return listV, nil
|
||||
}
|
||||
if v.Kind() == reflect.Map {
|
||||
mapV := payload.(map[string]interface{})
|
||||
// Check if this map represents an object reference (has "guid" field)
|
||||
if guid, hasGUID := mapV["guid"]; hasGUID {
|
||||
if channelOwner, ok := c.objects.Load(guid.(string)); ok {
|
||||
return channelOwner.channel
|
||||
guidStr, ok := guid.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("guid field is not a string: %T", guid)
|
||||
}
|
||||
// Try to load the object from connection's objects map
|
||||
if channelOwner, ok := c.objects.Load(guidStr); ok {
|
||||
return channelOwner.channel, nil
|
||||
}
|
||||
// Object not found - this indicates a protocol error or message ordering issue
|
||||
return nil, fmt.Errorf("object with guid %s was not bound in the connection", guidStr)
|
||||
}
|
||||
// Recursively process all values in the map
|
||||
for key := range mapV {
|
||||
mapV[key] = c.replaceGuidsWithChannels(mapV[key])
|
||||
resolved, err := c.replaceGuidsWithChannels(mapV[key])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve map key '%s': %w", key, err)
|
||||
}
|
||||
mapV[key] = resolved
|
||||
}
|
||||
return mapV
|
||||
return mapV, nil
|
||||
}
|
||||
return payload
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func (c *connection) sendMessageToServer(object *channelOwner, method string, params interface{}, noReply bool) (cb *protocolCallback) {
|
||||
@ -314,7 +354,20 @@ func newConnection(transport transport, localUtils ...*localUtilsImpl) *connecti
|
||||
}
|
||||
|
||||
func fromChannel(v interface{}) interface{} {
|
||||
return v.(*channel).object
|
||||
if ch, ok := v.(*channel); ok {
|
||||
return ch.object
|
||||
}
|
||||
panic(fmt.Sprintf("fromChannel: expected *channel, got %T", v))
|
||||
}
|
||||
|
||||
// fromChannelWithConnection resolves a value to a channel object
|
||||
// With the protocol fix, GUIDs are always eagerly resolved, so this behaves identically to fromChannel
|
||||
// The conn parameter is kept for API compatibility but is no longer used
|
||||
func fromChannelWithConnection(v interface{}, conn *connection) interface{} {
|
||||
if ch, ok := v.(*channel); ok {
|
||||
return ch.object
|
||||
}
|
||||
panic(fmt.Sprintf("fromChannelWithConnection: expected *channel, got %T: %+v", v, v))
|
||||
}
|
||||
|
||||
func fromNullableChannel(v interface{}) interface{} {
|
||||
@ -337,7 +390,9 @@ func (pc *protocolCallback) setResultOnce(result map[string]interface{}, err err
|
||||
pc.once.Do(func() {
|
||||
pc.value = result
|
||||
pc.err = err
|
||||
close(pc.done)
|
||||
if pc.done != nil {
|
||||
close(pc.done)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
package playwright
|
||||
|
||||
type consoleMessageImpl struct {
|
||||
event map[string]interface{}
|
||||
page Page
|
||||
event map[string]interface{}
|
||||
page Page
|
||||
worker Worker
|
||||
}
|
||||
|
||||
func (c *consoleMessageImpl) Type() string {
|
||||
@ -36,6 +37,10 @@ func (c *consoleMessageImpl) Page() Page {
|
||||
return c.page
|
||||
}
|
||||
|
||||
func (c *consoleMessageImpl) Worker() (Worker, error) {
|
||||
return c.worker, nil
|
||||
}
|
||||
|
||||
func newConsoleMessage(event map[string]interface{}) *consoleMessageImpl {
|
||||
bt := &consoleMessageImpl{}
|
||||
bt.event = event
|
||||
@ -43,5 +48,9 @@ func newConsoleMessage(event map[string]interface{}) *consoleMessageImpl {
|
||||
if page != nil {
|
||||
bt.page = page.(*pageImpl)
|
||||
}
|
||||
worker := fromNullableChannel(event["worker"])
|
||||
if worker != nil {
|
||||
bt.worker = worker.(*workerImpl)
|
||||
}
|
||||
return bt
|
||||
}
|
||||
|
||||
@ -72,12 +72,20 @@ func main() {
|
||||
|
||||
// Filter for active entries. There should be 0, because we have completed the entry already
|
||||
assertErrorToNilf("could not click: %v", page.Locator("text=Active").Click())
|
||||
// Wait is necessary because headless is too fast
|
||||
assertErrorToNilf("could not wait for selector state: %v", page.Locator("ul.todo-list > li").WaitFor(playwright.LocatorWaitForOptions{
|
||||
State: playwright.WaitForSelectorStateDetached,
|
||||
}))
|
||||
assertCountOfTodos(0)
|
||||
|
||||
// If we filter now for completed entries, there should be 1
|
||||
assertErrorToNilf("could not click: %v", page.GetByRole("link", playwright.PageGetByRoleOptions{
|
||||
Name: "Completed",
|
||||
}).Click())
|
||||
// Wait is necessary because headless is too fast
|
||||
assertErrorToNilf("could not wait for selector state: %v", page.Locator("ul.todo-list > li").WaitFor(playwright.LocatorWaitForOptions{
|
||||
State: playwright.WaitForSelectorStateVisible,
|
||||
}))
|
||||
assertCountOfTodos(1)
|
||||
|
||||
// Clear the list of completed entries, then it should be again 0
|
||||
|
||||
22
fetch.go
22
fetch.go
@ -41,13 +41,20 @@ func (r *apiRequestImpl) NewContext(options ...APIRequestNewContextOptions) (API
|
||||
options[0].StorageState = storageState
|
||||
options[0].StorageStatePath = nil
|
||||
}
|
||||
if options[0].Timeout != nil {
|
||||
overrides["timeout"] = options[0].Timeout
|
||||
}
|
||||
}
|
||||
|
||||
channel, err := r.channel.Send("newRequest", options, overrides)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fromChannel(channel).(*apiRequestContextImpl), nil
|
||||
ctx := fromChannel(channel).(*apiRequestContextImpl)
|
||||
if len(options) == 1 && options[0].Timeout != nil {
|
||||
ctx.defaultTimeout = options[0].Timeout
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func newApiRequestImpl(pw *Playwright) *apiRequestImpl {
|
||||
@ -56,8 +63,9 @@ func newApiRequestImpl(pw *Playwright) *apiRequestImpl {
|
||||
|
||||
type apiRequestContextImpl struct {
|
||||
channelOwner
|
||||
tracing *tracingImpl
|
||||
closeReason *string
|
||||
tracing *tracingImpl
|
||||
closeReason *string
|
||||
defaultTimeout *float64
|
||||
}
|
||||
|
||||
func (r *apiRequestContextImpl) Dispose(options ...APIRequestContextDisposeOptions) error {
|
||||
@ -207,6 +215,10 @@ func (r *apiRequestContextImpl) innerFetch(url string, request Request, options
|
||||
overrides["params"] = serializeMapToNameValue(options[0].Params)
|
||||
options[0].Params = nil
|
||||
}
|
||||
// Use context-level timeout as default if no per-request timeout specified
|
||||
if options[0].Timeout == nil && r.defaultTimeout != nil {
|
||||
overrides["timeout"] = *r.defaultTimeout
|
||||
}
|
||||
}
|
||||
|
||||
response, err := r.channel.Send("fetch", options, overrides)
|
||||
@ -312,7 +324,9 @@ func (r *apiRequestContextImpl) StorageState(path ...string) (*StorageState, err
|
||||
func newAPIRequestContext(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *apiRequestContextImpl {
|
||||
rc := &apiRequestContextImpl{}
|
||||
rc.createChannelOwner(rc, parent, objectType, guid, initializer)
|
||||
rc.tracing = fromChannel(initializer["tracing"]).(*tracingImpl)
|
||||
if tracingValue := initializer["tracing"]; tracingValue != nil {
|
||||
rc.tracing = fromNullableChannel(tracingValue).(*tracingImpl)
|
||||
}
|
||||
return rc
|
||||
}
|
||||
|
||||
|
||||
32
frame.go
32
frame.go
@ -60,9 +60,14 @@ func (f *frameImpl) Name() string {
|
||||
}
|
||||
|
||||
func (f *frameImpl) SetContent(content string, options ...FrameSetContentOptions) error {
|
||||
_, err := f.channel.Send("setContent", map[string]interface{}{
|
||||
overrides := map[string]interface{}{
|
||||
"html": content,
|
||||
}, options)
|
||||
}
|
||||
// timeout is required in Playwright v1.57+ protocol
|
||||
if len(options) == 0 || options[0].Timeout == nil {
|
||||
overrides["timeout"] = f.page.timeoutSettings.NavigationTimeout()
|
||||
}
|
||||
_, err := f.channel.Send("setContent", overrides, options)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -75,9 +80,14 @@ func (f *frameImpl) Content() (string, error) {
|
||||
}
|
||||
|
||||
func (f *frameImpl) Goto(url string, options ...FrameGotoOptions) (Response, error) {
|
||||
channel, err := f.channel.Send("goto", map[string]interface{}{
|
||||
overrides := map[string]interface{}{
|
||||
"url": url,
|
||||
}, options)
|
||||
}
|
||||
// timeout is required in Playwright v1.57+ protocol
|
||||
if len(options) == 0 || options[0].Timeout == nil {
|
||||
overrides["timeout"] = f.page.timeoutSettings.NavigationTimeout()
|
||||
}
|
||||
channel, err := f.channel.Send("goto", overrides, options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Frame.Goto %s: %w", url, err)
|
||||
}
|
||||
@ -414,7 +424,7 @@ func (f *frameImpl) DispatchEvent(selector, typ string, eventInit interface{}, o
|
||||
"selector": selector,
|
||||
"type": typ,
|
||||
"eventInit": serializeArgument(eventInit),
|
||||
})
|
||||
}, options)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -505,12 +515,18 @@ func (f *frameImpl) WaitForFunction(expression string, arg interface{}, options
|
||||
if len(options) == 1 {
|
||||
option = options[0]
|
||||
}
|
||||
result, err := f.channel.Send("waitForFunction", map[string]interface{}{
|
||||
overrides := map[string]interface{}{
|
||||
"expression": expression,
|
||||
"arg": serializeArgument(arg),
|
||||
"timeout": option.Timeout,
|
||||
"polling": option.Polling,
|
||||
})
|
||||
}
|
||||
// timeout is required in Playwright v1.57+ protocol
|
||||
if option.Timeout == nil {
|
||||
overrides["timeout"] = f.page.timeoutSettings.Timeout()
|
||||
} else {
|
||||
overrides["timeout"] = option.Timeout
|
||||
}
|
||||
result, err := f.channel.Send("waitForFunction", overrides)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -217,8 +217,9 @@ type Browser interface {
|
||||
// Non-persistent browser contexts don't write any browsing data to disk.
|
||||
type BrowserContext interface {
|
||||
EventEmitter
|
||||
// **NOTE** Only works with Chromium browser's persistent context.
|
||||
// Emitted when new background page is created in the context.
|
||||
// This event is not emitted.
|
||||
//
|
||||
// Deprecated: Background pages have been removed from Chromium together with Manifest V2 extensions.
|
||||
OnBackgroundPage(fn func(Page))
|
||||
|
||||
// Playwright has ability to mock clock and passage of time.
|
||||
@ -292,11 +293,13 @@ type BrowserContext interface {
|
||||
// script: Script to be evaluated in all pages in the browser context.
|
||||
AddInitScript(script Script) error
|
||||
|
||||
// **NOTE** Background pages are only supported on Chromium-based browsers.
|
||||
// All existing background pages in the context.
|
||||
// Returns an empty list.
|
||||
//
|
||||
// Deprecated: Background pages have been removed from Chromium together with Manifest V2 extensions.
|
||||
BackgroundPages() []Page
|
||||
|
||||
// Returns the browser instance of the context. If it was launched as a persistent context null gets returned.
|
||||
// Gets the browser instance that owns the context. Returns `null` if the context is created outside of normal
|
||||
// browser, e.g. Android or Electron.
|
||||
Browser() Browser
|
||||
|
||||
// Removes cookies from context. Accepts optional filter.
|
||||
@ -351,6 +354,8 @@ type BrowserContext interface {
|
||||
// - `'clipboard-write'`
|
||||
// - `'geolocation'`
|
||||
// - `'gyroscope'`
|
||||
// - `'local-fonts'`
|
||||
// - `'local-network-access'`
|
||||
// - `'magnetometer'`
|
||||
// - `'microphone'`
|
||||
// - `'midi-sysex'` (system-exclusive midi)
|
||||
@ -538,6 +543,12 @@ type BrowserType interface {
|
||||
// **parent** directory of the "Profile Path" seen at `chrome://version`.
|
||||
//
|
||||
// Note that browsers do not allow launching multiple instances with the same User Data Directory.
|
||||
//
|
||||
// **NOTE** Chromium/Chrome: Due to recent Chrome policy changes, automating the default Chrome user profile is not
|
||||
// supported. Pointing `userDataDir` to Chrome's main "User Data" directory (the profile used for your regular
|
||||
// browsing) may result in pages not loading or the browser exiting. Create and use a separate directory (for example,
|
||||
// an empty folder) as your automation profile instead. See https://developer.chrome.com/blog/remote-debugging-port
|
||||
// for details.
|
||||
LaunchPersistentContext(userDataDir string, options ...BrowserTypeLaunchPersistentContextOptions) (BrowserContext, error)
|
||||
|
||||
// Returns browser name. For example: `chromium`, `webkit` or `firefox`.
|
||||
@ -651,6 +662,10 @@ type ConsoleMessage interface {
|
||||
// `trace`, `clear`, `startGroup`, `startGroupCollapsed`, `endGroup`, `assert`, `profile`,
|
||||
// `profileEnd`, `count`, `timeEnd`.
|
||||
Type() string
|
||||
|
||||
// The web worker or service worker that produced this console message, if any. Note that console messages from web
|
||||
// workers also have non-null [ConsoleMessage.Page].
|
||||
Worker() (Worker, error)
|
||||
}
|
||||
|
||||
// [Dialog] objects are dispatched by page via the [Page.OnDialog] event.
|
||||
@ -2323,6 +2338,17 @@ type Locator interface {
|
||||
// [actionability]: https://playwright.dev/docs/actionability
|
||||
Dblclick(options ...LocatorDblclickOptions) error
|
||||
|
||||
// Describes the locator, description is used in the trace viewer and reports. Returns the locator pointing to the
|
||||
// same element.
|
||||
//
|
||||
// description: Locator description.
|
||||
Describe(description string) Locator
|
||||
|
||||
// Returns locator description previously set with [Locator.Describe]. Returns `null` if no custom description has
|
||||
// been set. Prefer `Locator.toString()` for a human-readable representation, as it uses the description when
|
||||
// available.
|
||||
Description() (string, error)
|
||||
|
||||
// Programmatically dispatch an event on the matching element.
|
||||
//
|
||||
// # Details
|
||||
@ -3029,7 +3055,13 @@ type LocatorAssertions interface {
|
||||
}
|
||||
|
||||
// The Mouse class operates in main-frame CSS pixels relative to the top-left corner of the viewport.
|
||||
// **NOTE** If you want to debug where the mouse moved, you can use the [Trace viewer] or
|
||||
// [Playwright Inspector]. A red dot showing the location of the mouse will be shown for every
|
||||
// mouse action.
|
||||
// Every `page` object has its own Mouse, accessible with [Page.Mouse].
|
||||
//
|
||||
// [Trace viewer]: https://playwright.dev/docs/trace-viewer-intro
|
||||
// [Playwright Inspector]: https://playwright.dev/docs/running-tests
|
||||
type Mouse interface {
|
||||
// Shortcut for [Mouse.Move], [Mouse.Down], [Mouse.Up].
|
||||
//
|
||||
@ -3669,6 +3701,9 @@ type Page interface {
|
||||
|
||||
Keyboard() Keyboard
|
||||
|
||||
// Returns up to (currently) 200 last console messages from this page. See [Page.OnConsole] for more details.
|
||||
ConsoleMessages() ([]ConsoleMessage, error)
|
||||
|
||||
// The method returns an element locator that can be used to perform actions on this page / frame. Locator is resolved
|
||||
// to the element immediately before performing an action, so a series of actions on the same locator can in fact be
|
||||
// performed on different DOM elements. That would happen if the DOM structure between those actions has changed.
|
||||
@ -3687,8 +3722,8 @@ type Page interface {
|
||||
// Returns the opener for popup pages and `null` for others. If the opener has been closed already the returns `null`.
|
||||
Opener() (Page, error)
|
||||
|
||||
// Pauses script execution. Playwright will stop executing the script and wait for the user to either press 'Resume'
|
||||
// button in the page overlay or to call `playwright.resume()` in the DevTools console.
|
||||
// Pauses script execution. Playwright will stop executing the script and wait for the user to either press the
|
||||
// 'Resume' button in the page overlay or to call `playwright.resume()` in the DevTools console.
|
||||
// User can inspect selectors or perform manual steps while paused. Resume will continue running the original script
|
||||
// from the place it was paused.
|
||||
// **NOTE** This method requires Playwright to be started in a headed mode, with a falsy “[object Object]” option.
|
||||
@ -3751,6 +3786,14 @@ type Page interface {
|
||||
// [locators]: https://playwright.dev/docs/locators
|
||||
QuerySelectorAll(selector string) ([]ElementHandle, error)
|
||||
|
||||
// Returns up to (currently) 100 last network request from this page. See [Page.OnRequest] for more details.
|
||||
// Returned requests should be accessed immediately, otherwise they might be collected to prevent unbounded memory
|
||||
// growth as new requests come in. Once collected, retrieving most information about the request is impossible.
|
||||
// Note that requests reported through the [Page.OnRequest] request are not collected, so there is a trade off between
|
||||
// efficient memory usage with [Page.Requests] and the amount of available information reported through
|
||||
// [Page.OnRequest].
|
||||
Requests() ([]Request, error)
|
||||
|
||||
// When testing a web page, sometimes unexpected overlays like a "Sign up" dialog appear and block actions you want to
|
||||
// automate, e.g. clicking a button. These overlays don't always show up in the same way or at the same time, making
|
||||
// them tricky to handle in automated tests.
|
||||
@ -4452,11 +4495,30 @@ type Touchscreen interface {
|
||||
|
||||
// API for collecting and saving Playwright traces. Playwright traces can be opened in
|
||||
// [Trace Viewer] after Playwright script runs.
|
||||
// **NOTE** You probably want to
|
||||
// [enable tracing in your config file] instead
|
||||
// of using `context.tracing`.
|
||||
// The `context.tracing` API captures browser operations and network activity, but it doesn't record test assertions
|
||||
// (like `expect` calls). We recommend
|
||||
// [enabling tracing through Playwright Test configuration],
|
||||
// which includes those assertions and provides a more complete trace for debugging test failures.
|
||||
// Start recording a trace before performing actions. At the end, stop tracing and save it to a file.
|
||||
//
|
||||
// [Trace Viewer]: https://playwright.dev/docs/trace-viewer
|
||||
// [enable tracing in your config file]: https://playwright.dev/docs/api/class-testoptions#test-options-trace
|
||||
// [enabling tracing through Playwright Test configuration]: https://playwright.dev/docs/api/class-testoptions#test-options-trace
|
||||
type Tracing interface {
|
||||
// Start tracing.
|
||||
// **NOTE** You probably want to
|
||||
// [enable tracing in your config file] instead
|
||||
// of using `Tracing.start`.
|
||||
// The `context.tracing` API captures browser operations and network activity, but it doesn't record test assertions
|
||||
// (like `expect` calls). We recommend
|
||||
// [enabling tracing through Playwright Test configuration],
|
||||
// which includes those assertions and provides a more complete trace for debugging test failures.
|
||||
//
|
||||
// [enable tracing in your config file]: https://playwright.dev/docs/api/class-testoptions#test-options-trace
|
||||
// [enabling tracing through Playwright Test configuration]: https://playwright.dev/docs/api/class-testoptions#test-options-trace
|
||||
Start(options ...TracingStartOptions) error
|
||||
|
||||
// Start a new trace chunk. If you'd like to record multiple traces on the same [BrowserContext], use [Tracing.Start]
|
||||
@ -4547,7 +4609,7 @@ type WebSocket interface {
|
||||
// [Page.RouteWebSocket] or [BrowserContext.RouteWebSocket], the `WebSocketRoute` object allows to handle the
|
||||
// WebSocket, like an actual server would do.
|
||||
// **Mocking**
|
||||
// By default, the routed WebSocket will not connect to the server. This way, you can mock entire communcation over
|
||||
// By default, the routed WebSocket will not connect to the server. This way, you can mock entire communication over
|
||||
// the WebSocket. Here is an example that responds to a `"request"` with a `"response"`.
|
||||
// Since we do not call [WebSocketRoute.ConnectToServer] inside the WebSocket route handler, Playwright assumes that
|
||||
// WebSocket will be mocked, and opens the WebSocket inside the page automatically.
|
||||
@ -4631,6 +4693,9 @@ type Worker interface {
|
||||
// [WebWorker]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
|
||||
OnClose(fn func(Worker))
|
||||
|
||||
// Emitted when JavaScript within the worker calls one of console API methods, e.g. `console.log` or `console.dir`.
|
||||
OnConsole(fn func(ConsoleMessage))
|
||||
|
||||
// Returns the return value of “[object Object]”.
|
||||
// If the function passed to the [Worker.Evaluate] returns a [Promise], then [Worker.Evaluate] would wait for the
|
||||
// promise to resolve and return its value.
|
||||
|
||||
@ -20,6 +20,9 @@ type APIRequestNewContextOptions struct {
|
||||
// a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally,
|
||||
// `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided
|
||||
// with an exact match to the request origin that the certificate is valid for.
|
||||
// Client certificate authentication is only active when at least one client certificate is provided. If you want to
|
||||
// reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
|
||||
// does not match any of the domains you plan to visit.
|
||||
// **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
|
||||
// work by replacing `localhost` with `local.playwright`.
|
||||
ClientCertificates []ClientCertificate `json:"clientCertificates"`
|
||||
@ -327,8 +330,8 @@ type APIRequestContextPutOptions struct {
|
||||
}
|
||||
|
||||
type StorageState struct {
|
||||
Cookies []Cookie `json:"cookies"`
|
||||
Origins []Origin `json:"origins"`
|
||||
Cookies []StorageStateCookie `json:"cookies"`
|
||||
Origins []Origin `json:"origins"`
|
||||
}
|
||||
|
||||
type NameValue struct {
|
||||
@ -367,6 +370,9 @@ type BrowserNewContextOptions struct {
|
||||
// a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally,
|
||||
// `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided
|
||||
// with an exact match to the request origin that the certificate is valid for.
|
||||
// Client certificate authentication is only active when at least one client certificate is provided. If you want to
|
||||
// reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
|
||||
// does not match any of the domains you plan to visit.
|
||||
// **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
|
||||
// work by replacing `localhost` with `local.playwright`.
|
||||
ClientCertificates []ClientCertificate `json:"clientCertificates"`
|
||||
@ -514,6 +520,9 @@ type BrowserNewPageOptions struct {
|
||||
// a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally,
|
||||
// `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided
|
||||
// with an exact match to the request origin that the certificate is valid for.
|
||||
// Client certificate authentication is only active when at least one client certificate is provided. If you want to
|
||||
// reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
|
||||
// does not match any of the domains you plan to visit.
|
||||
// **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
|
||||
// work by replacing `localhost` with `local.playwright`.
|
||||
ClientCertificates []ClientCertificate `json:"clientCertificates"`
|
||||
@ -651,12 +660,12 @@ type BrowserStartTracingOptions struct {
|
||||
type OptionalCookie struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
// Either url or domain / path are required. Optional.
|
||||
// Either `url` or both `domain` and `path` are required. Optional.
|
||||
URL *string `json:"url"`
|
||||
// For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com". Either url
|
||||
// or domain / path are required. Optional.
|
||||
// For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com". Either
|
||||
// `url` or both `domain` and `path` are required. Optional.
|
||||
Domain *string `json:"domain"`
|
||||
// Either url or domain / path are required Optional.
|
||||
// Either `url` or both `domain` and `path` are required. Optional.
|
||||
Path *string `json:"path"`
|
||||
// Unix time in seconds. Optional.
|
||||
Expires *float64 `json:"expires"`
|
||||
@ -666,6 +675,12 @@ type OptionalCookie struct {
|
||||
Secure *bool `json:"secure"`
|
||||
// Optional.
|
||||
SameSite *SameSiteAttribute `json:"sameSite"`
|
||||
// For partitioned third-party cookies (aka
|
||||
// [CHIPS], the
|
||||
// partition key. Optional.
|
||||
//
|
||||
// [CHIPS]: https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/Privacy_sandbox/Partitioned_cookies)
|
||||
PartitionKey *string `json:"partitionKey"`
|
||||
}
|
||||
|
||||
type Script struct {
|
||||
@ -696,10 +711,11 @@ type Cookie struct {
|
||||
Domain string `json:"domain"`
|
||||
Path string `json:"path"`
|
||||
// Unix time in seconds.
|
||||
Expires float64 `json:"expires"`
|
||||
HttpOnly bool `json:"httpOnly"`
|
||||
Secure bool `json:"secure"`
|
||||
SameSite *SameSiteAttribute `json:"sameSite"`
|
||||
Expires float64 `json:"expires"`
|
||||
HttpOnly bool `json:"httpOnly"`
|
||||
Secure bool `json:"secure"`
|
||||
SameSite *SameSiteAttribute `json:"sameSite"`
|
||||
PartitionKey *string `json:"partitionKey"`
|
||||
}
|
||||
|
||||
type BrowserContextGrantPermissionsOptions struct {
|
||||
@ -847,8 +863,11 @@ type BrowserTypeLaunchOptions struct {
|
||||
ExecutablePath *string `json:"executablePath"`
|
||||
// Firefox user preferences. Learn more about the Firefox user preferences at
|
||||
// [`about:config`].
|
||||
// You can also provide a path to a custom [`policies.json` file] via
|
||||
// `PLAYWRIGHT_FIREFOX_POLICIES_JSON` environment variable.
|
||||
//
|
||||
// [`about:config`]: https://support.mozilla.org/en-US/kb/about-config-editor-firefox
|
||||
// [`policies.json` file]: https://mozilla.github.io/policy-templates/
|
||||
FirefoxUserPrefs map[string]interface{} `json:"firefoxUserPrefs"`
|
||||
// Close the browser process on SIGHUP. Defaults to `true`.
|
||||
HandleSIGHUP *bool `json:"handleSIGHUP"`
|
||||
@ -922,6 +941,9 @@ type BrowserTypeLaunchPersistentContextOptions struct {
|
||||
// a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally,
|
||||
// `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided
|
||||
// with an exact match to the request origin that the certificate is valid for.
|
||||
// Client certificate authentication is only active when at least one client certificate is provided. If you want to
|
||||
// reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
|
||||
// does not match any of the domains you plan to visit.
|
||||
// **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
|
||||
// work by replacing `localhost` with `local.playwright`.
|
||||
ClientCertificates []ClientCertificate `json:"clientCertificates"`
|
||||
@ -957,8 +979,11 @@ type BrowserTypeLaunchPersistentContextOptions struct {
|
||||
ExtraHttpHeaders map[string]string `json:"extraHTTPHeaders"`
|
||||
// Firefox user preferences. Learn more about the Firefox user preferences at
|
||||
// [`about:config`].
|
||||
// You can also provide a path to a custom [`policies.json` file] via
|
||||
// `PLAYWRIGHT_FIREFOX_POLICIES_JSON` environment variable.
|
||||
//
|
||||
// [`about:config`]: https://support.mozilla.org/en-US/kb/about-config-editor-firefox
|
||||
// [`policies.json` file]: https://mozilla.github.io/policy-templates/
|
||||
FirefoxUserPrefs map[string]interface{} `json:"firefoxUserPrefs"`
|
||||
// Emulates `forced-colors` media feature, supported values are `active`, `none`. See [Page.EmulateMedia] for
|
||||
// more details. Passing `no-override` resets emulation to system defaults. Defaults to `none`.
|
||||
@ -1157,6 +1182,9 @@ type ElementHandleClickOptions struct {
|
||||
// A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of
|
||||
// the element.
|
||||
Position *Position `json:"position"`
|
||||
// Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between Playwright's current cursor
|
||||
// position and the provided destination. When set to 1, emits a single `mousemove` event at the destination location.
|
||||
Steps *int `json:"steps"`
|
||||
// Maximum time in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can
|
||||
// be changed by using the [BrowserContext.SetDefaultTimeout] or [Page.SetDefaultTimeout] methods.
|
||||
Timeout *float64 `json:"timeout"`
|
||||
@ -1187,6 +1215,9 @@ type ElementHandleDblclickOptions struct {
|
||||
// A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of
|
||||
// the element.
|
||||
Position *Position `json:"position"`
|
||||
// Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between Playwright's current cursor
|
||||
// position and the provided destination. When set to 1, emits a single `mousemove` event at the destination location.
|
||||
Steps *int `json:"steps"`
|
||||
// Maximum time in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can
|
||||
// be changed by using the [BrowserContext.SetDefaultTimeout] or [Page.SetDefaultTimeout] methods.
|
||||
Timeout *float64 `json:"timeout"`
|
||||
@ -1533,6 +1564,9 @@ type FrameClickOptions struct {
|
||||
// A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of
|
||||
// the element.
|
||||
Position *Position `json:"position"`
|
||||
// Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between Playwright's current cursor
|
||||
// position and the provided destination. When set to 1, emits a single `mousemove` event at the destination location.
|
||||
Steps *int `json:"steps"`
|
||||
// When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
|
||||
// element, the call throws an exception.
|
||||
Strict *bool `json:"strict"`
|
||||
@ -1604,6 +1638,9 @@ type FrameDragAndDropOptions struct {
|
||||
// Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not
|
||||
// specified, some visible point of the element is used.
|
||||
SourcePosition *Position `json:"sourcePosition"`
|
||||
// Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between the `mousedown` and `mouseup`
|
||||
// of the drag. When set to 1, emits a single `mousemove` event at the destination location.
|
||||
Steps *int `json:"steps"`
|
||||
// When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
|
||||
// element, the call throws an exception.
|
||||
Strict *bool `json:"strict"`
|
||||
@ -2258,9 +2295,6 @@ type KeyboardTypeOptions struct {
|
||||
}
|
||||
|
||||
type LocatorAriaSnapshotOptions struct {
|
||||
// Generate symbolic reference for each element. One can use `aria-ref=<ref>` locator immediately after capturing the
|
||||
// snapshot to perform actions on the element.
|
||||
Ref *bool `json:"ref"`
|
||||
// Maximum time in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can
|
||||
// be changed by using the [BrowserContext.SetDefaultTimeout] or [Page.SetDefaultTimeout] methods.
|
||||
Timeout *float64 `json:"timeout"`
|
||||
@ -2338,6 +2372,9 @@ type LocatorClickOptions struct {
|
||||
// A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of
|
||||
// the element.
|
||||
Position *Position `json:"position"`
|
||||
// Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between Playwright's current cursor
|
||||
// position and the provided destination. When set to 1, emits a single `mousemove` event at the destination location.
|
||||
Steps *int `json:"steps"`
|
||||
// Maximum time in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can
|
||||
// be changed by using the [BrowserContext.SetDefaultTimeout] or [Page.SetDefaultTimeout] methods.
|
||||
Timeout *float64 `json:"timeout"`
|
||||
@ -2370,6 +2407,9 @@ type LocatorDblclickOptions struct {
|
||||
// A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of
|
||||
// the element.
|
||||
Position *Position `json:"position"`
|
||||
// Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between Playwright's current cursor
|
||||
// position and the provided destination. When set to 1, emits a single `mousemove` event at the destination location.
|
||||
Steps *int `json:"steps"`
|
||||
// Maximum time in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can
|
||||
// be changed by using the [BrowserContext.SetDefaultTimeout] or [Page.SetDefaultTimeout] methods.
|
||||
Timeout *float64 `json:"timeout"`
|
||||
@ -2400,6 +2440,9 @@ type LocatorDragToOptions struct {
|
||||
// Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not
|
||||
// specified, some visible point of the element is used.
|
||||
SourcePosition *Position `json:"sourcePosition"`
|
||||
// Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between the `mousedown` and `mouseup`
|
||||
// of the drag. When set to 1, emits a single `mousemove` event at the destination location.
|
||||
Steps *int `json:"steps"`
|
||||
// Drops on the target element at this point relative to the top-left corner of the element's padding box. If not
|
||||
// specified, some visible point of the element is used.
|
||||
TargetPosition *Position `json:"targetPosition"`
|
||||
@ -3073,7 +3116,8 @@ type MouseDownOptions struct {
|
||||
}
|
||||
|
||||
type MouseMoveOptions struct {
|
||||
// Defaults to 1. Sends intermediate `mousemove` events.
|
||||
// Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between Playwright's current cursor
|
||||
// position and the provided destination. When set to 1, emits a single `mousemove` event at the destination location.
|
||||
Steps *int `json:"steps"`
|
||||
}
|
||||
|
||||
@ -3158,6 +3202,9 @@ type PageClickOptions struct {
|
||||
// A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of
|
||||
// the element.
|
||||
Position *Position `json:"position"`
|
||||
// Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between Playwright's current cursor
|
||||
// position and the provided destination. When set to 1, emits a single `mousemove` event at the destination location.
|
||||
Steps *int `json:"steps"`
|
||||
// When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
|
||||
// element, the call throws an exception.
|
||||
Strict *bool `json:"strict"`
|
||||
@ -3239,6 +3286,9 @@ type PageDragAndDropOptions struct {
|
||||
// Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not
|
||||
// specified, some visible point of the element is used.
|
||||
SourcePosition *Position `json:"sourcePosition"`
|
||||
// Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between the `mousedown` and `mouseup`
|
||||
// of the drag. When set to 1, emits a single `mousemove` event at the destination location.
|
||||
Steps *int `json:"steps"`
|
||||
// When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
|
||||
// element, the call throws an exception.
|
||||
Strict *bool `json:"strict"`
|
||||
@ -4320,6 +4370,18 @@ type Proxy struct {
|
||||
Password *string `json:"password"`
|
||||
}
|
||||
|
||||
type StorageStateCookie struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Domain string `json:"domain"`
|
||||
Path string `json:"path"`
|
||||
// Unix time in seconds.
|
||||
Expires float64 `json:"expires"`
|
||||
HttpOnly bool `json:"httpOnly"`
|
||||
Secure bool `json:"secure"`
|
||||
SameSite *SameSiteAttribute `json:"sameSite"`
|
||||
}
|
||||
|
||||
type Origin struct {
|
||||
Origin string `json:"origin"`
|
||||
LocalStorage []NameValue `json:"localStorage"`
|
||||
@ -4336,7 +4398,7 @@ type RecordVideo struct {
|
||||
|
||||
type OptionalStorageState struct {
|
||||
// Cookies to set for context
|
||||
Cookies []OptionalCookie `json:"cookies"`
|
||||
Cookies []OptionalStorageStateOptionalCookie `json:"cookies"`
|
||||
// localStorage to set for context
|
||||
Origins []Origin `json:"origins"`
|
||||
}
|
||||
@ -4362,3 +4424,23 @@ type TracingGroupOptionsLocation struct {
|
||||
Line *int `json:"line"`
|
||||
Column *int `json:"column"`
|
||||
}
|
||||
|
||||
type OptionalStorageStateOptionalCookie struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
// Either url or domain / path are required. Optional.
|
||||
URL *string `json:"url"`
|
||||
// For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com". Either url
|
||||
// or domain / path are required. Optional.
|
||||
Domain *string `json:"domain"`
|
||||
// Either url or domain / path are required Optional.
|
||||
Path *string `json:"path"`
|
||||
// Unix time in seconds. Optional.
|
||||
Expires *float64 `json:"expires"`
|
||||
// Optional.
|
||||
HttpOnly *bool `json:"httpOnly"`
|
||||
// Optional.
|
||||
Secure *bool `json:"secure"`
|
||||
// Optional.
|
||||
SameSite *SameSiteAttribute `json:"sameSite"`
|
||||
}
|
||||
|
||||
26
helpers.go
26
helpers.go
@ -61,15 +61,21 @@ func transformStructIntoMapIfNeeded(inStruct interface{}) map[string]interface{}
|
||||
// Merge into the base map by the JSON struct tag
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
fi := typ.Field(i)
|
||||
tagv := fi.Tag.Get("json")
|
||||
key := strings.Split(tagv, ",")[0]
|
||||
if key == "" {
|
||||
key = fi.Name
|
||||
}
|
||||
// Special handling for timeout field: provide default value when nil
|
||||
// This is required in Playwright v1.57+ protocol where timeout is no longer optional
|
||||
if key == "timeout" && skipFieldSerialization(v.Field(i)) {
|
||||
out[key] = float64(30000) // default 30s
|
||||
continue
|
||||
}
|
||||
// Skip the values when the field is a pointer (like *string) and nil.
|
||||
if fi.IsExported() && !skipFieldSerialization(v.Field(i)) {
|
||||
// We use the JSON struct fields for getting the original names
|
||||
// out of the field.
|
||||
tagv := fi.Tag.Get("json")
|
||||
key := strings.Split(tagv, ",")[0]
|
||||
if key == "" {
|
||||
key = fi.Name
|
||||
}
|
||||
out[key] = transformStructValues(v.Field(i).Interface())
|
||||
}
|
||||
}
|
||||
@ -110,6 +116,16 @@ func transformOptions(options ...interface{}) map[string]interface{} {
|
||||
v := reflect.ValueOf(option)
|
||||
if v.Kind() == reflect.Slice {
|
||||
if v.Len() == 0 {
|
||||
// Check if the slice element type has a Timeout field and add default if so
|
||||
// This is required in Playwright v1.57+ protocol where timeout is no longer optional
|
||||
elemType := v.Type().Elem()
|
||||
if elemType.Kind() == reflect.Struct {
|
||||
if _, hasTimeout := elemType.FieldByName("Timeout"); hasTimeout {
|
||||
if base["timeout"] == nil {
|
||||
base["timeout"] = float64(30000) // default 30s
|
||||
}
|
||||
}
|
||||
}
|
||||
return base
|
||||
}
|
||||
option = v.Index(0).Interface()
|
||||
|
||||
@ -115,7 +115,11 @@ func convertInputFiles(files interface{}, context *browserContextImpl) (*inputFi
|
||||
}
|
||||
|
||||
if result["rootDir"] != nil {
|
||||
converted.DirectoryStream = result["rootDir"].(*channel)
|
||||
if ch, ok := result["rootDir"].(*channel); ok {
|
||||
converted.DirectoryStream = ch
|
||||
} else {
|
||||
converted.DirectoryStream = fromChannel(result["rootDir"]).(*writableStream).channel
|
||||
}
|
||||
} else {
|
||||
converted.Streams = streams
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ func (j *jsonPipe) Poll() (*message, error) {
|
||||
|
||||
func newJsonPipe(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *jsonPipe {
|
||||
j := &jsonPipe{
|
||||
msgChan: make(chan *message, 2),
|
||||
msgChan: make(chan *message, 10),
|
||||
}
|
||||
j.createChannelOwner(j, parent, objectType, guid, initializer)
|
||||
j.channel.On("message", func(ev map[string]interface{}) {
|
||||
@ -54,6 +54,12 @@ func newJsonPipe(parent *channelOwner, objectType string, guid string, initializ
|
||||
},
|
||||
}
|
||||
}
|
||||
// Send directly to maintain message ordering - the channel buffer prevents blocking
|
||||
// Previously used a goroutine which could cause out-of-order delivery
|
||||
defer func() {
|
||||
// Recover from panic if channel is closed
|
||||
_ = recover()
|
||||
}()
|
||||
j.msgChan <- &msg
|
||||
})
|
||||
j.channel.Once("closed", func() {
|
||||
|
||||
34
locator.go
34
locator.go
@ -12,10 +12,11 @@ var (
|
||||
)
|
||||
|
||||
type locatorImpl struct {
|
||||
frame *frameImpl
|
||||
selector string
|
||||
options *LocatorOptions
|
||||
err error
|
||||
frame *frameImpl
|
||||
selector string
|
||||
options *LocatorOptions
|
||||
err error
|
||||
description *string
|
||||
}
|
||||
|
||||
type LocatorOptions LocatorFilterOptions
|
||||
@ -65,6 +66,23 @@ func (l *locatorImpl) Err() error {
|
||||
return l.err
|
||||
}
|
||||
|
||||
func (l *locatorImpl) Describe(description string) Locator {
|
||||
return &locatorImpl{
|
||||
frame: l.frame,
|
||||
selector: l.selector,
|
||||
options: l.options,
|
||||
err: l.err,
|
||||
description: &description,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *locatorImpl) Description() (string, error) {
|
||||
if l.description == nil {
|
||||
return "", nil
|
||||
}
|
||||
return *l.description, nil
|
||||
}
|
||||
|
||||
func (l *locatorImpl) All() ([]Locator, error) {
|
||||
result := make([]Locator, 0)
|
||||
count, err := l.Count()
|
||||
@ -125,10 +143,10 @@ func (l *locatorImpl) Blur(options ...LocatorBlurOptions) error {
|
||||
"selector": l.selector,
|
||||
"strict": true,
|
||||
}
|
||||
if len(options) == 1 {
|
||||
if options[0].Timeout != nil {
|
||||
params["timeout"] = options[0].Timeout
|
||||
}
|
||||
if len(options) == 1 && options[0].Timeout != nil {
|
||||
params["timeout"] = options[0].Timeout
|
||||
} else {
|
||||
params["timeout"] = float64(30000) // default 30s, required in Playwright v1.57+
|
||||
}
|
||||
_, err := l.frame.channel.Send("blur", params)
|
||||
return err
|
||||
|
||||
@ -1,13 +1,24 @@
|
||||
package playwright
|
||||
|
||||
// dummyObject is a placeholder for unimplemented protocol objects (Android, Electron, etc.)
|
||||
type dummyObject struct {
|
||||
channelOwner
|
||||
}
|
||||
|
||||
func newDummyObject(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *dummyObject {
|
||||
d := &dummyObject{}
|
||||
d.createChannelOwner(d, parent, objectType, guid, initializer)
|
||||
return d
|
||||
}
|
||||
|
||||
func createObjectFactory(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) interface{} {
|
||||
switch objectType {
|
||||
case "Android":
|
||||
return nil
|
||||
return newDummyObject(parent, objectType, guid, initializer)
|
||||
case "AndroidSocket":
|
||||
return nil
|
||||
return newDummyObject(parent, objectType, guid, initializer)
|
||||
case "AndroidDevice":
|
||||
return nil
|
||||
return newDummyObject(parent, objectType, guid, initializer)
|
||||
case "APIRequestContext":
|
||||
return newAPIRequestContext(parent, objectType, guid, initializer)
|
||||
case "Artifact":
|
||||
@ -25,9 +36,9 @@ func createObjectFactory(parent *channelOwner, objectType string, guid string, i
|
||||
case "Dialog":
|
||||
return newDialog(parent, objectType, guid, initializer)
|
||||
case "Electron":
|
||||
return nil
|
||||
return newDummyObject(parent, objectType, guid, initializer)
|
||||
case "ElectronApplication":
|
||||
return nil
|
||||
return newDummyObject(parent, objectType, guid, initializer)
|
||||
case "ElementHandle":
|
||||
return newElementHandle(parent, objectType, guid, initializer)
|
||||
case "Frame":
|
||||
|
||||
33
page.go
33
page.go
@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/playwright-community/playwright-go/internal/safe"
|
||||
)
|
||||
@ -30,7 +31,7 @@ type pageImpl struct {
|
||||
ownedContext BrowserContext
|
||||
bindings *safe.SyncMap[string, BindingCallFunction]
|
||||
closeReason *string
|
||||
closeWasCalled bool
|
||||
closeWasCalled atomic.Bool
|
||||
harRouters []*harRouter
|
||||
locatorHandlers map[float64]*locatorHandlerEntry
|
||||
}
|
||||
@ -125,7 +126,7 @@ func (p *pageImpl) Close(options ...PageCloseOptions) error {
|
||||
if len(options) == 1 {
|
||||
p.closeReason = options[0].Reason
|
||||
}
|
||||
p.closeWasCalled = true
|
||||
p.closeWasCalled.Store(true)
|
||||
_, err := p.channel.Send("close", options)
|
||||
if err == nil && p.ownedContext != nil {
|
||||
err = p.ownedContext.Close()
|
||||
@ -746,6 +747,32 @@ func (p *pageImpl) Keyboard() Keyboard {
|
||||
return p.keyboard
|
||||
}
|
||||
|
||||
func (p *pageImpl) ConsoleMessages() ([]ConsoleMessage, error) {
|
||||
result, err := p.channel.Send("consoleMessages", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messages := result.([]interface{})
|
||||
consoleMessages := make([]ConsoleMessage, len(messages))
|
||||
for i, m := range messages {
|
||||
consoleMessages[i] = newConsoleMessage(m.(map[string]interface{}))
|
||||
}
|
||||
return consoleMessages, nil
|
||||
}
|
||||
|
||||
func (p *pageImpl) Requests() ([]Request, error) {
|
||||
result, err := p.channel.Send("requests", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requests := result.([]interface{})
|
||||
reqs := make([]Request, len(requests))
|
||||
for i, r := range requests {
|
||||
reqs[i] = fromChannel(r).(*requestImpl)
|
||||
}
|
||||
return reqs, nil
|
||||
}
|
||||
|
||||
func (p *pageImpl) Mouse() Mouse {
|
||||
return p.mouse
|
||||
}
|
||||
@ -942,7 +969,7 @@ func (p *pageImpl) onRoute(route *routeImpl) {
|
||||
url := route.Request().URL()
|
||||
for _, handlerEntry := range routes {
|
||||
// If the page was closed we stall all requests right away.
|
||||
if p.closeWasCalled || p.browserContext.closeWasCalled {
|
||||
if p.closeWasCalled.Load() || p.browserContext.closeWasCalled.Load() {
|
||||
return
|
||||
}
|
||||
if !handlerEntry.Matches(url) {
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package playwright
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type pageAssertionsImpl struct {
|
||||
@ -21,6 +23,65 @@ func newPageAssertions(page Page, isNot bool, defaultTimeout *float64) *pageAsse
|
||||
}
|
||||
}
|
||||
|
||||
// expectOnFrame calls the frame's expect method directly without a selector.
|
||||
// This is needed for page-level assertions like ToHaveTitle and ToHaveURL
|
||||
// which should not be bound to a specific element.
|
||||
func (pa *pageAssertionsImpl) expectOnFrame(
|
||||
expression string,
|
||||
options frameExpectOptions,
|
||||
expected interface{},
|
||||
message string,
|
||||
) error {
|
||||
options.IsNot = pa.isNot
|
||||
if options.Timeout == nil {
|
||||
options.Timeout = pa.defaultTimeout
|
||||
}
|
||||
if options.IsNot {
|
||||
message = strings.ReplaceAll(message, "expected to", "expected not to")
|
||||
}
|
||||
|
||||
frame := pa.actualPage.MainFrame().(*frameImpl)
|
||||
overrides := map[string]interface{}{
|
||||
"expression": expression,
|
||||
}
|
||||
result, err := frame.channel.SendReturnAsDict("expect", options, overrides)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
received interface{}
|
||||
matches bool
|
||||
log []string
|
||||
)
|
||||
|
||||
if v, ok := result["received"]; ok {
|
||||
received = parseResult(v)
|
||||
}
|
||||
if v, ok := result["matches"]; ok {
|
||||
matches = v.(bool)
|
||||
}
|
||||
if v, ok := result["log"]; ok {
|
||||
for _, l := range v.([]interface{}) {
|
||||
log = append(log, l.(string))
|
||||
}
|
||||
}
|
||||
|
||||
if matches == pa.isNot {
|
||||
actual := received
|
||||
logStr := strings.Join(log, "\n")
|
||||
if logStr != "" {
|
||||
logStr = "\nCall log:\n" + logStr
|
||||
}
|
||||
if expected != nil {
|
||||
return fmt.Errorf("%s '%v'\nActual value: %v %s", message, expected, actual, logStr)
|
||||
}
|
||||
return fmt.Errorf("%s\nActual value: %v %s", message, actual, logStr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pa *pageAssertionsImpl) ToHaveTitle(titleOrRegExp interface{}, options ...PageAssertionsToHaveTitleOptions) error {
|
||||
var timeout *float64
|
||||
if len(options) == 1 {
|
||||
@ -30,7 +91,7 @@ func (pa *pageAssertionsImpl) ToHaveTitle(titleOrRegExp interface{}, options ...
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pa.expect(
|
||||
return pa.expectOnFrame(
|
||||
"to.have.title",
|
||||
frameExpectOptions{ExpectedText: expectedValues, Timeout: timeout},
|
||||
titleOrRegExp,
|
||||
@ -57,7 +118,7 @@ func (pa *pageAssertionsImpl) ToHaveURL(urlOrRegExp interface{}, options ...Page
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pa.expect(
|
||||
return pa.expectOnFrame(
|
||||
"to.have.url",
|
||||
frameExpectOptions{ExpectedText: expectedValues, Timeout: timeout},
|
||||
urlOrRegExp,
|
||||
|
||||
@ -330,10 +330,10 @@ index 7867ce5c8..6a27ede39 100644
|
||||
|
||||
:::note
|
||||
diff --git a/docs/src/api/class-browsercontext.md b/docs/src/api/class-browsercontext.md
|
||||
index afd73a414..bf3f5ee0a 100644
|
||||
index 3e7529c48..aef4351b8 100644
|
||||
--- a/docs/src/api/class-browsercontext.md
|
||||
+++ b/docs/src/api/class-browsercontext.md
|
||||
@@ -417,7 +417,7 @@ The order of evaluation of multiple scripts installed via [`method: BrowserConte
|
||||
@@ -389,7 +389,7 @@ The order of evaluation of multiple scripts installed via [`method: BrowserConte
|
||||
|
||||
### param: BrowserContext.addInitScript.script
|
||||
* since: v1.8
|
||||
@ -342,7 +342,7 @@ index afd73a414..bf3f5ee0a 100644
|
||||
- `script` <[function]|[string]|[Object]>
|
||||
- `path` ?<[path]> Path to the JavaScript file. If `path` is a relative path, then it is resolved relative to the
|
||||
current working directory. Optional.
|
||||
@@ -1214,7 +1214,7 @@ handler function to route the request.
|
||||
@@ -1186,7 +1186,7 @@ handler function to route the request.
|
||||
|
||||
### param: BrowserContext.route.handler
|
||||
* since: v1.8
|
||||
@ -351,7 +351,7 @@ index afd73a414..bf3f5ee0a 100644
|
||||
- `handler` <[function]\([Route]\)>
|
||||
|
||||
handler function to route the request.
|
||||
@@ -1357,7 +1357,7 @@ Handler function to route the WebSocket.
|
||||
@@ -1329,7 +1329,7 @@ Handler function to route the WebSocket.
|
||||
|
||||
### param: BrowserContext.routeWebSocket.handler
|
||||
* since: v1.48
|
||||
@ -360,7 +360,7 @@ index afd73a414..bf3f5ee0a 100644
|
||||
- `handler` <[function]\([WebSocketRoute]\)>
|
||||
|
||||
Handler function to route the WebSocket.
|
||||
@@ -1365,7 +1365,7 @@ Handler function to route the WebSocket.
|
||||
@@ -1337,7 +1337,7 @@ Handler function to route the WebSocket.
|
||||
|
||||
## method: BrowserContext.serviceWorkers
|
||||
* since: v1.11
|
||||
@ -369,7 +369,7 @@ index afd73a414..bf3f5ee0a 100644
|
||||
- returns: <[Array]<[Worker]>>
|
||||
|
||||
:::note
|
||||
@@ -1522,6 +1522,7 @@ Returns storage state for this browser context, contains current cookies, local
|
||||
@@ -1495,6 +1495,7 @@ Returns storage state for this browser context, contains current cookies, local
|
||||
|
||||
### option: BrowserContext.storageState.indexedDB
|
||||
* since: v1.51
|
||||
@ -377,7 +377,7 @@ index afd73a414..bf3f5ee0a 100644
|
||||
- `indexedDB` ?<boolean>
|
||||
|
||||
Set to `true` to include [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) in the storage state snapshot.
|
||||
@@ -1559,6 +1560,13 @@ A glob pattern, regex pattern or predicate receiving [URL] used to register a ro
|
||||
@@ -1532,6 +1533,13 @@ A glob pattern, regex pattern or predicate receiving [URL] used to register a ro
|
||||
|
||||
Optional handler function used to register a routing with [`method: BrowserContext.route`].
|
||||
|
||||
@ -391,7 +391,7 @@ index afd73a414..bf3f5ee0a 100644
|
||||
### param: BrowserContext.unroute.handler
|
||||
* since: v1.8
|
||||
* langs: csharp, java
|
||||
@@ -1600,7 +1608,8 @@ Condition to wait for.
|
||||
@@ -1573,7 +1581,8 @@ Condition to wait for.
|
||||
|
||||
## async method: BrowserContext.waitForConsoleMessage
|
||||
* since: v1.34
|
||||
@ -401,7 +401,7 @@ index afd73a414..bf3f5ee0a 100644
|
||||
- alias-python: expect_console_message
|
||||
- alias-csharp: RunAndWaitForConsoleMessage
|
||||
- returns: <[ConsoleMessage]>
|
||||
@@ -1631,7 +1640,8 @@ Receives the [ConsoleMessage] object and resolves to truthy value when the waiti
|
||||
@@ -1604,7 +1613,8 @@ Receives the [ConsoleMessage] object and resolves to truthy value when the waiti
|
||||
|
||||
## async method: BrowserContext.waitForEvent
|
||||
* since: v1.8
|
||||
@ -411,7 +411,7 @@ index afd73a414..bf3f5ee0a 100644
|
||||
- alias-python: expect_event
|
||||
- returns: <[any]>
|
||||
|
||||
@@ -1697,7 +1707,8 @@ Either a predicate that receives an event or an options object. Optional.
|
||||
@@ -1670,7 +1680,8 @@ Either a predicate that receives an event or an options object. Optional.
|
||||
|
||||
## async method: BrowserContext.waitForPage
|
||||
* since: v1.9
|
||||
@ -421,7 +421,7 @@ index afd73a414..bf3f5ee0a 100644
|
||||
- alias-python: expect_page
|
||||
- alias-csharp: RunAndWaitForPage
|
||||
- returns: <[Page]>
|
||||
@@ -1716,7 +1727,7 @@ Will throw an error if the context closes before new [Page] is created.
|
||||
@@ -1689,7 +1700,7 @@ Will throw an error if the context closes before new [Page] is created.
|
||||
|
||||
### option: BrowserContext.waitForPage.predicate
|
||||
* since: v1.9
|
||||
@ -430,7 +430,7 @@ index afd73a414..bf3f5ee0a 100644
|
||||
- `predicate` <[function]\([Page]\):[boolean]>
|
||||
|
||||
Receives the [Page] object and resolves to truthy value when the waiting should resolve.
|
||||
@@ -1729,7 +1740,8 @@ Receives the [Page] object and resolves to truthy value when the waiting should
|
||||
@@ -1702,7 +1713,8 @@ Receives the [Page] object and resolves to truthy value when the waiting should
|
||||
|
||||
## async method: BrowserContext.waitForEvent2
|
||||
* since: v1.8
|
||||
@ -494,7 +494,7 @@ index e5610b424..748b93b68 100644
|
||||
- `time` <[float]|[string]|[Date]>
|
||||
|
||||
diff --git a/docs/src/api/class-consolemessage.md b/docs/src/api/class-consolemessage.md
|
||||
index 347838ae4..e461d6f9f 100644
|
||||
index b712bca26..4a164b95e 100644
|
||||
--- a/docs/src/api/class-consolemessage.md
|
||||
+++ b/docs/src/api/class-consolemessage.md
|
||||
@@ -112,7 +112,7 @@ List of arguments passed to a `console` function call. See also [`event: Page.co
|
||||
@ -519,12 +519,31 @@ index 347838ae4..e461d6f9f 100644
|
||||
+
|
||||
## method: ConsoleMessage.type
|
||||
* since: v1.8
|
||||
* langs: js, python
|
||||
@@ -144,7 +151,7 @@ The text of the console message.
|
||||
|
||||
## method: ConsoleMessage.type
|
||||
* since: v1.8
|
||||
-* langs: csharp, java
|
||||
+* langs: csharp, java, go
|
||||
- returns: <[string]>
|
||||
|
||||
One of the following values: `'log'`, `'debug'`, `'info'`, `'error'`, `'warning'`, `'dir'`, `'dirxml'`, `'table'`,
|
||||
diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md
|
||||
index e286c63bf..7dbd5f4fd 100644
|
||||
index f9c366e42..678bf3e65 100644
|
||||
--- a/docs/src/api/class-frame.md
|
||||
+++ b/docs/src/api/class-frame.md
|
||||
@@ -1946,7 +1946,7 @@ await page.MainFrame.WaitForFunctionAsync("selector => !!document.querySelector(
|
||||
@@ -283,6 +283,9 @@ When all steps combined have not finished during the specified [`option: timeout
|
||||
### option: Frame.click.trial = %%-input-trial-with-modifiers-%%
|
||||
* since: v1.11
|
||||
|
||||
+### option: Frame.click.steps = %%-input-mousemove-steps-%%
|
||||
+* since: v1.57
|
||||
+
|
||||
## async method: Frame.content
|
||||
* since: v1.8
|
||||
- returns: <[string]>
|
||||
@@ -1949,7 +1952,7 @@ await page.MainFrame.WaitForFunctionAsync("selector => !!document.querySelector(
|
||||
|
||||
Optional argument to pass to [`param: expression`].
|
||||
|
||||
@ -533,7 +552,7 @@ index e286c63bf..7dbd5f4fd 100644
|
||||
* since: v1.8
|
||||
|
||||
### option: Frame.waitForFunction.polling = %%-csharp-java-wait-for-function-polling-%%
|
||||
@@ -1998,6 +1998,11 @@ await frame.WaitForLoadStateAsync(); // Defaults to LoadState.Load
|
||||
@@ -2001,6 +2004,11 @@ await frame.WaitForLoadStateAsync(); // Defaults to LoadState.Load
|
||||
```
|
||||
|
||||
### param: Frame.waitForLoadState.state = %%-wait-for-load-state-state-%%
|
||||
@ -545,7 +564,7 @@ index e286c63bf..7dbd5f4fd 100644
|
||||
* since: v1.8
|
||||
|
||||
### option: Frame.waitForLoadState.timeout = %%-navigation-timeout-%%
|
||||
@@ -2010,6 +2015,7 @@ await frame.WaitForLoadStateAsync(); // Defaults to LoadState.Load
|
||||
@@ -2013,6 +2021,7 @@ await frame.WaitForLoadStateAsync(); // Defaults to LoadState.Load
|
||||
* since: v1.8
|
||||
* deprecated: This method is inherently racy, please use [`method: Frame.waitForURL`] instead.
|
||||
* langs:
|
||||
@ -554,10 +573,10 @@ index e286c63bf..7dbd5f4fd 100644
|
||||
* alias-csharp: RunAndWaitForNavigation
|
||||
- returns: <[null]|[Response]>
|
||||
diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md
|
||||
index 25462a0ed..f85e4aff6 100644
|
||||
index 042c08bb9..a1925c73d 100644
|
||||
--- a/docs/src/api/class-locator.md
|
||||
+++ b/docs/src/api/class-locator.md
|
||||
@@ -885,7 +885,7 @@ Optional argument to pass to [`param: expression`].
|
||||
@@ -1006,7 +1006,7 @@ Optional argument to pass to [`param: expression`].
|
||||
|
||||
### option: Locator.evaluate.timeout
|
||||
* since: v1.14
|
||||
@ -566,7 +585,7 @@ index 25462a0ed..f85e4aff6 100644
|
||||
- `timeout` <[float]>
|
||||
|
||||
Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation itself is not limited by the timeout. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
|
||||
@@ -982,7 +982,7 @@ Optional argument to pass to [`param: expression`].
|
||||
@@ -1103,7 +1103,7 @@ Optional argument to pass to [`param: expression`].
|
||||
|
||||
### option: Locator.evaluateHandle.timeout
|
||||
* since: v1.14
|
||||
@ -576,7 +595,7 @@ index 25462a0ed..f85e4aff6 100644
|
||||
|
||||
Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation itself is not limited by the timeout. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
|
||||
diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md
|
||||
index 78ed036c0..e78149d3d 100644
|
||||
index c2edbf753..2f0a348a0 100644
|
||||
--- a/docs/src/api/class-locatorassertions.md
|
||||
+++ b/docs/src/api/class-locatorassertions.md
|
||||
@@ -67,7 +67,7 @@ public class ExampleTests : PageTest
|
||||
@ -625,10 +644,10 @@ index 78ed036c0..e78149d3d 100644
|
||||
|
||||
Expected options currently selected.
|
||||
diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md
|
||||
index a69ac2446..a026da311 100644
|
||||
index 717cbf06c..a54492b22 100644
|
||||
--- a/docs/src/api/class-page.md
|
||||
+++ b/docs/src/api/class-page.md
|
||||
@@ -621,7 +621,7 @@ The order of evaluation of multiple scripts installed via [`method: BrowserConte
|
||||
@@ -614,7 +614,7 @@ The order of evaluation of multiple scripts installed via [`method: BrowserConte
|
||||
|
||||
### param: Page.addInitScript.script
|
||||
* since: v1.8
|
||||
@ -637,7 +656,17 @@ index a69ac2446..a026da311 100644
|
||||
- `script` <[function]|[string]|[Object]>
|
||||
- `path` ?<[path]> Path to the JavaScript file. If `path` is a relative path, then it is resolved relative to the
|
||||
current working directory. Optional.
|
||||
@@ -1267,6 +1267,14 @@ Passing `null` disables CSS media emulation.
|
||||
@@ -808,6 +808,9 @@ When all steps combined have not finished during the specified [`option: timeout
|
||||
### option: Page.click.trial = %%-input-trial-with-modifiers-%%
|
||||
* since: v1.11
|
||||
|
||||
+### option: Page.click.steps = %%-input-mousemove-steps-%%
|
||||
+* since: v1.57
|
||||
+
|
||||
## async method: Page.close
|
||||
* since: v1.8
|
||||
|
||||
@@ -1263,6 +1266,14 @@ Passing `null` disables CSS media emulation.
|
||||
Changes the CSS media type of the page. The only allowed values are `'Screen'`, `'Print'` and `'Null'`.
|
||||
Passing `'Null'` disables CSS media emulation.
|
||||
|
||||
@ -652,7 +681,7 @@ index a69ac2446..a026da311 100644
|
||||
### option: Page.emulateMedia.colorScheme
|
||||
* since: v1.9
|
||||
* langs: js, java
|
||||
@@ -1283,6 +1291,14 @@ Emulates [prefers-colors-scheme](https://developer.mozilla.org/en-US/docs/Web/CS
|
||||
@@ -1279,6 +1290,14 @@ Emulates [prefers-colors-scheme](https://developer.mozilla.org/en-US/docs/Web/CS
|
||||
Emulates [prefers-colors-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) media feature, supported values are `'light'` and `'dark'`. Passing
|
||||
`'Null'` disables color scheme emulation. `'no-preference'` is deprecated.
|
||||
|
||||
@ -667,7 +696,7 @@ index a69ac2446..a026da311 100644
|
||||
### option: Page.emulateMedia.reducedMotion
|
||||
* since: v1.12
|
||||
* langs: js, java
|
||||
@@ -1297,6 +1313,13 @@ Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce
|
||||
@@ -1293,6 +1312,13 @@ Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce
|
||||
|
||||
Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce'`, `'no-preference'`. Passing `null` disables reduced motion emulation.
|
||||
|
||||
@ -681,7 +710,7 @@ index a69ac2446..a026da311 100644
|
||||
### option: Page.emulateMedia.forcedColors
|
||||
* since: v1.15
|
||||
* langs: js, java
|
||||
@@ -1309,6 +1332,13 @@ Emulates `'forced-colors'` media feature, supported values are `'active'` and `'
|
||||
@@ -1305,6 +1331,13 @@ Emulates `'forced-colors'` media feature, supported values are `'active'` and `'
|
||||
* langs: csharp, python
|
||||
- `forcedColors` <[ForcedColors]<"active"|"none"|"null">>
|
||||
|
||||
@ -695,7 +724,7 @@ index a69ac2446..a026da311 100644
|
||||
### option: Page.emulateMedia.contrast
|
||||
* since: v1.51
|
||||
* langs: js, java
|
||||
@@ -1321,6 +1351,13 @@ Emulates `'prefers-contrast'` media feature, supported values are `'no-preferenc
|
||||
@@ -1317,6 +1350,13 @@ Emulates `'prefers-contrast'` media feature, supported values are `'no-preferenc
|
||||
* langs: csharp, python
|
||||
- `contrast` <[Contrast]<"no-preference"|"more"|"null">>
|
||||
|
||||
@ -709,7 +738,7 @@ index a69ac2446..a026da311 100644
|
||||
## async method: Page.evalOnSelector
|
||||
* since: v1.9
|
||||
* discouraged: This method does not wait for the element to pass actionability
|
||||
@@ -2140,14 +2177,14 @@ Frame name specified in the `iframe`'s `name` attribute.
|
||||
@@ -2136,14 +2176,14 @@ Frame name specified in the `iframe`'s `name` attribute.
|
||||
|
||||
### option: Page.frame.name
|
||||
* since: v1.8
|
||||
@ -726,7 +755,7 @@ index a69ac2446..a026da311 100644
|
||||
- `url` ?<[string]|[RegExp]|[function]\([URL]\):[boolean]>
|
||||
|
||||
A glob pattern, regex pattern or predicate receiving frame's `url` as a [URL] object. Optional.
|
||||
@@ -2917,7 +2954,7 @@ Paper width, accepts values labeled with units.
|
||||
@@ -2936,7 +2976,7 @@ Paper width, accepts values labeled with units.
|
||||
|
||||
### option: Page.pdf.width
|
||||
* since: v1.8
|
||||
@ -735,7 +764,7 @@ index a69ac2446..a026da311 100644
|
||||
- `width` <[string]>
|
||||
|
||||
Paper width, accepts values labeled with units.
|
||||
@@ -2931,7 +2968,7 @@ Paper height, accepts values labeled with units.
|
||||
@@ -2950,7 +2990,7 @@ Paper height, accepts values labeled with units.
|
||||
|
||||
### option: Page.pdf.height
|
||||
* since: v1.8
|
||||
@ -744,7 +773,7 @@ index a69ac2446..a026da311 100644
|
||||
- `height` <[string]>
|
||||
|
||||
Paper height, accepts values labeled with units.
|
||||
@@ -2949,7 +2986,7 @@ Paper margins, defaults to none.
|
||||
@@ -2968,7 +3008,7 @@ Paper margins, defaults to none.
|
||||
|
||||
### option: Page.pdf.margin
|
||||
* since: v1.8
|
||||
@ -753,7 +782,7 @@ index a69ac2446..a026da311 100644
|
||||
- `margin` <[Object]>
|
||||
- `top` ?<[string]> Top margin, accepts values labeled with units. Defaults to `0`.
|
||||
- `right` ?<[string]> Right margin, accepts values labeled with units. Defaults to `0`.
|
||||
@@ -3370,7 +3407,7 @@ Function that should be run once [`param: locator`] appears. This function shoul
|
||||
@@ -3400,7 +3440,7 @@ Function that should be run once [`param: locator`] appears. This function shoul
|
||||
Function that should be run once [`param: locator`] appears. This function should get rid of the element that blocks actions like click.
|
||||
|
||||
### param: Page.addLocatorHandler.handler
|
||||
@ -762,7 +791,7 @@ index a69ac2446..a026da311 100644
|
||||
* since: v1.42
|
||||
- `handler` <[function]\([Locator]\)>
|
||||
|
||||
@@ -3616,6 +3653,13 @@ A glob pattern, regex pattern, or predicate that receives a [URL] to match durin
|
||||
@@ -3646,6 +3686,13 @@ A glob pattern, regex pattern, or predicate that receives a [URL] to match durin
|
||||
|
||||
handler function to route the request.
|
||||
|
||||
@ -776,7 +805,7 @@ index a69ac2446..a026da311 100644
|
||||
### param: Page.route.handler
|
||||
* since: v1.8
|
||||
* langs: csharp, java
|
||||
@@ -3750,7 +3794,7 @@ Handler function to route the WebSocket.
|
||||
@@ -3780,7 +3827,7 @@ Handler function to route the WebSocket.
|
||||
|
||||
### param: Page.routeWebSocket.handler
|
||||
* since: v1.48
|
||||
@ -785,7 +814,7 @@ index a69ac2446..a026da311 100644
|
||||
- `handler` <[function]\([WebSocketRoute]\)>
|
||||
|
||||
Handler function to route the WebSocket.
|
||||
@@ -4079,14 +4123,14 @@ await page.GotoAsync("https://www.microsoft.com");
|
||||
@@ -4109,14 +4156,14 @@ await page.GotoAsync("https://www.microsoft.com");
|
||||
|
||||
### param: Page.setViewportSize.width
|
||||
* since: v1.10
|
||||
@ -802,7 +831,7 @@ index a69ac2446..a026da311 100644
|
||||
- `height` <[int]>
|
||||
|
||||
Page height in pixels.
|
||||
@@ -4273,6 +4317,13 @@ A glob pattern, regex pattern or predicate receiving [URL] to match while routin
|
||||
@@ -4303,6 +4350,13 @@ A glob pattern, regex pattern or predicate receiving [URL] to match while routin
|
||||
|
||||
Optional handler function to route the request.
|
||||
|
||||
@ -816,7 +845,7 @@ index a69ac2446..a026da311 100644
|
||||
### param: Page.unroute.handler
|
||||
* since: v1.8
|
||||
* langs: csharp, java
|
||||
@@ -4311,7 +4362,8 @@ Performs action and waits for the Page to close.
|
||||
@@ -4341,7 +4395,8 @@ Performs action and waits for the Page to close.
|
||||
|
||||
## async method: Page.waitForConsoleMessage
|
||||
* since: v1.9
|
||||
@ -826,7 +855,7 @@ index a69ac2446..a026da311 100644
|
||||
- alias-python: expect_console_message
|
||||
- alias-csharp: RunAndWaitForConsoleMessage
|
||||
- returns: <[ConsoleMessage]>
|
||||
@@ -4342,7 +4394,8 @@ Receives the [ConsoleMessage] object and resolves to truthy value when the waiti
|
||||
@@ -4372,7 +4427,8 @@ Receives the [ConsoleMessage] object and resolves to truthy value when the waiti
|
||||
|
||||
## async method: Page.waitForDownload
|
||||
* since: v1.9
|
||||
@ -836,7 +865,7 @@ index a69ac2446..a026da311 100644
|
||||
- alias-python: expect_download
|
||||
- alias-csharp: RunAndWaitForDownload
|
||||
- returns: <[Download]>
|
||||
@@ -4373,7 +4426,8 @@ Receives the [Download] object and resolves to truthy value when the waiting sho
|
||||
@@ -4403,7 +4459,8 @@ Receives the [Download] object and resolves to truthy value when the waiting sho
|
||||
|
||||
## async method: Page.waitForEvent
|
||||
* since: v1.8
|
||||
@ -846,7 +875,7 @@ index a69ac2446..a026da311 100644
|
||||
- alias-python: expect_event
|
||||
- returns: <[any]>
|
||||
|
||||
@@ -4426,7 +4480,8 @@ Either a predicate that receives an event or an options object. Optional.
|
||||
@@ -4456,7 +4513,8 @@ Either a predicate that receives an event or an options object. Optional.
|
||||
|
||||
## async method: Page.waitForFileChooser
|
||||
* since: v1.9
|
||||
@ -856,7 +885,7 @@ index a69ac2446..a026da311 100644
|
||||
- alias-python: expect_file_chooser
|
||||
- alias-csharp: RunAndWaitForFileChooser
|
||||
- returns: <[FileChooser]>
|
||||
@@ -4584,7 +4639,7 @@ await page.WaitForFunctionAsync("selector => !!document.querySelector(selector)"
|
||||
@@ -4614,7 +4672,7 @@ await page.WaitForFunctionAsync("selector => !!document.querySelector(selector)"
|
||||
|
||||
Optional argument to pass to [`param: expression`].
|
||||
|
||||
@ -865,7 +894,7 @@ index a69ac2446..a026da311 100644
|
||||
* since: v1.8
|
||||
|
||||
### option: Page.waitForFunction.polling = %%-csharp-java-wait-for-function-polling-%%
|
||||
@@ -4681,6 +4736,11 @@ Console.WriteLine(await popup.TitleAsync()); // popup is ready to use.
|
||||
@@ -4711,6 +4769,11 @@ Console.WriteLine(await popup.TitleAsync()); // popup is ready to use.
|
||||
```
|
||||
|
||||
### param: Page.waitForLoadState.state = %%-wait-for-load-state-state-%%
|
||||
@ -877,7 +906,7 @@ index a69ac2446..a026da311 100644
|
||||
* since: v1.8
|
||||
|
||||
### option: Page.waitForLoadState.timeout = %%-navigation-timeout-%%
|
||||
@@ -4693,6 +4753,7 @@ Console.WriteLine(await popup.TitleAsync()); // popup is ready to use.
|
||||
@@ -4723,6 +4786,7 @@ Console.WriteLine(await popup.TitleAsync()); // popup is ready to use.
|
||||
* since: v1.8
|
||||
* deprecated: This method is inherently racy, please use [`method: Page.waitForURL`] instead.
|
||||
* langs:
|
||||
@ -885,7 +914,7 @@ index a69ac2446..a026da311 100644
|
||||
* alias-python: expect_navigation
|
||||
* alias-csharp: RunAndWaitForNavigation
|
||||
- returns: <[null]|[Response]>
|
||||
@@ -4777,7 +4838,8 @@ a navigation.
|
||||
@@ -4807,7 +4871,8 @@ a navigation.
|
||||
|
||||
## async method: Page.waitForPopup
|
||||
* since: v1.9
|
||||
@ -895,7 +924,7 @@ index a69ac2446..a026da311 100644
|
||||
- alias-python: expect_popup
|
||||
- alias-csharp: RunAndWaitForPopup
|
||||
- returns: <[Page]>
|
||||
@@ -4809,6 +4871,7 @@ Receives the [Page] object and resolves to truthy value when the waiting should
|
||||
@@ -4839,6 +4904,7 @@ Receives the [Page] object and resolves to truthy value when the waiting should
|
||||
## async method: Page.waitForRequest
|
||||
* since: v1.8
|
||||
* langs:
|
||||
@ -903,7 +932,7 @@ index a69ac2446..a026da311 100644
|
||||
* alias-python: expect_request
|
||||
* alias-csharp: RunAndWaitForRequest
|
||||
- returns: <[Request]>
|
||||
@@ -4916,7 +4979,8 @@ changed by using the [`method: Page.setDefaultTimeout`] method.
|
||||
@@ -4946,7 +5012,8 @@ changed by using the [`method: Page.setDefaultTimeout`] method.
|
||||
|
||||
## async method: Page.waitForRequestFinished
|
||||
* since: v1.12
|
||||
@ -913,7 +942,7 @@ index a69ac2446..a026da311 100644
|
||||
- alias-python: expect_request_finished
|
||||
- alias-csharp: RunAndWaitForRequestFinished
|
||||
- returns: <[Request]>
|
||||
@@ -4948,6 +5012,7 @@ Receives the [Request] object and resolves to truthy value when the waiting shou
|
||||
@@ -4978,6 +5045,7 @@ Receives the [Request] object and resolves to truthy value when the waiting shou
|
||||
## async method: Page.waitForResponse
|
||||
* since: v1.8
|
||||
* langs:
|
||||
@ -921,7 +950,7 @@ index a69ac2446..a026da311 100644
|
||||
* alias-python: expect_response
|
||||
* alias-csharp: RunAndWaitForResponse
|
||||
- returns: <[Response]>
|
||||
@@ -5311,7 +5376,8 @@ await page.WaitForURLAsync("**/target.html");
|
||||
@@ -5341,7 +5409,8 @@ await page.WaitForURLAsync("**/target.html");
|
||||
|
||||
## async method: Page.waitForWebSocket
|
||||
* since: v1.9
|
||||
@ -931,7 +960,7 @@ index a69ac2446..a026da311 100644
|
||||
- alias-python: expect_websocket
|
||||
- alias-csharp: RunAndWaitForWebSocket
|
||||
- returns: <[WebSocket]>
|
||||
@@ -5342,7 +5408,8 @@ Receives the [WebSocket] object and resolves to truthy value when the waiting sh
|
||||
@@ -5372,7 +5441,8 @@ Receives the [WebSocket] object and resolves to truthy value when the waiting sh
|
||||
|
||||
## async method: Page.waitForWorker
|
||||
* since: v1.9
|
||||
@ -941,7 +970,7 @@ index a69ac2446..a026da311 100644
|
||||
- alias-python: expect_worker
|
||||
- alias-csharp: RunAndWaitForWorker
|
||||
- returns: <[Worker]>
|
||||
@@ -5384,7 +5451,8 @@ This does not contain ServiceWorkers
|
||||
@@ -5414,7 +5484,8 @@ This does not contain ServiceWorkers
|
||||
|
||||
## async method: Page.waitForEvent2
|
||||
* since: v1.8
|
||||
@ -1126,10 +1155,10 @@ index 4c3011430..4ced2d60d 100644
|
||||
- `path` ?<[path]> Path to the JavaScript file. If `path` is a relative path, then it is resolved relative to the
|
||||
current working directory. Optional.
|
||||
diff --git a/docs/src/api/class-tracing.md b/docs/src/api/class-tracing.md
|
||||
index 3b0011c7b..48a28fa24 100644
|
||||
index 0528891fd..1323d2393 100644
|
||||
--- a/docs/src/api/class-tracing.md
|
||||
+++ b/docs/src/api/class-tracing.md
|
||||
@@ -142,7 +142,7 @@ If this option is true tracing will
|
||||
@@ -156,7 +156,7 @@ If this option is true tracing will
|
||||
|
||||
### option: Tracing.start.sources
|
||||
* since: v1.17
|
||||
@ -1191,7 +1220,7 @@ index e33740eac..d89648d91 100644
|
||||
- returns: <[any]>
|
||||
|
||||
diff --git a/docs/src/api/class-websocketroute.md b/docs/src/api/class-websocketroute.md
|
||||
index e23316ebc..ae3d20b9f 100644
|
||||
index fcbe1b21f..dc6503e4f 100644
|
||||
--- a/docs/src/api/class-websocketroute.md
|
||||
+++ b/docs/src/api/class-websocketroute.md
|
||||
@@ -325,7 +325,7 @@ Function that will handle WebSocket closure. Received an optional [close code](h
|
||||
@ -1218,7 +1247,7 @@ index e23316ebc..ae3d20b9f 100644
|
||||
* since: v1.48
|
||||
* langs: csharp, java
|
||||
diff --git a/docs/src/api/params.md b/docs/src/api/params.md
|
||||
index 4c4937004..ea6212dc9 100644
|
||||
index 37f6665a9..dbe37d8a1 100644
|
||||
--- a/docs/src/api/params.md
|
||||
+++ b/docs/src/api/params.md
|
||||
@@ -8,7 +8,7 @@ When to consider operation succeeded, defaults to `load`. Events can be either:
|
||||
@ -1248,7 +1277,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
- `timeout` <[float]>
|
||||
|
||||
Maximum time in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by
|
||||
@@ -188,8 +188,8 @@ Defaults to `'visible'`. Can be either:
|
||||
@@ -198,8 +198,8 @@ Defaults to `'visible'`. Can be either:
|
||||
* `'hidden'` - wait for element to be either detached from DOM, or have an empty bounding box or `visibility:hidden`.
|
||||
This is opposite to the `'visible'` option.
|
||||
|
||||
@ -1259,7 +1288,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
- `polling` <[float]|"raf">
|
||||
|
||||
If [`option: polling`] is `'raf'`, then [`param: expression`] is constantly executed in `requestAnimationFrame`
|
||||
@@ -210,14 +210,14 @@ If `true`, Playwright does not pass its own configurations args and only uses th
|
||||
@@ -220,14 +220,14 @@ If `true`, Playwright does not pass its own configurations args and only uses th
|
||||
array is given, then filters out the given default arguments. Dangerous option; use with care. Defaults to `false`.
|
||||
|
||||
## csharp-java-browser-option-ignoredefaultargs
|
||||
@ -1276,8 +1305,8 @@ index 4c4937004..ea6212dc9 100644
|
||||
- `ignoreAllDefaultArgs` <[boolean]>
|
||||
|
||||
If `true`, Playwright does not pass its own configurations args and only uses the ones from [`option: args`].
|
||||
@@ -236,7 +236,7 @@ Dangerous option; use with care. Defaults to `false`.
|
||||
Network proxy settings.
|
||||
@@ -250,7 +250,7 @@ Network proxy settings.
|
||||
- `env` <[Object]<[string], [string]|[undefined]>>
|
||||
|
||||
## csharp-java-browser-option-env
|
||||
-* langs: csharp, java
|
||||
@ -1285,7 +1314,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
- `env` <[Object]<[string], [string]>>
|
||||
|
||||
Specify environment variables that will be visible to the browser. Defaults to `process.env`.
|
||||
@@ -269,6 +269,29 @@ Learn more about [storage state and auth](../auth.md).
|
||||
@@ -283,6 +283,29 @@ Learn more about [storage state and auth](../auth.md).
|
||||
|
||||
Populates context with given storage state. This option can be used to initialize context with logged-in information obtained via [`method: BrowserContext.storageState`].
|
||||
|
||||
@ -1315,7 +1344,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
## csharp-java-context-option-storage-state
|
||||
* langs: csharp, java
|
||||
- `storageState` <[string]>
|
||||
@@ -277,7 +300,7 @@ Populates context with given storage state. This option can be used to initializ
|
||||
@@ -291,7 +314,7 @@ Populates context with given storage state. This option can be used to initializ
|
||||
obtained via [`method: BrowserContext.storageState`].
|
||||
|
||||
## csharp-java-context-option-storage-state-path
|
||||
@ -1324,7 +1353,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
- `storageStatePath` <[path]>
|
||||
|
||||
Populates context with given storage state. This option can be used to initialize context with logged-in information
|
||||
@@ -374,7 +397,7 @@ Query parameters to be sent with the URL.
|
||||
@@ -388,7 +411,7 @@ Query parameters to be sent with the URL.
|
||||
Query parameters to be sent with the URL.
|
||||
|
||||
## csharp-fetch-option-params
|
||||
@ -1333,7 +1362,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
- `params` <[Object]<[string], [Serializable]>>
|
||||
|
||||
Query parameters to be sent with the URL.
|
||||
@@ -392,19 +415,19 @@ Query parameters to be sent with the URL.
|
||||
@@ -406,19 +429,19 @@ Query parameters to be sent with the URL.
|
||||
Optional request parameters.
|
||||
|
||||
## js-python-csharp-fetch-option-headers
|
||||
@ -1356,7 +1385,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
- `failOnStatusCode` <[boolean]>
|
||||
|
||||
Whether to throw on response codes other than 2xx and 3xx. By default response object is returned
|
||||
@@ -436,6 +459,14 @@ unless explicitly provided.
|
||||
@@ -450,6 +473,14 @@ unless explicitly provided.
|
||||
|
||||
An instance of [FormData] can be created via [`method: APIRequestContext.createFormData`].
|
||||
|
||||
@ -1371,7 +1400,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
## js-fetch-option-multipart
|
||||
* langs: js
|
||||
- `multipart` <[FormData]|[Object]<[string], [string]|[float]|[boolean]|[ReadStream]|[Object]>>
|
||||
@@ -469,6 +500,15 @@ unless explicitly provided. File values can be passed as file-like object contai
|
||||
@@ -483,6 +514,15 @@ unless explicitly provided. File values can be passed as file-like object contai
|
||||
|
||||
An instance of [FormData] can be created via [`method: APIRequestContext.createFormData`].
|
||||
|
||||
@ -1387,7 +1416,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
## js-python-csharp-fetch-option-data
|
||||
* langs: js, python, csharp
|
||||
- `data` <[string]|[Buffer]|[Serializable]>
|
||||
@@ -477,21 +517,29 @@ Allows to set post data of the request. If the data parameter is an object, it w
|
||||
@@ -491,21 +531,29 @@ Allows to set post data of the request. If the data parameter is an object, it w
|
||||
and `content-type` header will be set to `application/json` if not explicitly set. Otherwise the `content-type` header will be
|
||||
set to `application/octet-stream` if not explicitly set.
|
||||
|
||||
@ -1420,7 +1449,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
- `maxRetries` <[int]>
|
||||
|
||||
Maximum number of times network errors should be retried. Currently only `ECONNRESET` error is retried. Does not retry based on HTTP response codes. An error will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||
@@ -533,7 +581,7 @@ Function to be evaluated in the worker context.
|
||||
@@ -547,7 +595,7 @@ Function to be evaluated in the worker context.
|
||||
Function to be evaluated in the main Electron process.
|
||||
|
||||
## python-context-option-viewport
|
||||
@ -1429,7 +1458,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
- `viewport` <[null]|[Object]>
|
||||
- `width` <[int]> page width in pixels.
|
||||
- `height` <[int]> page height in pixels.
|
||||
@@ -541,7 +589,7 @@ Function to be evaluated in the main Electron process.
|
||||
@@ -555,7 +603,7 @@ Function to be evaluated in the main Electron process.
|
||||
Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `no_viewport` disables the fixed viewport. Learn more about [viewport emulation](../emulation.md#viewport).
|
||||
|
||||
## python-context-option-no-viewport
|
||||
@ -1438,7 +1467,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
- `noViewport` <[boolean]>
|
||||
|
||||
Does not enforce fixed viewport, allows resizing window in the headed mode.
|
||||
@@ -649,6 +697,13 @@ Emulates [prefers-colors-scheme](https://developer.mozilla.org/en-US/docs/Web/CS
|
||||
@@ -665,6 +713,13 @@ Emulates [prefers-colors-scheme](https://developer.mozilla.org/en-US/docs/Web/CS
|
||||
Emulates [prefers-colors-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) media feature, supported values are `'light'` and `'dark'`. See
|
||||
[`method: Page.emulateMedia`] for more details. Passing `'null'` resets emulation to system defaults. Defaults to `'light'`.
|
||||
|
||||
@ -1452,7 +1481,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
## context-option-reducedMotion
|
||||
* langs: js, java
|
||||
- `reducedMotion` <null|[ReducedMotion]<"reduce"|"no-preference">>
|
||||
@@ -661,6 +716,12 @@ Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce
|
||||
@@ -677,6 +732,12 @@ Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce
|
||||
|
||||
Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce'`, `'no-preference'`. See [`method: Page.emulateMedia`] for more details. Passing `'null'` resets emulation to system defaults. Defaults to `'no-preference'`.
|
||||
|
||||
@ -1465,7 +1494,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
## context-option-forcedColors
|
||||
* langs: js, java
|
||||
- `forcedColors` <null|[ForcedColors]<"active"|"none">>
|
||||
@@ -668,10 +729,10 @@ Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce
|
||||
@@ -684,10 +745,10 @@ Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce
|
||||
Emulates `'forced-colors'` media feature, supported values are `'active'`, `'none'`. See [`method: Page.emulateMedia`] for more details. Passing `null` resets emulation to system defaults. Defaults to `'none'`.
|
||||
|
||||
## context-option-forcedColors-csharp-python
|
||||
@ -1479,7 +1508,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
|
||||
## context-option-contrast
|
||||
* langs: js, java
|
||||
@@ -718,7 +779,7 @@ specified, the HAR is not recorded. Make sure to await [`method: BrowserContext.
|
||||
@@ -735,7 +796,7 @@ specified, the HAR is not recorded. Make sure to await [`method: BrowserContext.
|
||||
saved.
|
||||
|
||||
## context-option-recordhar-path
|
||||
@ -1488,7 +1517,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
- alias-python: record_har_path
|
||||
- `recordHarPath` <[path]>
|
||||
|
||||
@@ -727,33 +788,33 @@ specified HAR file on the filesystem. If not specified, the HAR is not recorded.
|
||||
@@ -744,33 +805,33 @@ specified HAR file on the filesystem. If not specified, the HAR is not recorded.
|
||||
call [`method: BrowserContext.close`] for the HAR to be saved.
|
||||
|
||||
## context-option-recordhar-omit-content
|
||||
@ -1527,7 +1556,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
- `recordVideo` <[Object]>
|
||||
- `dir` <[path]> Path to the directory to put videos into.
|
||||
- `size` ?<[Object]> Optional dimensions of the recorded videos. If not specified the size will be equal to `viewport`
|
||||
@@ -820,7 +881,7 @@ Specifies whether to wait for already running listeners and what to do if they t
|
||||
@@ -837,7 +898,7 @@ Specifies whether to wait for already running listeners and what to do if they t
|
||||
* `'ignoreErrors'` - do not wait for current listener calls (if any) to finish, all errors thrown by the listeners after removal are silently caught
|
||||
|
||||
## unroute-all-options-behavior
|
||||
@ -1536,7 +1565,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
* since: v1.41
|
||||
- `behavior` <[UnrouteBehavior]<"wait"|"ignoreErrors"|"default">>
|
||||
|
||||
@@ -831,7 +892,7 @@ Specifies whether to wait for already running handlers and what to do if they th
|
||||
@@ -848,7 +909,7 @@ Specifies whether to wait for already running handlers and what to do if they th
|
||||
|
||||
|
||||
## select-options-values
|
||||
@ -1545,7 +1574,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
- `values` <[null]|[string]|[ElementHandle]|[Array]<[string]>|[Object]|[Array]<[ElementHandle]>|[Array]<[Object]>>
|
||||
- `value` ?<[string]> Matches by `option.value`. Optional.
|
||||
- `label` ?<[string]> Matches by `option.label`. Optional.
|
||||
@@ -849,7 +910,7 @@ the parameter is a string without wildcard characters, the method will wait for
|
||||
@@ -866,7 +927,7 @@ the parameter is a string without wildcard characters, the method will wait for
|
||||
equal to the string.
|
||||
|
||||
## wait-for-event-event
|
||||
@ -1554,7 +1583,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
- `event` <[string]>
|
||||
|
||||
Event name, same one typically passed into `*.on(event)`.
|
||||
@@ -907,7 +968,7 @@ only the first option matching one of the passed options is selected. Optional.
|
||||
@@ -924,7 +985,7 @@ only the first option matching one of the passed options is selected. Optional.
|
||||
Receives the event data and resolves to truthy value when the waiting should resolve.
|
||||
|
||||
## wait-for-event-timeout
|
||||
@ -1563,7 +1592,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
- `timeout` <[float]>
|
||||
|
||||
Maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
|
||||
@@ -927,7 +988,7 @@ using the [`method: AndroidDevice.setDefaultTimeout`] method.
|
||||
@@ -944,7 +1005,7 @@ using the [`method: AndroidDevice.setDefaultTimeout`] method.
|
||||
Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`.
|
||||
|
||||
## csharp-java-python-assertions-timeout
|
||||
@ -1572,7 +1601,7 @@ index 4c4937004..ea6212dc9 100644
|
||||
- `timeout` <[float]>
|
||||
|
||||
Time to retry the assertion for in milliseconds. Defaults to `5000`.
|
||||
@@ -981,8 +1042,10 @@ between the same pixel in compared images, between zero (strict) and one (lax),
|
||||
@@ -998,8 +1059,10 @@ between the same pixel in compared images, between zero (strict) and one (lax),
|
||||
- %%-context-option-httpcredentials-%%
|
||||
- %%-context-option-colorscheme-%%
|
||||
- %%-context-option-colorscheme-csharp-python-%%
|
||||
@ -1583,8 +1612,8 @@ index 4c4937004..ea6212dc9 100644
|
||||
- %%-context-option-forcedColors-%%
|
||||
- %%-context-option-forcedColors-csharp-python-%%
|
||||
- %%-context-option-contrast-%%
|
||||
@@ -1072,7 +1135,7 @@ Firefox user preferences. Learn more about the Firefox user preferences at
|
||||
[`about:config`](https://support.mozilla.org/en-US/kb/about-config-editor-firefox).
|
||||
@@ -1091,7 +1154,7 @@ Firefox user preferences. Learn more about the Firefox user preferences at
|
||||
You can also provide a path to a custom [`policies.json` file](https://mozilla.github.io/policy-templates/) via `PLAYWRIGHT_FIREFOX_POLICIES_JSON` environment variable.
|
||||
|
||||
## csharp-java-browser-option-firefoxuserprefs
|
||||
-* langs: csharp, java
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 471930b1ceae03c9e66e0eb80c1364a1a788e7db
|
||||
Subproject commit 80581972582c9565e141c5fedd3c5fa10cc0e38b
|
||||
@ -30,11 +30,26 @@ func (p *Playwright) Stop() error {
|
||||
return p.connection.Stop()
|
||||
}
|
||||
|
||||
// Pid returns the process ID of the Playwright driver process, or 0 if not available
|
||||
func (p *Playwright) Pid() int {
|
||||
if pt, ok := p.connection.transport.(*pipeTransport); ok {
|
||||
if pt.process != nil {
|
||||
return pt.process.Pid
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p *Playwright) setSelectors(selectors Selectors) {
|
||||
selectorsOwner := fromChannel(p.initializer["selectors"]).(*selectorsOwnerImpl)
|
||||
p.Selectors.(*selectorsImpl).removeChannel(selectorsOwner)
|
||||
p.Selectors = selectors
|
||||
p.Selectors.(*selectorsImpl).addChannel(selectorsOwner)
|
||||
// Selectors has been moved to client-side only in Playwright v1.57+
|
||||
if p.initializer["selectors"] != nil {
|
||||
selectorsOwner := fromChannel(p.initializer["selectors"]).(*selectorsOwnerImpl)
|
||||
p.Selectors.(*selectorsImpl).removeChannel(selectorsOwner)
|
||||
p.Selectors = selectors
|
||||
p.Selectors.(*selectorsImpl).addChannel(selectorsOwner)
|
||||
} else {
|
||||
p.Selectors = selectors
|
||||
}
|
||||
}
|
||||
|
||||
func newPlaywright(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *Playwright {
|
||||
@ -50,10 +65,14 @@ func newPlaywright(parent *channelOwner, objectType string, guid string, initial
|
||||
pw.Chromium.(*browserTypeImpl).playwright = pw
|
||||
pw.Firefox.(*browserTypeImpl).playwright = pw
|
||||
pw.WebKit.(*browserTypeImpl).playwright = pw
|
||||
selectorsOwner := fromChannel(initializer["selectors"]).(*selectorsOwnerImpl)
|
||||
pw.Selectors.(*selectorsImpl).addChannel(selectorsOwner)
|
||||
pw.connection.afterClose = func() {
|
||||
pw.Selectors.(*selectorsImpl).removeChannel(selectorsOwner)
|
||||
// Selectors has been moved to client-side only in Playwright v1.57+
|
||||
// Only set up channel if selectors is in the initializer (older protocol)
|
||||
if initializer["selectors"] != nil {
|
||||
selectorsOwner := fromChannel(initializer["selectors"]).(*selectorsOwnerImpl)
|
||||
pw.Selectors.(*selectorsImpl).addChannel(selectorsOwner)
|
||||
pw.connection.afterClose = func() {
|
||||
pw.Selectors.(*selectorsImpl).removeChannel(selectorsOwner)
|
||||
}
|
||||
}
|
||||
if pw.connection.localUtils != nil {
|
||||
pw.Devices = pw.connection.localUtils.Devices
|
||||
|
||||
2
run.go
2
run.go
@ -16,7 +16,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const playwrightCliVersion = "1.52.0"
|
||||
const playwrightCliVersion = "1.57.0"
|
||||
|
||||
var (
|
||||
logger = slog.Default()
|
||||
|
||||
27
run_test.go
27
run_test.go
@ -11,7 +11,6 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/go-ps"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -150,7 +149,12 @@ func TestShouldNotHangWhenPlaywrightUnexpectedExit(t *testing.T) {
|
||||
context, err := browser.NewContext()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = killPlaywrightProcess()
|
||||
// Get the process ID directly from Playwright
|
||||
pid := pw.Pid()
|
||||
require.NotZero(t, pid, "Playwright process PID should not be zero")
|
||||
|
||||
// Kill the process
|
||||
err = killProcessByPid(pid)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = context.NewPage()
|
||||
@ -172,25 +176,6 @@ func TestGetNodeExecutable(t *testing.T) {
|
||||
assert.Contains(t, executable, "testDirectory")
|
||||
}
|
||||
|
||||
// find and kill playwright process
|
||||
func killPlaywrightProcess() error {
|
||||
all, err := ps.Processes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, process := range all {
|
||||
if process.Executable() == "node" || process.Executable() == "node.exe" {
|
||||
if process.PPid() == os.Getpid() {
|
||||
if err := killProcessByPid(process.Pid()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("playwright process not found")
|
||||
}
|
||||
|
||||
func killProcessByPid(pid int) error {
|
||||
process, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
|
||||
71
selectors.go
71
selectors.go
@ -23,7 +23,8 @@ func newSelectorsOwner(parent *channelOwner, objectType string, guid string, ini
|
||||
}
|
||||
|
||||
type selectorsImpl struct {
|
||||
channels sync.Map
|
||||
mu sync.RWMutex // protects registrations slice
|
||||
contexts sync.Map // map of BrowserContext channels
|
||||
registrations []map[string]interface{}
|
||||
}
|
||||
|
||||
@ -41,48 +42,82 @@ func (s *selectorsImpl) Register(name string, script Script, options ...Selector
|
||||
} else {
|
||||
source = *script.Content
|
||||
}
|
||||
params := map[string]interface{}{
|
||||
selectorEngine := map[string]interface{}{
|
||||
"name": name,
|
||||
"source": source,
|
||||
}
|
||||
if len(options) == 1 && options[0].ContentScript != nil {
|
||||
params["contentScript"] = *options[0].ContentScript
|
||||
selectorEngine["contentScript"] = *options[0].ContentScript
|
||||
}
|
||||
var err error
|
||||
s.channels.Range(func(key, value any) bool {
|
||||
_, err = value.(*selectorsOwnerImpl).channel.Send("register", params)
|
||||
return err == nil
|
||||
params := map[string]interface{}{
|
||||
"selectorEngine": selectorEngine,
|
||||
}
|
||||
// Register with all active contexts, ignoring contexts that have been closed
|
||||
s.contexts.Range(func(key, value any) bool {
|
||||
_, _ = value.(*browserContextImpl).channel.Send("registerSelectorEngine", params)
|
||||
// Continue to next context even if this one failed (e.g., context closed)
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.registrations = append(s.registrations, params)
|
||||
s.mu.Lock()
|
||||
s.registrations = append(s.registrations, selectorEngine)
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *selectorsImpl) SetTestIdAttribute(name string) {
|
||||
setTestIdAttributeName(name)
|
||||
s.channels.Range(func(key, value any) bool {
|
||||
value.(*selectorsOwnerImpl).setTestIdAttributeName(name)
|
||||
s.contexts.Range(func(key, value any) bool {
|
||||
value.(*browserContextImpl).channel.SendNoReply("setTestIdAttributeName", map[string]interface{}{
|
||||
"testIdAttributeName": name,
|
||||
})
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (s *selectorsImpl) addChannel(channel *selectorsOwnerImpl) {
|
||||
s.channels.Store(channel.guid, channel)
|
||||
for _, params := range s.registrations {
|
||||
channel.channel.SendNoReply("register", params)
|
||||
// Legacy support for older Playwright versions with server-side selectors
|
||||
s.contexts.Store(channel.guid, channel)
|
||||
s.mu.RLock()
|
||||
for _, selectorEngine := range s.registrations {
|
||||
params := map[string]interface{}{
|
||||
"selectorEngine": selectorEngine,
|
||||
}
|
||||
channel.channel.SendNoReply("registerSelectorEngine", params)
|
||||
channel.setTestIdAttributeName(getTestIdAttributeName())
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
}
|
||||
|
||||
func (s *selectorsImpl) removeChannel(channel *selectorsOwnerImpl) {
|
||||
s.channels.Delete(channel.guid)
|
||||
// Legacy support for older Playwright versions with server-side selectors
|
||||
s.contexts.Delete(channel.guid)
|
||||
}
|
||||
|
||||
func (s *selectorsImpl) addContext(context *browserContextImpl) {
|
||||
s.contexts.Store(context.guid, context)
|
||||
s.mu.RLock()
|
||||
for _, selectorEngine := range s.registrations {
|
||||
params := map[string]interface{}{
|
||||
"selectorEngine": selectorEngine,
|
||||
}
|
||||
context.channel.SendNoReply("registerSelectorEngine", params)
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
testIdAttr := getTestIdAttributeName()
|
||||
if testIdAttr != "" {
|
||||
context.channel.SendNoReply("setTestIdAttributeName", map[string]interface{}{
|
||||
"testIdAttributeName": testIdAttr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *selectorsImpl) removeContext(context *browserContextImpl) {
|
||||
s.contexts.Delete(context.guid)
|
||||
}
|
||||
|
||||
func newSelectorsImpl() *selectorsImpl {
|
||||
return &selectorsImpl{
|
||||
channels: sync.Map{},
|
||||
contexts: sync.Map{},
|
||||
registrations: make([]map[string]interface{}, 0),
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,15 +179,15 @@ func TestBrowserContextAddCookies(t *testing.T) {
|
||||
|
||||
require.Equal(t, []playwright.Cookie{
|
||||
{
|
||||
Name: "password",
|
||||
Value: "123456",
|
||||
Domain: "127.0.0.1",
|
||||
Path: "/",
|
||||
Expires: -1,
|
||||
|
||||
HttpOnly: false,
|
||||
Secure: false,
|
||||
SameSite: sameSite,
|
||||
Name: "password",
|
||||
Value: "123456",
|
||||
Domain: "127.0.0.1",
|
||||
Path: "/",
|
||||
Expires: -1,
|
||||
HttpOnly: false,
|
||||
Secure: false,
|
||||
SameSite: sameSite,
|
||||
PartitionKey: playwright.String(""),
|
||||
},
|
||||
}, cookies)
|
||||
|
||||
@ -281,58 +281,8 @@ func TestBrowserContextUnrouteShouldWork(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBrowserContextShouldReturnBackgroundPage(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
if !isChromium {
|
||||
t.Skip()
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("flaky on windows")
|
||||
}
|
||||
extensionPath := Asset("simple-extension")
|
||||
context, err := browserType.LaunchPersistentContext(
|
||||
t.TempDir(),
|
||||
playwright.BrowserTypeLaunchPersistentContextOptions{
|
||||
Headless: playwright.Bool(false),
|
||||
Args: []string{
|
||||
fmt.Sprintf("--disable-extensions-except=%s", extensionPath),
|
||||
fmt.Sprintf("--load-extension=%s", extensionPath),
|
||||
},
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
var page playwright.Page
|
||||
if len(context.BackgroundPages()) == 1 {
|
||||
page = context.BackgroundPages()[0]
|
||||
} else {
|
||||
ret, err := context.WaitForEvent("backgroundPage", playwright.BrowserContextWaitForEventOptions{
|
||||
Timeout: playwright.Float(1000),
|
||||
})
|
||||
if err != nil {
|
||||
// probably missing event
|
||||
if len(context.BackgroundPages()) == 1 {
|
||||
page = context.BackgroundPages()[0]
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
page = ret.(playwright.Page)
|
||||
}
|
||||
}
|
||||
require.NotNil(t, page)
|
||||
contains := func(pages []playwright.Page, page playwright.Page) bool {
|
||||
for _, p := range pages {
|
||||
if p == page {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
require.False(t, contains(context.Pages(), page))
|
||||
require.True(t, contains(context.BackgroundPages(), page))
|
||||
context.Close()
|
||||
require.Len(t, context.BackgroundPages(), 0)
|
||||
require.Len(t, context.Pages(), 0)
|
||||
// Background pages have been removed from Chromium together with Manifest V2 extensions
|
||||
t.Skip("Background pages are deprecated - Manifest V2 extensions no longer supported in Chromium")
|
||||
}
|
||||
|
||||
func TestPageEventShouldHaveURL(t *testing.T) {
|
||||
|
||||
@ -139,7 +139,13 @@ func TestConsoleShouldTriggerCorrectLog(t *testing.T) {
|
||||
_, err = page.Evaluate("url => fetch(url).catch(e => {})", server.EMPTY_PAGE)
|
||||
require.NoError(t, err)
|
||||
message := <-messages
|
||||
require.Contains(t, message.Text(), "Access-Control-Allow-Origin")
|
||||
|
||||
headerString := "Access-Control-Allow-Origin"
|
||||
corsString := "CORS"
|
||||
require.Condition(t, func() bool {
|
||||
return strings.Contains(message.Text(), headerString) || strings.Contains(message.Text(), corsString)
|
||||
}, "The text should contain either '%s' or '%s'", headerString, corsString)
|
||||
|
||||
require.Equal(t, "error", message.Type())
|
||||
}
|
||||
|
||||
|
||||
@ -128,7 +128,7 @@ func TestShoulSupportGlobalTimeoutOption(t *testing.T) {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
})
|
||||
_, err = request.Get(server.PREFIX + "/empty.html")
|
||||
require.Contains(t, err.Error(), `Request timed out after`)
|
||||
require.ErrorContains(t, err, `Timeout 100ms exceeded`)
|
||||
}
|
||||
|
||||
func TestShouldPropagateExtraHttpHeadersWithRedirects(t *testing.T) {
|
||||
@ -261,7 +261,7 @@ func TestStorageStateShouldRoundTripThroughFile(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
storageState := &playwright.StorageState{
|
||||
Cookies: []playwright.Cookie{
|
||||
Cookies: []playwright.StorageStateCookie{
|
||||
{
|
||||
Name: "a",
|
||||
Value: "b",
|
||||
|
||||
@ -721,3 +721,82 @@ func TestLocatorShouldSupportFilterVisible(t *testing.T) {
|
||||
Visible: playwright.Bool(false),
|
||||
}).GetByText("data1")).ToHaveText("Hidden data1"))
|
||||
}
|
||||
|
||||
// TestLocatorDescribe verifies that Locator.Describe() sets a description
|
||||
// Based on upstream test: playwright/tests/page/locator-convenience.spec.ts
|
||||
func TestLocatorDescribe(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
require.NoError(t, page.SetContent(`<button>Submit</button>`))
|
||||
|
||||
locator := page.Locator("button")
|
||||
|
||||
// Locator without description should return empty string
|
||||
desc, err := locator.Description()
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, desc, "description should be empty for locator without description")
|
||||
|
||||
// Set description
|
||||
describedLocator := locator.Describe("Submit button")
|
||||
desc, err = describedLocator.Description()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Submit button", desc)
|
||||
|
||||
// Original locator should still have no description
|
||||
desc, err = locator.Description()
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, desc, "original locator should remain unchanged")
|
||||
}
|
||||
|
||||
// TestLocatorDescribeSpecialCharacters verifies descriptions with special characters
|
||||
func TestLocatorDescribeSpecialCharacters(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
require.NoError(t, page.SetContent(`<div>Test</div>`))
|
||||
|
||||
locator := page.Locator("div").Describe(`Button with "quotes" and 'apostrophes'`)
|
||||
desc, err := locator.Description()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, `Button with "quotes" and 'apostrophes'`, desc)
|
||||
}
|
||||
|
||||
// TestLocatorDescribeChained verifies descriptions work with chained locators
|
||||
func TestLocatorDescribeChained(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
require.NoError(t, page.SetContent(`
|
||||
<form>
|
||||
<input type="text" />
|
||||
</form>
|
||||
`))
|
||||
|
||||
locator := page.Locator("form").Locator("input").Describe("Form input field")
|
||||
desc, err := locator.Description()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Form input field", desc)
|
||||
}
|
||||
|
||||
// TestLocatorDescribeMultipleCalls verifies multiple describe calls override each other
|
||||
func TestLocatorDescribeMultipleCalls(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
require.NoError(t, page.SetContent(`<button><span>Click me</span></button>`))
|
||||
|
||||
// First description
|
||||
locator1 := page.Locator("button").Describe("First description")
|
||||
desc, err := locator1.Description()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "First description", desc)
|
||||
|
||||
// Second description on chained locator
|
||||
locator2 := locator1.Locator("span").Describe("Second description")
|
||||
desc, err = locator2.Description()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Second description", desc)
|
||||
|
||||
// Chained locator without describe should have no description
|
||||
locator3 := locator2.Locator("span")
|
||||
desc, err = locator3.Description()
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, desc, "chained locator without describe should have empty description")
|
||||
}
|
||||
|
||||
@ -100,20 +100,7 @@ func TestShouldSnapshotComplex(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShouldSnapshotWithRef(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
require.NoError(t, page.SetContent(`<ul><li><a href="about:blank">link</a></li></ul>`))
|
||||
expected := Unshift(`
|
||||
- list [ref=s1e3]:
|
||||
- listitem [ref=s1e4]:
|
||||
- link "link" [ref=s1e5]:
|
||||
- /url: about:blank
|
||||
`)
|
||||
ariaSnapshot, err := page.Locator("body").AriaSnapshot(playwright.LocatorAriaSnapshotOptions{
|
||||
Ref: playwright.Bool(true),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, ariaSnapshot)
|
||||
t.Skip("the Ref option was removed in Playwright v1.53")
|
||||
}
|
||||
|
||||
func TestShouldSnapshotWithUnexpectedChildrenEqual(t *testing.T) {
|
||||
|
||||
@ -332,11 +332,16 @@ func TestPageClockStubTimers(t *testing.T) {
|
||||
|
||||
beforePageClock(t, 0, 1000)
|
||||
|
||||
// Set up a signal to ensure the async function has started
|
||||
_, err := page.Evaluate(`window.timeoutStarted = false`)
|
||||
require.NoError(t, err)
|
||||
|
||||
chanRet := make(chan interface{}, 1)
|
||||
go func() {
|
||||
ret, err := page.Evaluate(`
|
||||
async () => {
|
||||
const prev = performance.now();
|
||||
window.timeoutStarted = true;
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
const next = performance.now();
|
||||
return { prev, next };
|
||||
@ -347,6 +352,13 @@ func TestPageClockStubTimers(t *testing.T) {
|
||||
close(chanRet)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for the async function to start and set up the setTimeout
|
||||
require.Eventually(t, func() bool {
|
||||
started, _ := page.Evaluate(`window.timeoutStarted`)
|
||||
return started == true
|
||||
}, 5*time.Second, 50*time.Millisecond)
|
||||
|
||||
require.NoError(t, page.Clock().RunFor(1000))
|
||||
ret := <-chanRet
|
||||
require.Equal(t, map[string]interface{}{
|
||||
@ -372,11 +384,16 @@ func TestPageClockStubTimersPerformance(t *testing.T) {
|
||||
|
||||
beforePageClock(t, 1000, 2000)
|
||||
|
||||
// Set up a signal to ensure the async function has started
|
||||
_, err := page.Evaluate(`window.timeoutStarted = false`)
|
||||
require.NoError(t, err)
|
||||
|
||||
chanRet := make(chan interface{}, 1)
|
||||
go func() {
|
||||
ret, err := page.Evaluate(`
|
||||
async () => {
|
||||
const prev = performance.now();
|
||||
window.timeoutStarted = true;
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
const next = performance.now();
|
||||
return { prev, next };
|
||||
@ -387,6 +404,13 @@ func TestPageClockStubTimersPerformance(t *testing.T) {
|
||||
close(chanRet)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for the async function to start and set up the setTimeout
|
||||
require.Eventually(t, func() bool {
|
||||
started, _ := page.Evaluate(`window.timeoutStarted`)
|
||||
return started == true
|
||||
}, 5*time.Second, 50*time.Millisecond)
|
||||
|
||||
require.NoError(t, page.Clock().RunFor(1000))
|
||||
origin, err := page.Evaluate(`performance.timeOrigin`)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -44,14 +44,14 @@ func TestPageSetContent(t *testing.T) {
|
||||
func TestPageSetContentShouldRespectDefaultNavigationTimeout(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
page.SetDefaultNavigationTimeout(5)
|
||||
imgPath := "/img/png"
|
||||
page.SetDefaultNavigationTimeout(1)
|
||||
imgPath := "/img.png"
|
||||
// stall for image
|
||||
require.NoError(t, page.Route(imgPath, func(r playwright.Route) {}))
|
||||
server.SetRoute(imgPath, func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
err := page.SetContent(fmt.Sprintf(`<img src="%s"></img>`, server.PREFIX+imgPath))
|
||||
require.ErrorIs(t, err, playwright.ErrTimeout)
|
||||
require.ErrorContains(t, err, "Timeout 5ms exceeded.")
|
||||
require.ErrorContains(t, err, "Timeout 1ms exceeded.")
|
||||
}
|
||||
|
||||
func TestPageScreenshot(t *testing.T) {
|
||||
@ -1219,11 +1219,10 @@ func TestPageGotoShouldFailWhenExceedingBrowserContextNavigationTimeout(t *testi
|
||||
|
||||
// Hang for request to the empty.html
|
||||
server.SetRoute("/empty.html", func(w http.ResponseWriter, r *http.Request) {})
|
||||
context.SetDefaultNavigationTimeout(5)
|
||||
defer context.SetDefaultNavigationTimeout(30 * 1000) // reset
|
||||
context.SetDefaultNavigationTimeout(2)
|
||||
_, err := page.Goto(server.EMPTY_PAGE)
|
||||
require.ErrorIs(t, err, playwright.ErrTimeout)
|
||||
require.ErrorContains(t, err, "Timeout 5ms exceeded.")
|
||||
require.ErrorContains(t, err, "Timeout 2ms exceeded.")
|
||||
require.ErrorContains(t, err, "/empty.html")
|
||||
}
|
||||
|
||||
@ -1264,3 +1263,129 @@ func TestShouldEmulateContrast(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.True(t, ret.(bool))
|
||||
}
|
||||
|
||||
// TestPageConsoleMessages verifies that Page.ConsoleMessages() returns accumulated console messages
|
||||
// Based on upstream test: playwright/tests/page/page-event-console.spec.ts
|
||||
func TestPageConsoleMessages(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
// Generate 301 console messages (should keep last 100)
|
||||
_, err := page.Evaluate(`() => {
|
||||
for (let i = 0; i < 301; i++) {
|
||||
console.log('message' + i);
|
||||
}
|
||||
}`)
|
||||
require.NoError(t, err)
|
||||
|
||||
messages, err := page.ConsoleMessages()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should return at least 100 messages (the buffer limit)
|
||||
require.GreaterOrEqual(t, len(messages), 100, "should be at least 100 messages")
|
||||
|
||||
// Verify the last 100 messages are correct (message201 to message300)
|
||||
expectedStart := 301 - len(messages)
|
||||
for i, msg := range messages {
|
||||
expectedText := fmt.Sprintf("message%d", expectedStart+i)
|
||||
require.Equal(t, expectedText, msg.Text())
|
||||
require.Equal(t, "log", msg.Type())
|
||||
|
||||
// Note: Page() may be nil for console messages retrieved from the buffer
|
||||
// as they don't include full channel references like live events do
|
||||
}
|
||||
}
|
||||
|
||||
// TestPageConsoleMessagesEmpty verifies that ConsoleMessages() returns empty array for new page
|
||||
func TestPageConsoleMessagesEmpty(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
messages, err := page.ConsoleMessages()
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, messages)
|
||||
}
|
||||
|
||||
// TestPageConsoleMessagesTypes verifies different console message types are captured
|
||||
func TestPageConsoleMessagesTypes(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
_, err := page.Evaluate(`() => {
|
||||
console.log('log message');
|
||||
console.warn('warn message');
|
||||
console.error('error message');
|
||||
console.info('info message');
|
||||
}`)
|
||||
require.NoError(t, err)
|
||||
|
||||
messages, err := page.ConsoleMessages()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, messages, 4)
|
||||
|
||||
expectedTypes := []string{"log", "warning", "error", "info"}
|
||||
for i, msg := range messages {
|
||||
require.Equal(t, expectedTypes[i], msg.Type())
|
||||
}
|
||||
}
|
||||
|
||||
// TestPageRequests verifies that Page.Requests() returns accumulated requests
|
||||
// Based on upstream test: playwright/tests/page/page-event-request.spec.ts
|
||||
func TestPageRequests(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
// Navigate to a page (creates initial request)
|
||||
_, err := page.Goto(server.EMPTY_PAGE)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create multiple fetch requests
|
||||
for i := 0; i < 99; i++ {
|
||||
path := fmt.Sprintf("/fetch%d", i)
|
||||
server.SetRoute(path, func(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := w.Write([]byte("response"))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
// Make 99 fetch requests
|
||||
for i := 0; i < 99; i++ {
|
||||
url := fmt.Sprintf("%s/fetch%d", server.PREFIX, i)
|
||||
_, err := page.Evaluate(`url => fetch(url)`, url)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
requests, err := page.Requests()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should have at least 100 requests (navigation + 99 fetches, buffer limit is 100)
|
||||
require.GreaterOrEqual(t, len(requests), 99, "should capture fetch requests")
|
||||
|
||||
// Verify requests are functional
|
||||
for _, req := range requests {
|
||||
require.NotEmpty(t, req.URL())
|
||||
require.NotEmpty(t, req.Method())
|
||||
}
|
||||
}
|
||||
|
||||
// TestPageRequestsEmpty verifies that Requests() returns empty array for new page
|
||||
func TestPageRequestsEmpty(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
requests, err := page.Requests()
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, requests)
|
||||
}
|
||||
|
||||
// TestPageRequestsWithNavigation verifies navigation requests are included
|
||||
func TestPageRequestsWithNavigation(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
_, err := page.Goto(server.EMPTY_PAGE)
|
||||
require.NoError(t, err)
|
||||
|
||||
requests, err := page.Requests()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, requests, 1, "should capture navigation request")
|
||||
|
||||
req := requests[0]
|
||||
require.Equal(t, server.EMPTY_PAGE, req.URL())
|
||||
require.Equal(t, "GET", req.Method())
|
||||
require.Equal(t, "document", req.ResourceType())
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ func newRemoteServer() (*remoteServer, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not start Playwright: %v", err)
|
||||
}
|
||||
cmd := driver.Command("launch-server", "--browser", browserName)
|
||||
cmd := driver.Command("run-server")
|
||||
cmd.Stderr = os.Stderr
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
@ -31,11 +31,17 @@ func newRemoteServer() (*remoteServer, error) {
|
||||
return nil, fmt.Errorf("could not start server: %v", err)
|
||||
}
|
||||
scanner := bufio.NewReader(stdout)
|
||||
url, err := scanner.ReadString('\n')
|
||||
url = strings.TrimRight(url, "\n")
|
||||
line, err := scanner.ReadString('\n')
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read url: %v", err)
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
// Remove "Listening on " prefix
|
||||
const prefix = "Listening on "
|
||||
if !strings.HasPrefix(line, prefix) {
|
||||
return nil, fmt.Errorf("unexpected output format: %s", line)
|
||||
}
|
||||
url := strings.TrimPrefix(line, prefix)
|
||||
return &remoteServer{
|
||||
url: url,
|
||||
cmd: cmd,
|
||||
|
||||
@ -2,39 +2,36 @@
|
||||
package playwright_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/playwright-community/playwright-go"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSelectorsRegisterShouldWork(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
tagSelector := `
|
||||
{
|
||||
create(root, target) {
|
||||
return target.nodeName;
|
||||
},
|
||||
tagSelector := `(() => ({
|
||||
query(root, selector) {
|
||||
return root.querySelector(selector);
|
||||
},
|
||||
queryAll(root, selector) {
|
||||
return Array.from(root.querySelectorAll(selector));
|
||||
}
|
||||
}
|
||||
`
|
||||
selectorName := "tag_" + browserName
|
||||
selector2Name := "tag2_" + browserName
|
||||
}))()`
|
||||
// Use unique names to avoid conflicts when running tests multiple times
|
||||
uniqueSuffix := fmt.Sprintf("%s_%d", t.Name(), time.Now().UnixNano())
|
||||
selectorName := "tag_" + browserName + "_" + uniqueSuffix
|
||||
selector2Name := "tag2_" + browserName + "_" + uniqueSuffix
|
||||
|
||||
err := pw.Selectors.Register(selectorName, playwright.Script{})
|
||||
require.ErrorContains(t, err, `Either source or path should be specified`)
|
||||
// Register one engine before creating context.
|
||||
err = pw.Selectors.Register(selectorName, playwright.Script{
|
||||
err := pw.Selectors.Register(selectorName, playwright.Script{
|
||||
Content: &tagSelector,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
BeforeEach(t)
|
||||
|
||||
// Register another engine after creating context.
|
||||
err = pw.Selectors.Register(selector2Name, playwright.Script{
|
||||
Content: &tagSelector,
|
||||
@ -49,7 +46,7 @@ func TestSelectorsRegisterShouldWork(t *testing.T) {
|
||||
ret, err = page.EvalOnSelector(selectorName+"=SPAN", `e => e.nodeName`, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "SPAN", ret)
|
||||
ret, err = page.EvalOnSelectorAll(selectorName+"=DIV", `es => es.length`, nil)
|
||||
ret, err = page.EvalOnSelectorAll(selectorName+"=DIV", `es => es.length`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, ret)
|
||||
|
||||
@ -64,7 +61,7 @@ func TestSelectorsRegisterShouldWork(t *testing.T) {
|
||||
require.Equal(t, 2, ret)
|
||||
|
||||
// Selector names are case-sensitive.
|
||||
_, err = page.QuerySelector("tAG=DIV")
|
||||
_, err = page.Locator("tAG=DIV").All()
|
||||
require.ErrorContains(t, err, `Unknown engine "tAG" while parsing selector tAG=DIV`)
|
||||
|
||||
require.NoError(t, context.Close())
|
||||
@ -101,12 +98,14 @@ func TestSelectorsShouldUseDataTestIdInStrictErrors(t *testing.T) {
|
||||
func TestSelectorsShouldWorkWithPath(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
require.NoError(t, pw.Selectors.Register("foo", playwright.Script{
|
||||
// Use unique name to avoid conflicts when running tests multiple times
|
||||
selectorName := fmt.Sprintf("foo_%s_%d", t.Name(), time.Now().UnixNano())
|
||||
require.NoError(t, pw.Selectors.Register(selectorName, playwright.Script{
|
||||
Path: playwright.String(Asset("sectionselectorengine.js")),
|
||||
}))
|
||||
require.NoError(t, page.SetContent(`<section></section>`))
|
||||
|
||||
ret, err := page.EvalOnSelector("foo=whatever", `e => e.nodeName`, nil)
|
||||
ret, err := page.EvalOnSelector(selectorName+"=whatever", `e => e.nodeName`, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "SECTION", ret)
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/playwright-community/playwright-go"
|
||||
@ -163,6 +164,59 @@ func TestShouldShowTracingGroupInActionList(t *testing.T) {
|
||||
}, actions)
|
||||
}
|
||||
|
||||
// mapInternalAPIToPublic maps internal Playwright class.method names to public API names
|
||||
func mapInternalAPIToPublic(class, method string) string {
|
||||
// Map internal classes to public API classes
|
||||
classMethodKey := class + "." + method
|
||||
|
||||
// Common Frame methods that should map to Page
|
||||
frameToPageMethods := map[string]bool{
|
||||
"goto": true, "reload": true, "goBack": true, "goForward": true,
|
||||
"setContent": true, "waitForNavigation": true, "waitForURL": true,
|
||||
"waitForLoadState": true, "screenshot": true, "pdf": true,
|
||||
"close": true, "pause": true,
|
||||
}
|
||||
|
||||
// Frame selector/locator methods that should map to Locator
|
||||
frameLocatorMethods := map[string]bool{
|
||||
"click": true, "dblclick": true, "fill": true, "press": true,
|
||||
"type": true, "hover": true, "check": true, "uncheck": true,
|
||||
"selectOption": true, "setInputFiles": true, "focus": true,
|
||||
"blur": true, "tap": true, "dispatchEvent": true, "evaluate": true,
|
||||
"isVisible": true, "isHidden": true, "isEnabled": true, "isDisabled": true,
|
||||
"isChecked": true, "isEditable": true, "textContent": true,
|
||||
"innerText": true, "innerHTML": true, "getAttribute": true,
|
||||
}
|
||||
|
||||
if class == "Frame" {
|
||||
if frameToPageMethods[method] {
|
||||
class = "Page"
|
||||
} else if frameLocatorMethods[method] {
|
||||
class = "Locator"
|
||||
}
|
||||
}
|
||||
|
||||
// Special case mappings
|
||||
specialMappings := map[string]string{
|
||||
"BrowserContext.newPage": "BrowserContext.NewPage",
|
||||
"Page.waitForTimeout": "Page.WaitForTimeout",
|
||||
}
|
||||
|
||||
// Convert method to title case (first letter uppercase)
|
||||
titleCaseMethod := strings.ToUpper(method[:1]) + method[1:]
|
||||
apiName := class + "." + titleCaseMethod
|
||||
|
||||
// Check for special mappings
|
||||
if mapped, ok := specialMappings[classMethodKey]; ok {
|
||||
return mapped
|
||||
}
|
||||
if mapped, ok := specialMappings[apiName]; ok {
|
||||
return mapped
|
||||
}
|
||||
|
||||
return apiName
|
||||
}
|
||||
|
||||
func parseTrace(t *testing.T, tracePath string) (files map[string][]byte, events []interface{}) {
|
||||
t.Helper()
|
||||
// read and unzip trace
|
||||
@ -193,9 +247,23 @@ func parseTrace(t *testing.T, tracePath string) (files map[string][]byte, events
|
||||
var event map[string]interface{}
|
||||
err := json.Unmarshal(line, &event)
|
||||
require.NoError(t, err)
|
||||
switch event["type"].(string) {
|
||||
eventType, _ := event["type"].(string)
|
||||
|
||||
switch eventType {
|
||||
case "before":
|
||||
event["type"] = "action"
|
||||
// Compute apiName from class and method for regular actions
|
||||
// For tracing groups, use the title field
|
||||
class, _ := event["class"].(string)
|
||||
method, _ := event["method"].(string)
|
||||
title, hasTitle := event["title"].(string)
|
||||
|
||||
if method == "tracingGroup" && hasTitle {
|
||||
event["apiName"] = title
|
||||
} else if class != "" && method != "" {
|
||||
event["apiName"] = mapInternalAPIToPublic(class, method)
|
||||
}
|
||||
|
||||
actionMap[event["callId"].(string)] = event
|
||||
events = append(events, event)
|
||||
case "input":
|
||||
@ -233,7 +301,9 @@ func getTraceActions(events []interface{}) []string {
|
||||
})
|
||||
for _, e := range actionEvents {
|
||||
event := e.(map[string]interface{})
|
||||
actions = append(actions, event["apiName"].(string))
|
||||
if apiName, ok := event["apiName"].(string); ok {
|
||||
actions = append(actions, apiName)
|
||||
}
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
@ -210,7 +210,7 @@ func TestPageUnrouteShouldNotWaitForPendingHandlersToComplete(t *testing.T) {
|
||||
|
||||
secondHandlerCalled := false
|
||||
|
||||
require.NoError(t, context.Route(regexp.MustCompile(".*"), func(route playwright.Route) {
|
||||
require.NoError(t, page.Route(regexp.MustCompile(".*"), func(route playwright.Route) {
|
||||
secondHandlerCalled = true
|
||||
require.NoError(t, route.Continue())
|
||||
}))
|
||||
|
||||
@ -147,3 +147,73 @@ func TestWorkerShouldClearUponCrossProcessNavigation(t *testing.T) {
|
||||
require.True(t, destroyed)
|
||||
require.Equal(t, 0, len(page.Workers()))
|
||||
}
|
||||
|
||||
// TestConsoleMessageWorker verifies that ConsoleMessage.Worker() returns the worker
|
||||
// Based on upstream test: playwright/tests/page/workers.spec.ts
|
||||
func TestConsoleMessageWorker(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
// Create a worker and capture console message from it
|
||||
workerChan := make(chan playwright.Worker, 1)
|
||||
page.Once("worker", func(worker playwright.Worker) {
|
||||
workerChan <- worker
|
||||
})
|
||||
|
||||
consoleChan := make(chan playwright.ConsoleMessage, 1)
|
||||
page.Once("console", func(message playwright.ConsoleMessage) {
|
||||
consoleChan <- message
|
||||
})
|
||||
|
||||
// Create a worker that logs a message
|
||||
_, err := page.Evaluate(`() => {
|
||||
const workerCode = 'console.log("hello from worker")';
|
||||
const blob = new Blob([workerCode], { type: 'application/javascript' });
|
||||
const worker = new Worker(URL.createObjectURL(blob));
|
||||
}`)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Wait for worker creation
|
||||
worker := <-workerChan
|
||||
require.NotNil(t, worker)
|
||||
|
||||
// Wait for console message
|
||||
message := <-consoleChan
|
||||
require.NotNil(t, message)
|
||||
|
||||
// Verify the message is from the worker
|
||||
require.Equal(t, "hello from worker", message.Text())
|
||||
|
||||
msgWorker, err := message.Worker()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, worker, msgWorker, "console message should reference the worker")
|
||||
|
||||
// Worker console messages also have a page reference (they're emitted to both)
|
||||
msgPage := message.Page()
|
||||
require.Equal(t, page, msgPage, "worker console messages are also associated with the page")
|
||||
}
|
||||
|
||||
// TestConsoleMessageWorkerNil verifies that page console messages have nil worker
|
||||
func TestConsoleMessageWorkerNil(t *testing.T) {
|
||||
BeforeEach(t)
|
||||
|
||||
consoleChan := make(chan playwright.ConsoleMessage, 1)
|
||||
page.Once("console", func(message playwright.ConsoleMessage) {
|
||||
consoleChan <- message
|
||||
})
|
||||
|
||||
_, err := page.Evaluate(`() => console.log('hello from page')`)
|
||||
require.NoError(t, err)
|
||||
|
||||
message := <-consoleChan
|
||||
require.NotNil(t, message)
|
||||
require.Equal(t, "hello from page", message.Text())
|
||||
|
||||
// Page console messages should not have a worker
|
||||
msgWorker, err := message.Worker()
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, msgWorker, "page console messages should not have a worker")
|
||||
|
||||
// But should have a page
|
||||
msgPage := message.Page()
|
||||
require.Equal(t, page, msgPage)
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ type pipeTransport struct {
|
||||
bufReader *bufio.Reader
|
||||
closed chan struct{}
|
||||
onClose func() error
|
||||
process *os.Process
|
||||
}
|
||||
|
||||
func (t *pipeTransport) Poll() (*message, error) {
|
||||
@ -137,5 +138,7 @@ func newPipeTransport(driver *PlaywrightDriver, stderr io.Writer) (transport, er
|
||||
return nil, fmt.Errorf("could not start driver: %w", err)
|
||||
}
|
||||
|
||||
t.process = cmd.Process
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
@ -48,9 +48,9 @@ func IntSlice(v ...int) *[]int {
|
||||
|
||||
// ToOptionalStorageState converts StorageState to OptionalStorageState for use directly in [Browser.NewContext]
|
||||
func (s StorageState) ToOptionalStorageState() *OptionalStorageState {
|
||||
cookies := make([]OptionalCookie, len(s.Cookies))
|
||||
cookies := make([]OptionalStorageStateOptionalCookie, len(s.Cookies))
|
||||
for i, c := range s.Cookies {
|
||||
cookies[i] = c.ToOptionalCookie()
|
||||
cookies[i] = c.ToOptionalStorageStateOptionalCookie()
|
||||
}
|
||||
return &OptionalStorageState{
|
||||
Origins: s.Origins,
|
||||
@ -58,6 +58,19 @@ func (s StorageState) ToOptionalStorageState() *OptionalStorageState {
|
||||
}
|
||||
}
|
||||
|
||||
func (c StorageStateCookie) ToOptionalStorageStateOptionalCookie() OptionalStorageStateOptionalCookie {
|
||||
return OptionalStorageStateOptionalCookie{
|
||||
Name: c.Name,
|
||||
Value: c.Value,
|
||||
Domain: String(c.Domain),
|
||||
Path: String(c.Path),
|
||||
Expires: Float(c.Expires),
|
||||
HttpOnly: Bool(c.HttpOnly),
|
||||
Secure: Bool(c.Secure),
|
||||
SameSite: c.SameSite,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Cookie) ToOptionalCookie() OptionalCookie {
|
||||
return OptionalCookie{
|
||||
Name: c.Name,
|
||||
|
||||
@ -70,9 +70,16 @@ func (w *workerImpl) OnClose(fn func(Worker)) {
|
||||
w.On("close", fn)
|
||||
}
|
||||
|
||||
func (w *workerImpl) OnConsole(fn func(ConsoleMessage)) {
|
||||
w.On("console", fn)
|
||||
}
|
||||
|
||||
func newWorker(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *workerImpl {
|
||||
bt := &workerImpl{}
|
||||
bt.createChannelOwner(bt, parent, objectType, guid, initializer)
|
||||
bt.channel.On("close", bt.onClose)
|
||||
bt.channel.On("console", func(ev map[string]interface{}) {
|
||||
bt.Emit("console", newConsoleMessage(ev))
|
||||
})
|
||||
return bt
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user