diff --git a/common/frame.go b/common/frame.go index 8e5f4a1a0..0dc7620f8 100644 --- a/common/frame.go +++ b/common/frame.go @@ -152,11 +152,7 @@ func (f *Frame) clearLifecycle() { // clear lifecycle events f.lifecycleEventsMu.Lock() - { - for e := range f.lifecycleEvents { - f.lifecycleEvents[e] = false - } - } + f.lifecycleEvents = make(map[LifecycleEvent]bool) f.lifecycleEventsMu.Unlock() f.page.frameManager.MainFrame().recalculateLifecycle() @@ -462,12 +458,16 @@ func (f *Frame) onLoadingStarted() { func (f *Frame) onLoadingStopped() { f.log.Debugf("Frame:onLoadingStopped", "fid:%s furl:%q", f.ID(), f.URL()) - f.lifecycleEventsMu.Lock() - defer f.lifecycleEventsMu.Unlock() - - f.lifecycleEvents[LifecycleEventDOMContentLoad] = true - f.lifecycleEvents[LifecycleEventLoad] = true - f.lifecycleEvents[LifecycleEventNetworkIdle] = true + // TODO: We should start a timer here and allow the user + // to set how long to wait until after onLoadingStopped + // has occurred. The reason we may want a timeout here + // are for websites where they perform many network + // requests so it may take a long time for us to see + // a networkIdle event or we may never see one if the + // website never stops performing network requests. + // NOTE: This is a different timeout to networkIdleTimer, + // which only works once there are no more network + // requests and we don't see a networkIdle event. } func (f *Frame) position() *Position { diff --git a/common/frame_options.go b/common/frame_options.go index 9adf76163..db473aefc 100644 --- a/common/frame_options.go +++ b/common/frame_options.go @@ -39,7 +39,7 @@ type FrameFillOptions struct { type FrameGotoOptions struct { Referer string `json:"referer"` Timeout time.Duration `json:"timeout"` - WaitUntil LifecycleEvent `json:"waitUntil"` + WaitUntil LifecycleEvent `json:"waitUntil" js:"waitUntil"` } type FrameHoverOptions struct { @@ -95,7 +95,7 @@ type FrameSelectOptionOptions struct { type FrameSetContentOptions struct { Timeout time.Duration `json:"timeout"` - WaitUntil LifecycleEvent `json:"waitUntil"` + WaitUntil LifecycleEvent `json:"waitUntil" js:"waitUntil"` } type FrameTapOptions struct { @@ -130,7 +130,7 @@ type FrameWaitForLoadStateOptions struct { type FrameWaitForNavigationOptions struct { URL string `json:"url"` - WaitUntil LifecycleEvent `json:"waitUntil"` + WaitUntil LifecycleEvent `json:"waitUntil" js:"waitUntil"` Timeout time.Duration `json:"timeout"` } diff --git a/common/frame_session.go b/common/frame_session.go index cbcccf629..32eac456d 100644 --- a/common/frame_session.go +++ b/common/frame_session.go @@ -475,8 +475,8 @@ func (fs *FrameSession) handleFrameTree(frameTree *cdppage.FrameTree) { func (fs *FrameSession) navigateFrame(frame *Frame, url, referrer string) (string, error) { fs.logger.Debugf("FrameSession:navigateFrame", - "sid:%v tid:%v url:%q referrer:%q", - fs.session.ID(), fs.targetID, url, referrer) + "sid:%v fid:%s tid:%v url:%q referrer:%q", + fs.session.ID(), frame.ID(), fs.targetID, url, referrer) action := cdppage.Navigate(url).WithReferrer(referrer).WithFrameID(cdp.FrameID(frame.ID())) _, documentID, errorText, err := action.Do(cdp.WithExecutor(fs.ctx, fs.session)) @@ -705,6 +705,8 @@ func (fs *FrameSession) onPageLifecycle(event *cdppage.EventLifecycleEvent) { fs.manager.frameLifecycleEvent(event.FrameID, LifecycleEventLoad) case "DOMContentLoaded": fs.manager.frameLifecycleEvent(event.FrameID, LifecycleEventDOMContentLoad) + case "networkIdle": + fs.manager.frameLifecycleEvent(event.FrameID, LifecycleEventNetworkIdle) } eventToMetric := map[string]*k6metrics.Metric{ diff --git a/common/page_options.go b/common/page_options.go index d7d522546..cca1bdfe2 100644 --- a/common/page_options.go +++ b/common/page_options.go @@ -19,7 +19,7 @@ type PageEmulateMediaOptions struct { } type PageReloadOptions struct { - WaitUntil LifecycleEvent `json:"waitUntil"` + WaitUntil LifecycleEvent `json:"waitUntil" js:"waitUntil"` Timeout time.Duration `json:"timeout"` } diff --git a/tests/frame_test.go b/tests/frame_test.go index 7741feaf7..ce123ab82 100644 --- a/tests/frame_test.go +++ b/tests/frame_test.go @@ -1,9 +1,17 @@ package tests import ( + "fmt" + "net/http" + "sync" "testing" + "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/grafana/xk6-browser/api" + "github.com/grafana/xk6-browser/common" ) func TestFramePress(t *testing.T) { @@ -21,3 +29,130 @@ func TestFramePress(t *testing.T) { require.Equal(t, "AbC", f.InputValue("#text1", nil)) } + +func TestLifecycleNetworkIdle(t *testing.T) { + t.Parallel() + + assertHome := func(tb *testBrowser, p api.Page, check func()) { + var resolved, rejected bool + err := tb.await(func() error { + opts := tb.toGojaValue(common.FrameGotoOptions{ + WaitUntil: common.LifecycleEventNetworkIdle, + Timeout: 30 * time.Second, + }) + tb.promise(p.Goto(tb.URL("/home"), opts)).then( + func() { + check() + resolved = true + }, + func() { + rejected = true + }, + ) + + return nil + }) + require.NoError(t, err) + + assert.True(t, resolved) + assert.False(t, rejected) + } + + t.Run("doesn't timeout waiting for networkIdle", func(t *testing.T) { + t.Parallel() + + tb := newTestBrowser(t, withHTTPServer()) + p := tb.NewPage(nil) + tb.withHandler("/home", func(w http.ResponseWriter, _ *http.Request) { + fmt.Fprintf(w, ` + + + +
Waiting...
+ + + + `) + }) + + tb.withHandler("/ping.js", func(w http.ResponseWriter, _ *http.Request) { + fmt.Fprintf(w, ` + var serverMsgOutput = document.getElementById("serverMsg"); + serverMsgOutput.innerText = "ping.js loaded from server"; + `) + }) + + assertHome(tb, p, func() { + result := p.TextContent("#serverMsg", nil) + assert.EqualValues(t, "ping.js loaded from server", result) + }) + }) + + t.Run("doesn't unblock wait for networkIdle too early", func(t *testing.T) { + t.Parallel() + + tb := newTestBrowser(t, withFileServer()) + p := tb.NewPage(nil) + tb.withHandler("/home", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, tb.staticURL("prolonged_network_idle.html"), http.StatusMovedPermanently) + }) + + var counter int64 + ch := make(chan bool) + tb.withHandler("/ping", func(w http.ResponseWriter, _ *http.Request) { + <-ch + + var counterMu sync.Mutex + counterMu.Lock() + defer counterMu.Unlock() + + time.Sleep(time.Millisecond * 50) + + counter++ + fmt.Fprintf(w, "pong %d", counter) + }) + + tb.withHandler("/ping.js", func(w http.ResponseWriter, _ *http.Request) { + fmt.Fprintf(w, ` + var serverMsgOutput = document.getElementById("serverMsg"); + serverMsgOutput.innerText = "ping.js loaded from server"; + `) + close(ch) + }) + + assertHome(tb, p, func() { + result := p.TextContent("#prolongNetworkIdleLoad", nil) + assert.EqualValues(t, "Waiting... pong 4 - for loop complete", result) + + result = p.TextContent("#serverMsg", nil) + assert.EqualValues(t, "ping.js loaded from server", result) + }) + }) + + t.Run("doesn't unblock wait on networkIdle early when load and domcontentloaded complete at once", func(t *testing.T) { + t.Parallel() + + tb := newTestBrowser(t, withFileServer()) + p := tb.NewPage(nil) + tb.withHandler("/home", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, tb.staticURL("prolonged_network_idle_10.html"), http.StatusMovedPermanently) + }) + + var counterMu sync.Mutex + var counter int64 + tb.withHandler("/ping", func(w http.ResponseWriter, _ *http.Request) { + counterMu.Lock() + defer counterMu.Unlock() + + time.Sleep(time.Millisecond * 50) + + counter++ + fmt.Fprintf(w, "pong %d", counter) + }) + + assertHome(tb, p, func() { + result := p.TextContent("#prolongNetworkIdleLoad", nil) + assert.EqualValues(t, "Waiting... pong 10 - for loop complete", result) + }) + }) +} diff --git a/tests/static/prolonged_network_idle.html b/tests/static/prolonged_network_idle.html new file mode 100644 index 000000000..9e8046105 --- /dev/null +++ b/tests/static/prolonged_network_idle.html @@ -0,0 +1,30 @@ + + + + + +
Waiting...
+
Waiting...
+ + + + + + \ No newline at end of file diff --git a/tests/static/prolonged_network_idle_10.html b/tests/static/prolonged_network_idle_10.html new file mode 100644 index 000000000..1006ee750 --- /dev/null +++ b/tests/static/prolonged_network_idle_10.html @@ -0,0 +1,28 @@ + + + + + +
Waiting...
+ + + + + \ No newline at end of file