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

Add lifecycle event validation to WaitForLoadState #300

Merged
merged 5 commits into from
May 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 4 additions & 5 deletions common/frame.go
Original file line number Diff line number Diff line change
Expand Up @@ -1584,11 +1584,10 @@ func (f *Frame) WaitForLoadState(state string, opts goja.Value) {
}

waitUntil := LifecycleEventLoad
switch state {
case "domcontentloaded":
waitUntil = LifecycleEventDOMContentLoad
case "networkidle":
waitUntil = LifecycleEventNetworkIdle
if state != "" {
if err = waitUntil.UnmarshalText([]byte(state)); err != nil {
k6Throw(f.ctx, "waitForLoadState error: %v", err)
}
}

if f.hasLifecycleEventFired(waitUntil) {
Expand Down
18 changes: 6 additions & 12 deletions common/frame_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,8 @@ func (o *FrameGotoOptions) Parse(ctx context.Context, opts goja.Value) error {
o.Timeout = time.Duration(opts.Get(k).ToInteger()) * time.Millisecond
case "waitUntil":
lifeCycle := opts.Get(k).String()
if l, ok := lifecycleEventToID[lifeCycle]; ok {
o.WaitUntil = l
} else {
return fmt.Errorf("%q is not a valid lifecycle", lifeCycle)
if err := o.WaitUntil.UnmarshalText([]byte(lifeCycle)); err != nil {
return fmt.Errorf("error parsing goto options: %w", err)
}
}
}
Expand Down Expand Up @@ -504,10 +502,8 @@ func (o *FrameSetContentOptions) Parse(ctx context.Context, opts goja.Value) err
o.Timeout = time.Duration(opts.Get(k).ToInteger()) * time.Millisecond
case "waitUntil":
lifeCycle := opts.Get(k).String()
if l, ok := lifecycleEventToID[lifeCycle]; ok {
o.WaitUntil = l
} else {
return fmt.Errorf("%q is not a valid lifecycle", lifeCycle)
if err := o.WaitUntil.UnmarshalText([]byte(lifeCycle)); err != nil {
return fmt.Errorf("error parsing setContent options: %w", err)
}
}
}
Expand Down Expand Up @@ -678,10 +674,8 @@ func (o *FrameWaitForNavigationOptions) Parse(ctx context.Context, opts goja.Val
o.Timeout = time.Duration(opts.Get(k).ToInteger()) * time.Millisecond
case "waitUntil":
lifeCycle := opts.Get(k).String()
if l, ok := lifecycleEventToID[lifeCycle]; ok {
o.WaitUntil = l
} else {
return fmt.Errorf("%q is not a valid lifecycle", lifeCycle)
if err := o.WaitUntil.UnmarshalText([]byte(lifeCycle)); err != nil {
return fmt.Errorf("error parsing waitForNavigation options: %w", err)
}
}
}
Expand Down
119 changes: 119 additions & 0 deletions common/frame_options_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package common

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestFrameGotoOptionsParse(t *testing.T) {
t.Parallel()

t.Run("ok", func(t *testing.T) {
t.Parallel()

mockVU := newMockVU(t)
opts := mockVU.RuntimeField.ToValue(map[string]interface{}{
"timeout": "1000",
"waitUntil": "networkidle",
})
gotoOpts := NewFrameGotoOptions("https://example.com/", 0)
err := gotoOpts.Parse(mockVU.CtxField, opts)
require.NoError(t, err)

assert.Equal(t, "https://example.com/", gotoOpts.Referer)
assert.Equal(t, time.Second, gotoOpts.Timeout)
assert.Equal(t, LifecycleEventNetworkIdle, gotoOpts.WaitUntil)
})

t.Run("err/invalid_waitUntil", func(t *testing.T) {
t.Parallel()

mockVU := newMockVU(t)
opts := mockVU.RuntimeField.ToValue(map[string]interface{}{
"waitUntil": "none",
})
navOpts := NewFrameGotoOptions("", 0)
err := navOpts.Parse(mockVU.CtxField, opts)

assert.EqualError(t, err,
`error parsing goto options: `+
`invalid lifecycle event: "none"; must be one of: `+
`load, domcontentloaded, networkidle`)
})
}

func TestFrameSetContentOptionsParse(t *testing.T) {
t.Parallel()

t.Run("ok", func(t *testing.T) {
t.Parallel()

mockVU := newMockVU(t)
opts := mockVU.RuntimeField.ToValue(map[string]interface{}{
"waitUntil": "networkidle",
})
scOpts := NewFrameSetContentOptions(30 * time.Second)
err := scOpts.Parse(mockVU.CtxField, opts)
require.NoError(t, err)

assert.Equal(t, 30*time.Second, scOpts.Timeout)
assert.Equal(t, LifecycleEventNetworkIdle, scOpts.WaitUntil)
})

t.Run("err/invalid_waitUntil", func(t *testing.T) {
t.Parallel()

mockVU := newMockVU(t)
opts := mockVU.RuntimeField.ToValue(map[string]interface{}{
"waitUntil": "none",
})
navOpts := NewFrameSetContentOptions(0)
err := navOpts.Parse(mockVU.CtxField, opts)

assert.EqualError(t, err,
`error parsing setContent options: `+
`invalid lifecycle event: "none"; must be one of: `+
`load, domcontentloaded, networkidle`)
})
}

func TestFrameWaitForNavigationOptionsParse(t *testing.T) {
t.Parallel()

t.Run("ok", func(t *testing.T) {
t.Parallel()

mockVU := newMockVU(t)
opts := mockVU.RuntimeField.ToValue(map[string]interface{}{
"url": "https://example.com/",
"timeout": "1000",
"waitUntil": "networkidle",
})
navOpts := NewFrameWaitForNavigationOptions(0)
err := navOpts.Parse(mockVU.CtxField, opts)
require.NoError(t, err)

assert.Equal(t, "https://example.com/", navOpts.URL)
assert.Equal(t, time.Second, navOpts.Timeout)
assert.Equal(t, LifecycleEventNetworkIdle, navOpts.WaitUntil)
})

t.Run("err/invalid_waitUntil", func(t *testing.T) {
t.Parallel()

mockVU := newMockVU(t)
opts := mockVU.RuntimeField.ToValue(map[string]interface{}{
"waitUntil": "none",
})
navOpts := NewFrameWaitForNavigationOptions(0)
err := navOpts.Parse(mockVU.CtxField, opts)

assert.EqualError(t, err,
`error parsing waitForNavigation options: `+
`invalid lifecycle event: "none"; must be one of: `+
`load, domcontentloaded, networkidle`)
})
}
43 changes: 43 additions & 0 deletions common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (
"encoding/json"
"fmt"
"math"
"sort"
"strings"

"github.com/dop251/goja"

Expand Down Expand Up @@ -278,6 +280,47 @@ func (l *LifecycleEvent) UnmarshalJSON(b []byte) error {
return nil
}

// MarshalText returns the string representation of the enum value.
// It returns an error if the enum value is invalid.
func (l *LifecycleEvent) MarshalText() ([]byte, error) {
if l == nil {
return []byte(""), nil
}
var (
ok bool
s string
)
if s, ok = lifecycleEventToString[*l]; !ok {
return nil, fmt.Errorf("invalid lifecycle event: %v", int(*l))
}

return []byte(s), nil
}

// UnmarshalText unmarshals a text representation to the enum value.
// It returns an error if given a wrong value.
func (l *LifecycleEvent) UnmarshalText(text []byte) error {
inancgumus marked this conversation as resolved.
Show resolved Hide resolved
var (
ok bool
val = string(text)
)

if *l, ok = lifecycleEventToID[val]; !ok {
valid := make([]string, 0, len(lifecycleEventToID))
for k := range lifecycleEventToID {
valid = append(valid, k)
}
sort.Slice(valid, func(i, j int) bool {
return lifecycleEventToID[valid[j]] > lifecycleEventToID[valid[i]]
})
return fmt.Errorf(
"invalid lifecycle event: %q; must be one of: %s",
val, strings.Join(valid, ", "))
}

return nil
}

type MediaType string

const (
Expand Down
69 changes: 69 additions & 0 deletions common/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// See: Issue #183 for details.
Expand Down Expand Up @@ -60,3 +61,71 @@ func TestViewportCalculateInset(t *testing.T) {
})
}
}

func TestLifecycleEventMarshalText(t *testing.T) {
t.Parallel()

t.Run("ok/nil", func(t *testing.T) {
t.Parallel()

var evt *LifecycleEvent
m, err := evt.MarshalText()
require.NoError(t, err)
assert.Equal(t, []byte(""), m)
})

t.Run("err/invalid", func(t *testing.T) {
t.Parallel()

evt := LifecycleEvent(-1)
_, err := evt.MarshalText()
require.EqualError(t, err, "invalid lifecycle event: -1")
})
}

func TestLifecycleEventMarshalTextRound(t *testing.T) {
t.Parallel()

evt := LifecycleEventLoad
m, err := evt.MarshalText()
require.NoError(t, err)
assert.Equal(t, []byte("load"), m)

var evt2 LifecycleEvent
err = evt2.UnmarshalText(m)
require.NoError(t, err)
assert.Equal(t, evt, evt2)
}

func TestLifecycleEventUnmarshalText(t *testing.T) {
t.Parallel()

t.Run("ok", func(t *testing.T) {
t.Parallel()

var evt LifecycleEvent
err := evt.UnmarshalText([]byte("load"))
require.NoError(t, err)
assert.Equal(t, LifecycleEventLoad, evt)
})

t.Run("err/invalid", func(t *testing.T) {
t.Parallel()

var evt LifecycleEvent
err := evt.UnmarshalText([]byte("none"))
require.EqualError(t, err,
`invalid lifecycle event: "none"; `+
`must be one of: load, domcontentloaded, networkidle`)
})

t.Run("err/invalid_empty", func(t *testing.T) {
t.Parallel()

var evt LifecycleEvent
err := evt.UnmarshalText([]byte(""))
require.EqualError(t, err,
`invalid lifecycle event: ""; `+
`must be one of: load, domcontentloaded, networkidle`)
})
}
23 changes: 21 additions & 2 deletions tests/page_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,26 @@ func TestPageWaitForFunction(t *testing.T) {
})
}

func TestPageWaitForLoadState(t *testing.T) {
t.Parallel()

// TODO: Add happy path tests once WaitForLoadState is not racy.

t.Run("err_wrong_event", func(t *testing.T) {
t.Parallel()

defer func() {
assertPanicErrorContains(t, recover(),
`invalid lifecycle event: "none"; `+
`must be one of: load, domcontentloaded, networkidle`)
}()

p := newTestBrowser(t).NewPage(nil)
p.WaitForLoadState("none", nil)
t.Error("did not panic")
})
}

// See: The issue #187 for details.
func TestPageWaitForNavigationShouldNotPanic(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
Expand All @@ -645,8 +665,7 @@ func assertPanicErrorContains(t *testing.T, err interface{}, expErrMsg string) {
require.IsType(t, &goja.Object{}, err)
gotObj, _ := err.(*goja.Object)
got := gotObj.Export()
expErr := fmt.Errorf("%w", errors.New(expErrMsg))
require.IsType(t, expErr, got)
expErr := errors.New(expErrMsg)
gotErr, ok := got.(error)
require.True(t, ok)
assert.Contains(t, gotErr.Error(), expErr.Error())
Expand Down