Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor options to const #1044

Draft
wants to merge 41 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
f62d724
Add a WaitForEventOptions type
ankur22 Sep 20, 2023
7221b56
Add the implementation to parse the options
ankur22 Sep 20, 2023
29aa69e
Refactor WaitForEvent to work with its parser
ankur22 Sep 20, 2023
8c734f4
Fix browserContext.waitForEvent's timeout
ankur22 Sep 20, 2023
1314be4
Update waitForEvent to clarify page support
ankur22 Sep 20, 2023
ff2b281
Update messaging around the other cases
ankur22 Sep 20, 2023
1020eef
Refactor waitForEvent to work with errors
ankur22 Sep 20, 2023
fb354e4
Fix parsing of waitForEvent options
ankur22 Sep 20, 2023
e8fec53
Update to allow nil predicate function
ankur22 Sep 20, 2023
3c37f9f
Refactor duplicate code into defer
ankur22 Sep 20, 2023
2e2e063
Update mapping to promisify waitForEvent
ankur22 Sep 20, 2023
aa5952b
Add happy path tests for waitForEvent
ankur22 Sep 20, 2023
c00acd5
Add failure cases to integration test
ankur22 Sep 21, 2023
0be19f3
Add an example script for waitForEvent
ankur22 Sep 20, 2023
4c096de
Add a new waitForEventType type
ankur22 Sep 21, 2023
d741d45
Refactor error message for incorrect event
ankur22 Sep 21, 2023
af81e9a
Update log message with correct method name
ankur22 Sep 21, 2023
bbfe8df
Refactor for early return
ankur22 Sep 21, 2023
232422c
fixup! Add a new waitForEventType type
ankur22 Sep 21, 2023
4f0b44a
fixup! Update log message with correct method name
ankur22 Sep 21, 2023
3d1f558
Refactor to early exit if opts doesn't exist
ankur22 Sep 21, 2023
855850a
Refactor comments to align with 80 chars line len
ankur22 Sep 21, 2023
b2d2503
Refactor test names to be less verbose
ankur22 Sep 21, 2023
8fd96c5
Add comments to test to bring clarity
ankur22 Sep 21, 2023
d8c52f9
Add a const for the timeout option
ankur22 Sep 21, 2023
4b8d60d
Add a const for the predicate option
ankur22 Sep 21, 2023
5e72dc1
Add const for noWaitAfter option
ankur22 Sep 21, 2023
5485c29
Add const for force option
ankur22 Sep 21, 2023
7fa2466
Add const for delay option
ankur22 Sep 21, 2023
6c8b776
Add const for omitBackground option
ankur22 Sep 21, 2023
0a46fac
Add const for type option
ankur22 Sep 21, 2023
9d037f3
Add const for quality option
ankur22 Sep 21, 2023
e51fbcf
Add const for path option
ankur22 Sep 21, 2023
e597d69
Add const for strict option
ankur22 Sep 21, 2023
7f8673e
Add const for referer option
ankur22 Sep 21, 2023
53d63ea
Add const for waitUntil option
ankur22 Sep 21, 2023
57467a7
Add const for polling option
ankur22 Sep 21, 2023
50791d6
Add const for url option
ankur22 Sep 21, 2023
71178ef
Add const for button option
ankur22 Sep 21, 2023
f06d53a
Add const for clickCount option
ankur22 Sep 21, 2023
90b47b3
Add const for modifiers option
ankur22 Sep 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/browser_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type BrowserContext interface {
SetOffline(offline bool)
StorageState(opts goja.Value)
Unroute(url goja.Value, handler goja.Callable)
WaitForEvent(event string, optsOrPredicate goja.Value) any
WaitForEvent(event string, optsOrPredicate goja.Value) (any, error)
}

// Cookie represents a browser cookie.
Expand Down
17 changes: 16 additions & 1 deletion browser/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,22 @@ func mapBrowserContext(vu moduleVU, bc api.BrowserContext) mapping {
"setOffline": bc.SetOffline,
"storageState": bc.StorageState,
"unroute": bc.Unroute,
"waitForEvent": bc.WaitForEvent,
"waitForEvent": func(event string, optsOrPredicate goja.Value) *goja.Promise {
ctx := vu.Context()
return k6ext.Promise(ctx, func() (result any, reason error) {
resp, err := bc.WaitForEvent(event, optsOrPredicate)
if err != nil {
return nil, err //nolint:wrapcheck
}

p, ok := resp.(api.Page)
if !ok {
panicIfFatalError(ctx, fmt.Errorf("response object is not a page: %w", k6error.ErrFatal))
}

return mapPage(vu, p), nil
})
},
"pages": func() *goja.Object {
var (
mpages []mapping
Expand Down
121 changes: 64 additions & 57 deletions common/browser_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package common

import (
"context"
"errors"
"fmt"
"net/url"
"reflect"
Expand Down Expand Up @@ -30,6 +31,16 @@ var (
_ api.BrowserContext = &BrowserContext{}
)

// waitForEventType represents the event types that can be used when working
// with the browserContext.waitForEvent API.
type waitForEventType string

const (
// waitForEventTypePage represents the page event which fires when a new
// page is created.
waitForEventTypePage = "page"
)

// BrowserContext stores context information for a single independent browser session.
// A newly launched browser instance contains a default browser context.
// Any browser context created aside from the default will be considered an "incognito"
Expand Down Expand Up @@ -340,43 +351,28 @@ func (b *BrowserContext) Unroute(url goja.Value, handler goja.Callable) {
}

// WaitForEvent waits for event.
func (b *BrowserContext) WaitForEvent(event string, optsOrPredicate goja.Value) any {
// TODO: This public API needs Promise support (as return value) to be useful in JS!
func (b *BrowserContext) WaitForEvent(event string, optsOrPredicate goja.Value) (any, error) {
b.logger.Debugf("BrowserContext:WaitForEvent", "bctxid:%v event:%q", b.id, event)

var (
isCallable bool
predicateFn goja.Callable
// TODO: Find out whether * time.Second is necessary.
timeout = b.browser.browserOpts.Timeout * time.Second //nolint:durationcheck
parsedOpts := NewWaitForEventOptions(
b.timeoutSettings.timeout(),
)
if gojaValueExists(optsOrPredicate) {
switch optsOrPredicate.ExportType() {
case reflect.TypeOf(goja.Object{}):
opts := optsOrPredicate.ToObject(b.vu.Runtime())
for _, k := range opts.Keys() {
switch k {
case "predicate":
predicateFn, isCallable = goja.AssertFunction(opts.Get(k))
if !isCallable {
k6ext.Panic(b.ctx, "predicate function is not callable")
}
case "timeout":
timeout = time.Duration(opts.Get(k).ToInteger()) * time.Millisecond
}
}
default:
predicateFn, isCallable = goja.AssertFunction(optsOrPredicate)
if !isCallable {
k6ext.Panic(b.ctx, "predicate function is not callable")
}
}
if err := parsedOpts.Parse(b.ctx, optsOrPredicate); err != nil {
return nil, fmt.Errorf("parsing waitForEvent options: %w", err)
}

return b.waitForEvent(event, predicateFn, timeout)
return b.waitForEvent(waitForEventType(event), parsedOpts.PredicateFn, parsedOpts.Timeout)
}

func (b *BrowserContext) waitForEvent(event string, predicateFn goja.Callable, timeout time.Duration) any {
func (b *BrowserContext) waitForEvent(
event waitForEventType,
predicateFn goja.Callable,
timeout time.Duration,
) (any, error) {
if event != waitForEventTypePage {
return nil, fmt.Errorf("incorrect event %q, %q is the only event supported", event, waitForEventTypePage)
}

evCancelCtx, evCancelFn := context.WithCancel(b.ctx)
chEvHandler := make(chan Event)
ch := make(chan any)
Expand All @@ -388,52 +384,63 @@ func (b *BrowserContext) waitForEvent(event string, predicateFn goja.Callable, t

select {
case <-b.ctx.Done():
b.logger.Debugf("BrowserContext:WaitForEvent:ctx.Done", "bctxid:%v event:%q", b.id, event)
return nil, errors.New("test iteration ended")
case <-time.After(timeout):
b.logger.Debugf("BrowserContext:WaitForEvent:timeout", "bctxid:%v event:%q", b.id, event)
return nil, fmt.Errorf("waitForEvent timed out after %v", timeout)
case evData := <-ch:
b.logger.Debugf("BrowserContext:WaitForEvent:evData", "bctxid:%v event:%q", b.id, event)
return evData
return evData, nil
}
b.logger.Debugf("BrowserContext:WaitForEvent:return nil", "bctxid:%v event:%q", b.id, event)

return nil
}

// runWaitForEventHandler can work with a nil predicateFn. If predicateFn is
// nil it will return the response straight away.
func (b *BrowserContext) runWaitForEventHandler(
ctx context.Context, evCancelFn func(), chEvHandler chan Event, out chan any, predicateFn goja.Callable,
) {
b.logger.Debugf("BrowserContext:WaitForEvent:go():starts", "bctxid:%v", b.id)
defer b.logger.Debugf("BrowserContext:WaitForEvent:go():returns", "bctxid:%v", b.id)
b.logger.Debugf("BrowserContext:runWaitForEventHandler:go():starts", "bctxid:%v", b.id)
defer b.logger.Debugf("BrowserContext:runWaitForEventHandler:go():returns", "bctxid:%v", b.id)

var p *Page
defer func() {
out <- p
close(out)

// We wait for one matching event only, then remove event handler by
// cancelling context and stopping goroutine.
evCancelFn()
}()

for {
select {
case <-ctx.Done():
b.logger.Debugf("BrowserContext:WaitForEvent:go():ctx:done", "bctxid:%v", b.id)
b.logger.Debugf("BrowserContext:runWaitForEventHandler:go():ctx:done", "bctxid:%v", b.id)
return
case ev := <-chEvHandler:
if ev.typ == EventBrowserContextClose {
b.logger.Debugf("BrowserContext:WaitForEvent:go():EventBrowserContextClose:return", "bctxid:%v", b.id)
out <- nil
close(out)
b.logger.Debugf("BrowserContext:runWaitForEventHandler:go():EventBrowserContextClose:return", "bctxid:%v", b.id)
return
}

if ev.typ != EventBrowserContextPage {
continue
}

b.logger.Debugf("BrowserContext:runWaitForEventHandler:go():EventBrowserContextPage", "bctxid:%v", b.id)
p, _ = ev.data.(*Page)

// We wait for one matching event only,
// then remove event handler by cancelling context and stopping goroutine.
evCancelFn()
if predicateFn == nil {
b.logger.Debugf("BrowserContext:runWaitForEventHandler:go():EventBrowserContextPage:return", "bctxid:%v", b.id)
return
}
if ev.typ == EventBrowserContextPage {
b.logger.Debugf("BrowserContext:WaitForEvent:go():EventBrowserContextPage", "bctxid:%v", b.id)
p, _ := ev.data.(*Page)
if retVal, err := predicateFn(b.vu.Runtime().ToValue(p)); err == nil && retVal.ToBoolean() {
b.logger.Debugf("BrowserContext:WaitForEvent:go():EventBrowserContextPage:return", "bctxid:%v", b.id)
out <- p
close(out)

// We wait for one matching event only,
// then remove event handler by cancelling context and stopping goroutine.
evCancelFn()
return
}

if retVal, err := predicateFn(b.vu.Runtime().ToValue(p)); err == nil && retVal.ToBoolean() {
b.logger.Debugf(
"BrowserContext:runWaitForEventHandler:go():EventBrowserContextPage:predicateFn:return",
"bctxid:%v", b.id,
)
return
}
}
}
Expand Down
50 changes: 50 additions & 0 deletions common/browser_context_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package common

import (
"context"
"errors"
"fmt"
"time"

"github.com/grafana/xk6-browser/k6ext"

Expand Down Expand Up @@ -132,3 +134,51 @@ func (b *BrowserContextOptions) Parse(ctx context.Context, opts goja.Value) erro
}
return nil
}

// WaitForEventOptions are the options used by the browserContext.waitForEvent API.
type WaitForEventOptions struct {
Timeout time.Duration
PredicateFn goja.Callable
}

// NewWaitForEventOptions created a new instance of WaitForEventOptions with a
// default timeout.
func NewWaitForEventOptions(defaultTimeout time.Duration) *WaitForEventOptions {
return &WaitForEventOptions{
Timeout: defaultTimeout,
}
}

// Parse will parse the options or a callable predicate function. It can parse
// only a callable predicate function or an object which contains a callable
// predicate function and a timeout.
func (w *WaitForEventOptions) Parse(ctx context.Context, optsOrPredicate goja.Value) error {
if !gojaValueExists(optsOrPredicate) {
return nil
}

var (
isCallable bool
rt = k6ext.Runtime(ctx)
)

w.PredicateFn, isCallable = goja.AssertFunction(optsOrPredicate)
if isCallable {
return nil
}

opts := optsOrPredicate.ToObject(rt)
for _, k := range opts.Keys() {
switch k {
case OptionsPredicate:
w.PredicateFn, isCallable = goja.AssertFunction(opts.Get(k))
if !isCallable {
return errors.New("predicate function is not callable")
}
case OptionsTimeout:
w.Timeout = time.Duration(opts.Get(k).ToInteger()) * time.Millisecond
}
}

return nil
}
20 changes: 20 additions & 0 deletions common/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,24 @@ const (
// Life-cycle consts

LifeCycleNetworkIdleTimeout time.Duration = 500 * time.Millisecond

// Options consts.

OptionsTimeout = "timeout"
OptionsPredicate = "predicate"
OptionsNoWaitAfter = "noWaitAfter"
OptionsForce = "force"
OptionsDelay = "delay"
OptionsOmitBackground = "omitBackground"
OptionsType = "type"
OptionsQuality = "quality"
OptionsPath = "path"
OptionsStrict = "strict"
OptionsReferer = "referer"
OptionsWaitUntil = "waitUntil"
OptionsPolling = "polling"
OptionsURL = "url"
OptionsButton = "button"
OptionsClickCount = "clickCount"
OptionsModifiers = "modifiers"
)
Loading
Loading