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

Commit e05b8d6

Browse files
committed
Fix waitUntil option for Reload
The logic to waitUntil has been copied from FrameManager.NavigateFrame. Fixes: #622
1 parent 52264a4 commit e05b8d6

File tree

5 files changed

+236
-23
lines changed

5 files changed

+236
-23
lines changed

common/page.go

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -687,34 +687,54 @@ func (p *Page) Reload(opts goja.Value) api.Response {
687687
k6ext.Panic(p.ctx, "parsing reload options: %w", err)
688688
}
689689

690-
ch, evCancelFn := createWaitForEventHandler(p.ctx,
691-
p.frameManager.MainFrame(),
692-
[]string{EventFrameNavigation}, func(data any) bool {
690+
timeoutCtx, timeoutCancelFn := context.WithTimeout(p.ctx, parsedOpts.Timeout)
691+
defer timeoutCancelFn()
692+
693+
ch, evCancelFn := createWaitForEventHandler(
694+
timeoutCtx, p.frameManager.MainFrame(), []string{EventFrameNavigation},
695+
func(data any) bool {
693696
return true // Both successful and failed navigations are considered
694697
},
695698
)
696699
defer evCancelFn() // Remove event handler
697700

701+
lifecycleEvtCh, lifecycleEvtCancel := createWaitForEventPredicateHandler(
702+
timeoutCtx, p.frameManager.MainFrame(), []string{EventFrameAddLifecycle},
703+
func(data any) bool {
704+
if le, ok := data.(LifecycleEvent); ok {
705+
return le == parsedOpts.WaitUntil
706+
}
707+
return false
708+
})
709+
defer lifecycleEvtCancel()
710+
698711
action := cdppage.Reload()
699712
if err := action.Do(cdp.WithExecutor(p.ctx, p.session)); err != nil {
700713
k6ext.Panic(p.ctx, "reloading page: %w", err)
701714
}
702715

716+
wrapTimeoutError := func(err error) error {
717+
if errors.Is(err, context.DeadlineExceeded) {
718+
err = &k6ext.UserFriendlyError{
719+
Err: err,
720+
Timeout: parsedOpts.Timeout,
721+
}
722+
return fmt.Errorf("reloading page: %w", err)
723+
}
724+
p.logger.Debugf("Page:Reload", "timeoutCtx done: %v", err)
725+
726+
return err // TODO maybe wrap this as well?
727+
}
728+
703729
var event *NavigationEvent
704730
select {
705731
case <-p.ctx.Done():
706-
case <-time.After(parsedOpts.Timeout):
707-
k6ext.Panic(p.ctx, "%w", ErrTimedOut)
732+
case <-timeoutCtx.Done():
733+
k6ext.Panic(p.ctx, "%w", wrapTimeoutError(timeoutCtx.Err()))
708734
case data := <-ch:
709735
event = data.(*NavigationEvent)
710736
}
711737

712-
if p.frameManager.mainFrame.hasSubtreeLifecycleEventFired(parsedOpts.WaitUntil) {
713-
_, _ = waitForEvent(p.ctx, p.frameManager.MainFrame(), []string{EventFrameAddLifecycle}, func(data any) bool {
714-
return data.(LifecycleEvent) == parsedOpts.WaitUntil
715-
}, parsedOpts.Timeout)
716-
}
717-
718738
var resp *Response
719739
req := event.newDocument.request
720740
if req != nil {
@@ -723,6 +743,12 @@ func (p *Page) Reload(opts goja.Value) api.Response {
723743
req.responseMu.RUnlock()
724744
}
725745

746+
select {
747+
case <-lifecycleEvtCh:
748+
case <-timeoutCtx.Done():
749+
k6ext.Panic(p.ctx, "%w", wrapTimeoutError(timeoutCtx.Err()))
750+
}
751+
726752
applySlowMo(p.ctx)
727753

728754
return resp

tests/lifecycle_wait_test.go

Lines changed: 165 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,163 @@ import (
77
"testing"
88
"time"
99

10-
"github.com/grafana/xk6-browser/api"
11-
"github.com/grafana/xk6-browser/common"
1210
"github.com/stretchr/testify/assert"
1311
"github.com/stretchr/testify/require"
12+
13+
"github.com/grafana/xk6-browser/api"
14+
"github.com/grafana/xk6-browser/common"
1415
)
1516

17+
func TestLifecycleReloadLoad(t *testing.T) {
18+
t.Parallel()
19+
20+
tb := newTestBrowser(t, withFileServer())
21+
p := tb.NewPage(nil)
22+
tb.withHandler("/home", func(w http.ResponseWriter, r *http.Request) {
23+
http.Redirect(w, r, tb.staticURL("reload_lifecycle.html"), http.StatusMovedPermanently)
24+
})
25+
26+
var counter int64
27+
var counterMu sync.Mutex
28+
tb.withHandler("/ping", func(w http.ResponseWriter, _ *http.Request) {
29+
counterMu.Lock()
30+
defer counterMu.Unlock()
31+
32+
time.Sleep(time.Millisecond * 100)
33+
34+
counter++
35+
fmt.Fprintf(w, "pong %d", counter)
36+
})
37+
38+
tb.withHandler("/ping.js", func(w http.ResponseWriter, _ *http.Request) {
39+
fmt.Fprintf(w, `
40+
var pingJSTextOutput = document.getElementById("pingJSText");
41+
pingJSTextOutput.innerText = "ping.js loaded from server";
42+
`)
43+
})
44+
45+
waitUntil := common.LifecycleEventLoad
46+
assertHome(t, tb, p, waitUntil, func() {
47+
result := p.TextContent("#pingRequestText", nil)
48+
assert.NotEqualValues(t, "Waiting... pong 10 - for loop complete", result)
49+
50+
result = p.TextContent("#pingJSText", nil)
51+
assert.EqualValues(t, "ping.js loaded from server", result)
52+
53+
opts := tb.toGojaValue(common.PageReloadOptions{
54+
WaitUntil: waitUntil,
55+
Timeout: 30 * time.Second,
56+
})
57+
p.Reload(opts)
58+
59+
result = p.TextContent("#pingRequestText", nil)
60+
assert.NotEqualValues(t, "Waiting... pong 20 - for loop complete", result)
61+
62+
result = p.TextContent("#pingJSText", nil)
63+
assert.EqualValues(t, "ping.js loaded from server", result)
64+
})
65+
}
66+
67+
func TestLifecycleReloadDOMContentLoaded(t *testing.T) {
68+
t.Parallel()
69+
70+
tb := newTestBrowser(t, withFileServer())
71+
p := tb.NewPage(nil)
72+
tb.withHandler("/home", func(w http.ResponseWriter, r *http.Request) {
73+
http.Redirect(w, r, tb.staticURL("reload_lifecycle.html"), http.StatusMovedPermanently)
74+
})
75+
76+
var counter int64
77+
var counterMu sync.Mutex
78+
tb.withHandler("/ping", func(w http.ResponseWriter, _ *http.Request) {
79+
counterMu.Lock()
80+
defer counterMu.Unlock()
81+
82+
time.Sleep(time.Millisecond * 100)
83+
84+
counter++
85+
fmt.Fprintf(w, "pong %d", counter)
86+
})
87+
88+
tb.withHandler("/ping.js", func(w http.ResponseWriter, _ *http.Request) {
89+
fmt.Fprintf(w, `
90+
await new Promise(resolve => setTimeout(resolve, 1000));
91+
92+
var pingJSTextOutput = document.getElementById("pingJSText");
93+
pingJSTextOutput.innerText = "ping.js loaded from server";
94+
`)
95+
})
96+
97+
waitUntil := common.LifecycleEventDOMContentLoad
98+
assertHome(t, tb, p, waitUntil, func() {
99+
result := p.TextContent("#pingRequestText", nil)
100+
assert.NotEqualValues(t, "Waiting... pong 10 - for loop complete", result)
101+
102+
result = p.TextContent("#pingJSText", nil)
103+
assert.EqualValues(t, "Waiting...", result)
104+
105+
opts := tb.toGojaValue(common.PageReloadOptions{
106+
WaitUntil: waitUntil,
107+
Timeout: 30 * time.Second,
108+
})
109+
p.Reload(opts)
110+
111+
result = p.TextContent("#pingRequestText", nil)
112+
assert.NotEqualValues(t, "Waiting... pong 20 - for loop complete", result)
113+
114+
result = p.TextContent("#pingJSText", nil)
115+
assert.EqualValues(t, "Waiting...", result)
116+
})
117+
}
118+
119+
func TestLifecycleReloadNetworkIdle(t *testing.T) {
120+
t.Parallel()
121+
122+
tb := newTestBrowser(t, withFileServer())
123+
p := tb.NewPage(nil)
124+
tb.withHandler("/home", func(w http.ResponseWriter, r *http.Request) {
125+
http.Redirect(w, r, tb.staticURL("reload_lifecycle.html"), http.StatusMovedPermanently)
126+
})
127+
128+
var counter int64
129+
var counterMu sync.Mutex
130+
tb.withHandler("/ping", func(w http.ResponseWriter, _ *http.Request) {
131+
counterMu.Lock()
132+
defer counterMu.Unlock()
133+
134+
counter++
135+
fmt.Fprintf(w, "pong %d", counter)
136+
})
137+
138+
tb.withHandler("/ping.js", func(w http.ResponseWriter, _ *http.Request) {
139+
fmt.Fprintf(w, `
140+
var pingJSTextOutput = document.getElementById("pingJSText");
141+
pingJSTextOutput.innerText = "ping.js loaded from server";
142+
`)
143+
})
144+
145+
waitUntil := common.LifecycleEventNetworkIdle
146+
assertHome(t, tb, p, waitUntil, func() {
147+
result := p.TextContent("#pingRequestText", nil)
148+
assert.EqualValues(t, "Waiting... pong 10 - for loop complete", result)
149+
150+
result = p.TextContent("#pingJSText", nil)
151+
assert.EqualValues(t, "ping.js loaded from server", result)
152+
153+
opts := tb.toGojaValue(common.PageReloadOptions{
154+
WaitUntil: waitUntil,
155+
Timeout: 30 * time.Second,
156+
})
157+
p.Reload(opts)
158+
159+
result = p.TextContent("#pingRequestText", nil)
160+
assert.EqualValues(t, "Waiting... pong 20 - for loop complete", result)
161+
162+
result = p.TextContent("#pingJSText", nil)
163+
assert.EqualValues(t, "ping.js loaded from server", result)
164+
})
165+
}
166+
16167
func TestLifecycleNetworkIdle(t *testing.T) {
17168
t.Parallel()
18169

@@ -40,7 +191,7 @@ func TestLifecycleNetworkIdle(t *testing.T) {
40191
`)
41192
})
42193

43-
assertHome(t, tb, p, "/home", common.LifecycleEventNetworkIdle, func() {
194+
assertHome(t, tb, p, common.LifecycleEventNetworkIdle, func() {
44195
result := p.TextContent("#pingJSText", nil)
45196
assert.EqualValues(t, "ping.js loaded from server", result)
46197
})
@@ -57,10 +208,10 @@ func TestLifecycleNetworkIdle(t *testing.T) {
57208

58209
var counter int64
59210
ch := make(chan bool)
211+
var counterMu sync.Mutex
60212
tb.withHandler("/ping", func(w http.ResponseWriter, _ *http.Request) {
61213
<-ch
62214

63-
var counterMu sync.Mutex
64215
counterMu.Lock()
65216
defer counterMu.Unlock()
66217

@@ -78,7 +229,7 @@ func TestLifecycleNetworkIdle(t *testing.T) {
78229
close(ch)
79230
})
80231

81-
assertHome(t, tb, p, "/home", common.LifecycleEventNetworkIdle, func() {
232+
assertHome(t, tb, p, common.LifecycleEventNetworkIdle, func() {
82233
result := p.TextContent("#pingRequestText", nil)
83234
assert.EqualValues(t, "Waiting... pong 4 - for loop complete", result)
84235

@@ -108,14 +259,20 @@ func TestLifecycleNetworkIdle(t *testing.T) {
108259
fmt.Fprintf(w, "pong %d", counter)
109260
})
110261

111-
assertHome(t, tb, p, "/home", common.LifecycleEventNetworkIdle, func() {
262+
assertHome(t, tb, p, common.LifecycleEventNetworkIdle, func() {
112263
result := p.TextContent("#pingRequestText", nil)
113264
assert.EqualValues(t, "Waiting... pong 10 - for loop complete", result)
114265
})
115266
})
116267
}
117268

118-
func assertHome(t *testing.T, tb *testBrowser, p api.Page, gotoURL string, waitUntil common.LifecycleEvent, check func()) {
269+
func assertHome(
270+
t *testing.T,
271+
tb *testBrowser,
272+
p api.Page,
273+
waitUntil common.LifecycleEvent,
274+
check func(),
275+
) {
119276
t.Helper()
120277

121278
var resolved, rejected bool
@@ -124,7 +281,7 @@ func assertHome(t *testing.T, tb *testBrowser, p api.Page, gotoURL string, waitU
124281
WaitUntil: waitUntil,
125282
Timeout: 30 * time.Second,
126283
})
127-
tb.promise(p.Goto(tb.URL(gotoURL), opts)).then(
284+
tb.promise(p.Goto(tb.URL("/home"), opts)).then(
128285
func() {
129286
check()
130287
resolved = true

tests/static/prolonged_network_idle.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
<script>
1010
var pingRequestTextOutput = document.getElementById("pingRequestText");
1111

12-
var p = pingRequestText();
12+
var p = requestPings();
1313
p.then(() => {
1414
pingRequestTextOutput.innerText += ' - for loop complete';
1515
})
1616

17-
async function pingRequestText() {
17+
async function requestPings() {
1818
for (var i = 0; i < 4; i++) {
1919
await fetch('/ping')
2020
.then(response => response.text())

tests/static/prolonged_network_idle_10.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
<script>
99
var pingRequestTextOutput = document.getElementById("pingRequestText");
1010

11-
var p = pingRequestText();
11+
var p = requestPings();
1212
p.then(() => {
1313
pingRequestTextOutput.innerText += ' - for loop complete';
1414
})
1515

16-
async function pingRequestText() {
16+
async function requestPings() {
1717
for (var i = 0; i < 10; i++) {
1818
await fetch('/ping')
1919
.then(response => response.text())

tests/static/reload_lifecycle.html

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<html>
2+
3+
<head></head>
4+
5+
<body>
6+
<div id="pingRequestText">Waiting...</div>
7+
<div id="pingJSText">Waiting...</div>
8+
9+
<script>
10+
var pingRequestTextOutput = document.getElementById("pingRequestText");
11+
12+
var p = requestPings();
13+
p.then(() => {
14+
pingRequestTextOutput.innerText += ' - for loop complete';
15+
})
16+
17+
async function requestPings() {
18+
for (var i = 0; i < 10; i++) {
19+
await fetch('/ping')
20+
.then(response => response.text())
21+
.then((data) => {
22+
pingRequestTextOutput.innerText = 'Waiting... ' + data;
23+
});
24+
}
25+
}
26+
</script>
27+
<script src="/ping.js" async></script>
28+
</body>
29+
30+
</html>

0 commit comments

Comments
 (0)