Skip to content
This repository was archived by the owner on Jan 30, 2025. It is now read-only.

Commit 241e402

Browse files
author
Ivan Mirić
authored
Merge pull request #300 from grafana/feat/validate-lifecycleevent
Add lifecycle event validation to `WaitForLoadState`
2 parents 065ba5c + a526dd9 commit 241e402

File tree

6 files changed

+262
-19
lines changed

6 files changed

+262
-19
lines changed

common/frame.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1579,11 +1579,10 @@ func (f *Frame) WaitForLoadState(state string, opts goja.Value) {
15791579
}
15801580

15811581
waitUntil := LifecycleEventLoad
1582-
switch state {
1583-
case "domcontentloaded":
1584-
waitUntil = LifecycleEventDOMContentLoad
1585-
case "networkidle":
1586-
waitUntil = LifecycleEventNetworkIdle
1582+
if state != "" {
1583+
if err = waitUntil.UnmarshalText([]byte(state)); err != nil {
1584+
k6Throw(f.ctx, "waitForLoadState error: %v", err)
1585+
}
15871586
}
15881587

15891588
if f.hasLifecycleEventFired(waitUntil) {

common/frame_options.go

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -297,10 +297,8 @@ func (o *FrameGotoOptions) Parse(ctx context.Context, opts goja.Value) error {
297297
o.Timeout = time.Duration(opts.Get(k).ToInteger()) * time.Millisecond
298298
case "waitUntil":
299299
lifeCycle := opts.Get(k).String()
300-
if l, ok := lifecycleEventToID[lifeCycle]; ok {
301-
o.WaitUntil = l
302-
} else {
303-
return fmt.Errorf("%q is not a valid lifecycle", lifeCycle)
300+
if err := o.WaitUntil.UnmarshalText([]byte(lifeCycle)); err != nil {
301+
return fmt.Errorf("error parsing goto options: %w", err)
304302
}
305303
}
306304
}
@@ -504,10 +502,8 @@ func (o *FrameSetContentOptions) Parse(ctx context.Context, opts goja.Value) err
504502
o.Timeout = time.Duration(opts.Get(k).ToInteger()) * time.Millisecond
505503
case "waitUntil":
506504
lifeCycle := opts.Get(k).String()
507-
if l, ok := lifecycleEventToID[lifeCycle]; ok {
508-
o.WaitUntil = l
509-
} else {
510-
return fmt.Errorf("%q is not a valid lifecycle", lifeCycle)
505+
if err := o.WaitUntil.UnmarshalText([]byte(lifeCycle)); err != nil {
506+
return fmt.Errorf("error parsing setContent options: %w", err)
511507
}
512508
}
513509
}
@@ -678,10 +674,8 @@ func (o *FrameWaitForNavigationOptions) Parse(ctx context.Context, opts goja.Val
678674
o.Timeout = time.Duration(opts.Get(k).ToInteger()) * time.Millisecond
679675
case "waitUntil":
680676
lifeCycle := opts.Get(k).String()
681-
if l, ok := lifecycleEventToID[lifeCycle]; ok {
682-
o.WaitUntil = l
683-
} else {
684-
return fmt.Errorf("%q is not a valid lifecycle", lifeCycle)
677+
if err := o.WaitUntil.UnmarshalText([]byte(lifeCycle)); err != nil {
678+
return fmt.Errorf("error parsing waitForNavigation options: %w", err)
685679
}
686680
}
687681
}

common/frame_options_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package common
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestFrameGotoOptionsParse(t *testing.T) {
12+
t.Parallel()
13+
14+
t.Run("ok", func(t *testing.T) {
15+
t.Parallel()
16+
17+
mockVU := newMockVU(t)
18+
opts := mockVU.RuntimeField.ToValue(map[string]interface{}{
19+
"timeout": "1000",
20+
"waitUntil": "networkidle",
21+
})
22+
gotoOpts := NewFrameGotoOptions("https://example.com/", 0)
23+
err := gotoOpts.Parse(mockVU.CtxField, opts)
24+
require.NoError(t, err)
25+
26+
assert.Equal(t, "https://example.com/", gotoOpts.Referer)
27+
assert.Equal(t, time.Second, gotoOpts.Timeout)
28+
assert.Equal(t, LifecycleEventNetworkIdle, gotoOpts.WaitUntil)
29+
})
30+
31+
t.Run("err/invalid_waitUntil", func(t *testing.T) {
32+
t.Parallel()
33+
34+
mockVU := newMockVU(t)
35+
opts := mockVU.RuntimeField.ToValue(map[string]interface{}{
36+
"waitUntil": "none",
37+
})
38+
navOpts := NewFrameGotoOptions("", 0)
39+
err := navOpts.Parse(mockVU.CtxField, opts)
40+
41+
assert.EqualError(t, err,
42+
`error parsing goto options: `+
43+
`invalid lifecycle event: "none"; must be one of: `+
44+
`load, domcontentloaded, networkidle`)
45+
})
46+
}
47+
48+
func TestFrameSetContentOptionsParse(t *testing.T) {
49+
t.Parallel()
50+
51+
t.Run("ok", func(t *testing.T) {
52+
t.Parallel()
53+
54+
mockVU := newMockVU(t)
55+
opts := mockVU.RuntimeField.ToValue(map[string]interface{}{
56+
"waitUntil": "networkidle",
57+
})
58+
scOpts := NewFrameSetContentOptions(30 * time.Second)
59+
err := scOpts.Parse(mockVU.CtxField, opts)
60+
require.NoError(t, err)
61+
62+
assert.Equal(t, 30*time.Second, scOpts.Timeout)
63+
assert.Equal(t, LifecycleEventNetworkIdle, scOpts.WaitUntil)
64+
})
65+
66+
t.Run("err/invalid_waitUntil", func(t *testing.T) {
67+
t.Parallel()
68+
69+
mockVU := newMockVU(t)
70+
opts := mockVU.RuntimeField.ToValue(map[string]interface{}{
71+
"waitUntil": "none",
72+
})
73+
navOpts := NewFrameSetContentOptions(0)
74+
err := navOpts.Parse(mockVU.CtxField, opts)
75+
76+
assert.EqualError(t, err,
77+
`error parsing setContent options: `+
78+
`invalid lifecycle event: "none"; must be one of: `+
79+
`load, domcontentloaded, networkidle`)
80+
})
81+
}
82+
83+
func TestFrameWaitForNavigationOptionsParse(t *testing.T) {
84+
t.Parallel()
85+
86+
t.Run("ok", func(t *testing.T) {
87+
t.Parallel()
88+
89+
mockVU := newMockVU(t)
90+
opts := mockVU.RuntimeField.ToValue(map[string]interface{}{
91+
"url": "https://example.com/",
92+
"timeout": "1000",
93+
"waitUntil": "networkidle",
94+
})
95+
navOpts := NewFrameWaitForNavigationOptions(0)
96+
err := navOpts.Parse(mockVU.CtxField, opts)
97+
require.NoError(t, err)
98+
99+
assert.Equal(t, "https://example.com/", navOpts.URL)
100+
assert.Equal(t, time.Second, navOpts.Timeout)
101+
assert.Equal(t, LifecycleEventNetworkIdle, navOpts.WaitUntil)
102+
})
103+
104+
t.Run("err/invalid_waitUntil", func(t *testing.T) {
105+
t.Parallel()
106+
107+
mockVU := newMockVU(t)
108+
opts := mockVU.RuntimeField.ToValue(map[string]interface{}{
109+
"waitUntil": "none",
110+
})
111+
navOpts := NewFrameWaitForNavigationOptions(0)
112+
err := navOpts.Parse(mockVU.CtxField, opts)
113+
114+
assert.EqualError(t, err,
115+
`error parsing waitForNavigation options: `+
116+
`invalid lifecycle event: "none"; must be one of: `+
117+
`load, domcontentloaded, networkidle`)
118+
})
119+
}

common/types.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
"encoding/json"
2727
"fmt"
2828
"math"
29+
"sort"
30+
"strings"
2931

3032
"github.com/dop251/goja"
3133

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

283+
// MarshalText returns the string representation of the enum value.
284+
// It returns an error if the enum value is invalid.
285+
func (l *LifecycleEvent) MarshalText() ([]byte, error) {
286+
if l == nil {
287+
return []byte(""), nil
288+
}
289+
var (
290+
ok bool
291+
s string
292+
)
293+
if s, ok = lifecycleEventToString[*l]; !ok {
294+
return nil, fmt.Errorf("invalid lifecycle event: %v", int(*l))
295+
}
296+
297+
return []byte(s), nil
298+
}
299+
300+
// UnmarshalText unmarshals a text representation to the enum value.
301+
// It returns an error if given a wrong value.
302+
func (l *LifecycleEvent) UnmarshalText(text []byte) error {
303+
var (
304+
ok bool
305+
val = string(text)
306+
)
307+
308+
if *l, ok = lifecycleEventToID[val]; !ok {
309+
valid := make([]string, 0, len(lifecycleEventToID))
310+
for k := range lifecycleEventToID {
311+
valid = append(valid, k)
312+
}
313+
sort.Slice(valid, func(i, j int) bool {
314+
return lifecycleEventToID[valid[j]] > lifecycleEventToID[valid[i]]
315+
})
316+
return fmt.Errorf(
317+
"invalid lifecycle event: %q; must be one of: %s",
318+
val, strings.Join(valid, ", "))
319+
}
320+
321+
return nil
322+
}
323+
281324
type MediaType string
282325

283326
const (

common/types_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"testing"
55

66
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
78
)
89

910
// See: Issue #183 for details.
@@ -60,3 +61,71 @@ func TestViewportCalculateInset(t *testing.T) {
6061
})
6162
}
6263
}
64+
65+
func TestLifecycleEventMarshalText(t *testing.T) {
66+
t.Parallel()
67+
68+
t.Run("ok/nil", func(t *testing.T) {
69+
t.Parallel()
70+
71+
var evt *LifecycleEvent
72+
m, err := evt.MarshalText()
73+
require.NoError(t, err)
74+
assert.Equal(t, []byte(""), m)
75+
})
76+
77+
t.Run("err/invalid", func(t *testing.T) {
78+
t.Parallel()
79+
80+
evt := LifecycleEvent(-1)
81+
_, err := evt.MarshalText()
82+
require.EqualError(t, err, "invalid lifecycle event: -1")
83+
})
84+
}
85+
86+
func TestLifecycleEventMarshalTextRound(t *testing.T) {
87+
t.Parallel()
88+
89+
evt := LifecycleEventLoad
90+
m, err := evt.MarshalText()
91+
require.NoError(t, err)
92+
assert.Equal(t, []byte("load"), m)
93+
94+
var evt2 LifecycleEvent
95+
err = evt2.UnmarshalText(m)
96+
require.NoError(t, err)
97+
assert.Equal(t, evt, evt2)
98+
}
99+
100+
func TestLifecycleEventUnmarshalText(t *testing.T) {
101+
t.Parallel()
102+
103+
t.Run("ok", func(t *testing.T) {
104+
t.Parallel()
105+
106+
var evt LifecycleEvent
107+
err := evt.UnmarshalText([]byte("load"))
108+
require.NoError(t, err)
109+
assert.Equal(t, LifecycleEventLoad, evt)
110+
})
111+
112+
t.Run("err/invalid", func(t *testing.T) {
113+
t.Parallel()
114+
115+
var evt LifecycleEvent
116+
err := evt.UnmarshalText([]byte("none"))
117+
require.EqualError(t, err,
118+
`invalid lifecycle event: "none"; `+
119+
`must be one of: load, domcontentloaded, networkidle`)
120+
})
121+
122+
t.Run("err/invalid_empty", func(t *testing.T) {
123+
t.Parallel()
124+
125+
var evt LifecycleEvent
126+
err := evt.UnmarshalText([]byte(""))
127+
require.EqualError(t, err,
128+
`invalid lifecycle event: ""; `+
129+
`must be one of: load, domcontentloaded, networkidle`)
130+
})
131+
}

tests/page_test.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,26 @@ func TestPageWaitForFunction(t *testing.T) {
633633
})
634634
}
635635

636+
func TestPageWaitForLoadState(t *testing.T) {
637+
t.Parallel()
638+
639+
// TODO: Add happy path tests once WaitForLoadState is not racy.
640+
641+
t.Run("err_wrong_event", func(t *testing.T) {
642+
t.Parallel()
643+
644+
defer func() {
645+
assertPanicErrorContains(t, recover(),
646+
`invalid lifecycle event: "none"; `+
647+
`must be one of: load, domcontentloaded, networkidle`)
648+
}()
649+
650+
p := newTestBrowser(t).NewPage(nil)
651+
p.WaitForLoadState("none", nil)
652+
t.Error("did not panic")
653+
})
654+
}
655+
636656
// See: The issue #187 for details.
637657
func TestPageWaitForNavigationShouldNotPanic(t *testing.T) {
638658
ctx, cancel := context.WithCancel(context.Background())
@@ -649,8 +669,7 @@ func assertPanicErrorContains(t *testing.T, err interface{}, expErrMsg string) {
649669
require.IsType(t, &goja.Object{}, err)
650670
gotObj, _ := err.(*goja.Object)
651671
got := gotObj.Export()
652-
expErr := fmt.Errorf("%w", errors.New(expErrMsg))
653-
require.IsType(t, expErr, got)
672+
expErr := errors.New(expErrMsg)
654673
gotErr, ok := got.(error)
655674
require.True(t, ok)
656675
assert.Contains(t, gotErr.Error(), expErr.Error())

0 commit comments

Comments
 (0)