diff --git a/tests/lifecycle_wait_test.go b/tests/lifecycle_wait_test.go index d6fb0632f..e685a3da8 100644 --- a/tests/lifecycle_wait_test.go +++ b/tests/lifecycle_wait_test.go @@ -14,366 +14,541 @@ import ( "github.com/grafana/xk6-browser/common" ) -func TestLifecycleWaitForLoadStateLoad(t *testing.T) { +// General guidelines on lifecycle events: +// +// load +// +// The load event is fired when the initial HTML document has been completely +// loaded. It does not wait for the other network requests to complete. +// +// Emitted by the browser once: +// 1. The HTML is loaded. +// 2. The async scripts have loaded. +// +// domcontentloaded +// +// The DOMContentLoaded event is fired when the initial HTML document has been +// completely loaded and parsed. It does not wait for the async scripts or the +// other network requests to complete. +// +// Emitted by the browser once: +// 1. The HTML is loaded. +// +// networkidle +// +// The networkidle event is fired when there are no network connections for at +// least 500ms. +// +// Emitted by the browser once: +// 1. The HTML is loaded. +// 2. The async scripts have loaded. +// 3. All other network requests have completed. + +func TestLifecycleWaitForNavigation(t *testing.T) { // Test description // - // 1. goto /home and wait for the load lifecycle event. - // 2. use WaitForLoadState with load to ensure that load - // lifecycle event has already fired. + // Steps: + // 1. goto /home and wait for the specified lifecycle event. + // 2. click on a link that navigates to a page, and wait on + // the specified lifecycle event. // - // Success criteria: We don't wait for all network requests to - // complete, but we are interested in waiting - // for all async scripts to have fully loaded - // (which is when load is fired). We also want - // to ensure that the load event is stored - // internally, and we don't block on WaitForLoadState. + // Success criteria: + // The click will perform a navigation away from the current page, + // it should wait for the specified lifecycle event and the result + // of the page should match the original nav. 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("wait_for_nav_lifecycle.html"), http.StatusMovedPermanently) - }) - - var counter int64 - var counterMu sync.Mutex - tb.withHandler("/ping", func(w http.ResponseWriter, _ *http.Request) { - counterMu.Lock() - defer counterMu.Unlock() - - time.Sleep(time.Millisecond * 100) - - counter++ - fmt.Fprintf(w, "pong %d", counter) - }) - - tb.withHandler("/ping.js", func(w http.ResponseWriter, _ *http.Request) { - fmt.Fprintf(w, ` - var pingJSTextOutput = document.getElementById("pingJSText"); - pingJSTextOutput.innerText = "ping.js loaded from server"; - `) - }) - - waitUntil := common.LifecycleEventLoad - assertHome(t, tb, p, waitUntil, func() { - result := p.TextContent("#pingRequestText", nil) - assert.NotEqualValues(t, "Waiting... pong 10 - for loop complete", result) - - result = p.TextContent("#pingJSText", nil) - assert.EqualValues(t, "ping.js loaded from server", result) - - // This shouldn't block and return after calling hasLifecycleEventFired. - p.WaitForLoadState(waitUntil.String(), nil) - }) + tests := []struct { + name string + pingSlowness time.Duration + pingJSSlow bool + waitUntil common.LifecycleEvent + pingRequestTextAssert func(result string, pingCount int) + pingJSTextAssert func(result string) + assertFunc func(tb *testBrowser, p api.Page) testPromise + wantError string + }{ + { + name: "load", + pingSlowness: 100 * time.Millisecond, + pingJSSlow: false, + waitUntil: common.LifecycleEventLoad, + pingRequestTextAssert: func(result string, pingCount int) { + assert.NotEqualValues(t, fmt.Sprintf("Waiting... pong %d - for loop complete", pingCount), result) + }, + pingJSTextAssert: func(result string) { + assert.EqualValues(t, "ping.js loaded from server", result) + }, + }, + { + name: "domcontentloaded", + pingSlowness: 100 * time.Millisecond, + pingJSSlow: true, + waitUntil: common.LifecycleEventDOMContentLoad, + pingRequestTextAssert: func(result string, pingCount int) { + assert.NotEqualValues(t, fmt.Sprintf("Waiting... pong %d - for loop complete", pingCount), result) + }, + pingJSTextAssert: func(result string) { + assert.EqualValues(t, "Waiting...", result) + }, + }, + { + name: "networkidle", + pingSlowness: 0, + pingJSSlow: false, + waitUntil: common.LifecycleEventNetworkIdle, + pingRequestTextAssert: func(result string, pingCount int) { + assert.EqualValues(t, fmt.Sprintf("Waiting... pong %d - for loop complete", pingCount), result) + }, + pingJSTextAssert: func(result string) { + assert.EqualValues(t, "ping.js loaded from server", result) + }, + }, + { + // Test description + // + // Steps: + // 1. goto /home and wait for the specified lifecycle event. + // 2. call WaitForNavigation without clicking on the link. + // the specified lifecycle event. + // + // Success criteria: + // We want this test to timeout since the navigation has + // completed, a new one hasn't started but we "accidentally" + // call WaitForNavigation. + name: "timeout", + pingSlowness: 0, + pingJSSlow: false, + waitUntil: common.LifecycleEventNetworkIdle, + assertFunc: func(tb *testBrowser, p api.Page) testPromise { + result := p.TextContent("#pingRequestText", nil) + assert.EqualValues(t, "Waiting... pong 10 - for loop complete", result) + + waitForNav := p.WaitForNavigation(tb.toGojaValue(&common.FrameWaitForNavigationOptions{ + Timeout: 1000, + WaitUntil: common.LifecycleEventNetworkIdle, + })) + + return tb.promise(waitForNav) + }, + wantError: "Uncaught (in promise) waiting for navigation: timed out after 1s", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + tb := newTestBrowser(t, withFileServer()) + p := tb.NewPage(nil) + + withHomeHandler(t, tb, "lifecycle.html?pingCount=10") + withPingHandler(t, tb, tt.pingSlowness, nil) + withPingJSHandler(t, tb, tt.pingJSSlow, nil, false) + + if tt.assertFunc != nil { + assertHome(t, tb, p, tt.waitUntil, func() testPromise { + return tt.assertFunc(tb, p) + }, nil, tt.wantError) + return + } + + assertHome(t, tb, p, tt.waitUntil, func() testPromise { + result := p.TextContent("#pingRequestText", nil) + tt.pingRequestTextAssert(result, 10) + + result = p.TextContent("#pingJSText", nil) + tt.pingJSTextAssert(result) + + waitForNav := p.WaitForNavigation(tb.toGojaValue(&common.FrameWaitForNavigationOptions{ + Timeout: 30000, + WaitUntil: tt.waitUntil, + })) + click := p.Click("#homeLink", nil) + + return tb.promiseAll(waitForNav, click) + }, func() { + result := p.TextContent("#pingRequestText", nil) + tt.pingRequestTextAssert(result, 20) + + result = p.TextContent("#pingJSText", nil) + tt.pingJSTextAssert(result) + }, "") + }) + } } -func TestLifecycleWaitForLoadStateDOMContentLoaded(t *testing.T) { - // Test description - // - // 1. goto /home and wait for the domcontentloaded lifecycle event. - // 2. use WaitForLoadState with domcontentloaded to ensure that - // domcontentloaded lifecycle event has already fired. - // - // Success criteria: We don't wait for all network requests or the - // async scripts to complete, and we're only - // interested in the html file being loaded. We - // also want to ensure that the domcontentloaded - // event is stored internally, and we don't block - // on WaitForLoadState. - +func TestLifecycleWaitForLoadState(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("wait_for_nav_lifecycle.html"), http.StatusMovedPermanently) - }) - - var counter int64 - var counterMu sync.Mutex - tb.withHandler("/ping", func(w http.ResponseWriter, _ *http.Request) { - counterMu.Lock() - defer counterMu.Unlock() - - time.Sleep(time.Millisecond * 100) - - counter++ - fmt.Fprintf(w, "pong %d", counter) - }) - - tb.withHandler("/ping.js", func(w http.ResponseWriter, _ *http.Request) { - fmt.Fprintf(w, ` - await new Promise(resolve => setTimeout(resolve, 1000)); - - var pingJSTextOutput = document.getElementById("pingJSText"); - pingJSTextOutput.innerText = "ping.js loaded from server"; - `) - }) - - waitUntil := common.LifecycleEventDOMContentLoad - assertHome(t, tb, p, waitUntil, func() { - result := p.TextContent("#pingRequestText", nil) - assert.NotEqualValues(t, "Waiting... pong 10 - for loop complete", result) - - result = p.TextContent("#pingJSText", nil) - assert.EqualValues(t, "Waiting...", result) - - // This shouldn't block and return after calling hasLifecycleEventFired. - p.WaitForLoadState(waitUntil.String(), nil) - }) -} - -func TestLifecycleWaitForLoadStateNetworkIdle(t *testing.T) { // Test description // - // 1. goto /home and wait for the networkidle lifecycle event. - // 2. use WaitForLoadState with networkidle to ensure that - // networkidle lifecycle event has already fired. + // Steps: + // 1. goto /home and wait for the specified lifecycle event. + // 2. use WaitForLoadState with the same specified lifecycle event. // - // Success criteria: We wait for all network requests and async - // scripts to complete. We also want to ensure - // that the networkidle event is stored internally, - // and we don't block on WaitForLoadState. + // Success criteria: + // We want to ensure that the specified event is persisted in + // memory, and we don't block on WaitForLoadState. + + tests := []struct { + name string + pingSlowness time.Duration + pingJSSlow bool + waitUntil common.LifecycleEvent + pingRequestTextAssert func(result string) + pingJSTextAssert func(result string) + assertFunc func(p api.Page) + }{ + { + name: "load", + pingSlowness: 100 * time.Millisecond, + pingJSSlow: false, + waitUntil: common.LifecycleEventLoad, + pingRequestTextAssert: func(result string) { + assert.NotEqualValues(t, "Waiting... pong 10 - for loop complete", result) + }, + pingJSTextAssert: func(result string) { + assert.EqualValues(t, "ping.js loaded from server", result) + }, + }, + { + name: "domcontentloaded", + pingSlowness: 100 * time.Millisecond, + pingJSSlow: true, + waitUntil: common.LifecycleEventDOMContentLoad, + pingRequestTextAssert: func(result string) { + assert.NotEqualValues(t, "Waiting... pong 10 - for loop complete", result) + }, + pingJSTextAssert: func(result string) { + assert.EqualValues(t, "Waiting...", result) + }, + }, + { + name: "networkidle", + pingSlowness: 0, + pingJSSlow: false, + waitUntil: common.LifecycleEventNetworkIdle, + pingRequestTextAssert: func(result string) { + assert.EqualValues(t, "Waiting... pong 10 - for loop complete", result) + }, + pingJSTextAssert: func(result string) { + assert.EqualValues(t, "ping.js loaded from server", result) + }, + }, + { + // Test description + // + // Steps: + // 1. goto /home and wait for the domcontentloaded lifecycle event. + // 2. use WaitForLoadState with networkidle. + // + // Success criteria: + // We want to quickly move to calling WaitForLoadState + // so that we wait until networkidle is received from + // the browser -- not relying on the persisted state in memory. + name: "domcontentloaded then networkidle", + pingSlowness: 100 * time.Millisecond, + pingJSSlow: false, + waitUntil: common.LifecycleEventDOMContentLoad, + assertFunc: func(p api.Page) { + p.WaitForLoadState(common.LifecycleEventNetworkIdle.String(), nil) + + result := p.TextContent("#pingRequestText", nil) + assert.EqualValues(t, "Waiting... pong 10 - for loop complete", result) + + result = p.TextContent("#pingJSText", nil) + assert.EqualValues(t, "ping.js loaded from server", result) + }, + }, + } - t.Parallel() + for _, tt := range tests { + t.Run(tt.name, 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("wait_for_nav_lifecycle.html"), http.StatusMovedPermanently) - }) + tb := newTestBrowser(t, withFileServer()) + p := tb.NewPage(nil) - var counter int64 - var counterMu sync.Mutex - tb.withHandler("/ping", func(w http.ResponseWriter, _ *http.Request) { - counterMu.Lock() - defer counterMu.Unlock() + withHomeHandler(t, tb, "lifecycle.html?pingCount=10") + withPingHandler(t, tb, tt.pingSlowness, nil) + withPingJSHandler(t, tb, tt.pingJSSlow, nil, false) - counter++ - fmt.Fprintf(w, "pong %d", counter) - }) + if tt.assertFunc != nil { + assertHome(t, tb, p, tt.waitUntil, func() testPromise { + tt.assertFunc(p) + return testPromise{} + }, nil, "") + return + } - tb.withHandler("/ping.js", func(w http.ResponseWriter, _ *http.Request) { - fmt.Fprintf(w, ` - var pingJSTextOutput = document.getElementById("pingJSText"); - pingJSTextOutput.innerText = "ping.js loaded from server"; - `) - }) + assertHome(t, tb, p, tt.waitUntil, func() testPromise { + result := p.TextContent("#pingRequestText", nil) + tt.pingRequestTextAssert(result) - waitUntil := common.LifecycleEventNetworkIdle - assertHome(t, tb, p, waitUntil, func() { - result := p.TextContent("#pingRequestText", nil) - assert.EqualValues(t, "Waiting... pong 10 - for loop complete", result) + result = p.TextContent("#pingJSText", nil) + tt.pingJSTextAssert(result) - result = p.TextContent("#pingJSText", nil) - assert.EqualValues(t, "ping.js loaded from server", result) + // This shouldn't block and return after calling hasLifecycleEventFired. + p.WaitForLoadState(tt.waitUntil.String(), nil) - // This shouldn't block and return after calling hasLifecycleEventFired. - p.WaitForLoadState(waitUntil.String(), nil) - }) + return testPromise{} + }, nil, "") + }) + } } -func TestLifecycleWaitForLoadStateDOMContentLoadedThenNetworkIdle(t *testing.T) { +func TestLifecycleReload(t *testing.T) { + t.Parallel() + // Test description // - // 1. goto /home and wait for the domcontentloaded lifecycle event. - // 2. use WaitForLoadState with networkidle to now wait for the - // lifecycle event from the browser. + // Steps: + // 1. goto /home and wait for the specified lifecycle event. + // 2. reload the page and wait for the specified lifecycle event. // - // Success criteria: We want to quickly move to calling WaitForLoadState - // so that we block until a networkidle lifecycle - // event is received from the browser. - - 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("wait_for_nav_lifecycle.html"), http.StatusMovedPermanently) - }) - - var counter int64 - var counterMu sync.Mutex - tb.withHandler("/ping", func(w http.ResponseWriter, _ *http.Request) { - counterMu.Lock() - defer counterMu.Unlock() + // Success criteria: + // The resulting page after reload is the same as + // the initial navigation with goto. + + tests := []struct { + name string + pingSlowness time.Duration + pingJSSlow bool + waitUntil common.LifecycleEvent + pingRequestTextAssert func(result string, pingCount int) + pingJSTextAssert func(result string) + }{ + { + name: "load", + pingSlowness: 100 * time.Millisecond, + pingJSSlow: false, + waitUntil: common.LifecycleEventLoad, + pingRequestTextAssert: func(result string, pingCount int) { + assert.NotEqualValues(t, fmt.Sprintf("Waiting... pong %d - for loop complete", pingCount), result) + }, + pingJSTextAssert: func(result string) { + assert.EqualValues(t, "ping.js loaded from server", result) + }, + }, + { + name: "domcontentloaded", + pingSlowness: 100 * time.Millisecond, + pingJSSlow: true, + waitUntil: common.LifecycleEventDOMContentLoad, + pingRequestTextAssert: func(result string, pingCount int) { + assert.NotEqualValues(t, fmt.Sprintf("Waiting... pong %d - for loop complete", pingCount), result) + }, + pingJSTextAssert: func(result string) { + assert.EqualValues(t, "Waiting...", result) + }, + }, + { + name: "networkidle", + pingSlowness: 0, + pingJSSlow: false, + waitUntil: common.LifecycleEventNetworkIdle, + pingRequestTextAssert: func(result string, pingCount int) { + assert.EqualValues(t, fmt.Sprintf("Waiting... pong %d - for loop complete", pingCount), result) + }, + pingJSTextAssert: func(result string) { + assert.EqualValues(t, "ping.js loaded from server", result) + }, + }, + } - time.Sleep(time.Millisecond * 100) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() - counter++ - fmt.Fprintf(w, "pong %d", counter) - }) + tb := newTestBrowser(t, withFileServer()) + p := tb.NewPage(nil) - tb.withHandler("/ping.js", func(w http.ResponseWriter, _ *http.Request) { - fmt.Fprintf(w, ` - var pingJSTextOutput = document.getElementById("pingJSText"); - pingJSTextOutput.innerText = "ping.js loaded from server"; - `) - }) + withHomeHandler(t, tb, "lifecycle.html?pingCount=10") + withPingHandler(t, tb, tt.pingSlowness, nil) + withPingJSHandler(t, tb, tt.pingJSSlow, nil, false) - assertHome(t, tb, p, common.LifecycleEventDOMContentLoad, func() { - p.WaitForLoadState(common.LifecycleEventNetworkIdle.String(), nil) + assertHome(t, tb, p, tt.waitUntil, func() testPromise { + result := p.TextContent("#pingRequestText", nil) + tt.pingRequestTextAssert(result, 10) - result := p.TextContent("#pingRequestText", nil) - assert.EqualValues(t, "Waiting... pong 10 - for loop complete", result) + result = p.TextContent("#pingJSText", nil) + tt.pingJSTextAssert(result) - result = p.TextContent("#pingJSText", nil) - assert.EqualValues(t, "ping.js loaded from server", result) - }) -} + opts := tb.toGojaValue(common.PageReloadOptions{ + WaitUntil: tt.waitUntil, + Timeout: 30 * time.Second, + }) + p.Reload(opts) -func TestLifecycleReloadLoad(t *testing.T) { - t.Parallel() + result = p.TextContent("#pingRequestText", nil) + tt.pingRequestTextAssert(result, 20) - tb := newTestBrowser(t, withFileServer()) - p := tb.NewPage(nil) - tb.withHandler("/home", func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, tb.staticURL("reload_lifecycle.html"), http.StatusMovedPermanently) - }) - - var counter int64 - var counterMu sync.Mutex - tb.withHandler("/ping", func(w http.ResponseWriter, _ *http.Request) { - counterMu.Lock() - defer counterMu.Unlock() - - time.Sleep(time.Millisecond * 100) + result = p.TextContent("#pingJSText", nil) + tt.pingJSTextAssert(result) - counter++ - fmt.Fprintf(w, "pong %d", counter) - }) - - tb.withHandler("/ping.js", func(w http.ResponseWriter, _ *http.Request) { - fmt.Fprintf(w, ` - var pingJSTextOutput = document.getElementById("pingJSText"); - pingJSTextOutput.innerText = "ping.js loaded from server"; - `) - }) - - waitUntil := common.LifecycleEventLoad - assertHome(t, tb, p, waitUntil, func() { - result := p.TextContent("#pingRequestText", nil) - assert.NotEqualValues(t, "Waiting... pong 10 - for loop complete", result) - - result = p.TextContent("#pingJSText", nil) - assert.EqualValues(t, "ping.js loaded from server", result) - - opts := tb.toGojaValue(common.PageReloadOptions{ - WaitUntil: waitUntil, - Timeout: 30 * time.Second, + return testPromise{} + }, nil, "") }) - p.Reload(opts) - - result = p.TextContent("#pingRequestText", nil) - assert.NotEqualValues(t, "Waiting... pong 20 - for loop complete", result) - - result = p.TextContent("#pingJSText", nil) - assert.EqualValues(t, "ping.js loaded from server", result) - }) + } } -func TestLifecycleReloadDOMContentLoaded(t *testing.T) { +func TestLifecycleGotoWithSubFrame(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("reload_lifecycle.html"), http.StatusMovedPermanently) - }) - - var counter int64 - var counterMu sync.Mutex - tb.withHandler("/ping", func(w http.ResponseWriter, _ *http.Request) { - counterMu.Lock() - defer counterMu.Unlock() - - time.Sleep(time.Millisecond * 100) + // Test description + // + // Steps: + // 1. goto /home (with iframe to /sub) and wait for the specified lifecycle event. + // + // Success criteria: + // The web page (all frames) is in the expected state + // once we receive the specified lifecycle event from + // the browser. + + tests := []struct { + name string + pingSlowness time.Duration + pingJSSlow bool + waitUntil common.LifecycleEvent + pingRequestTextAssert func(result string) + pingJSTextAssert func(result string) + }{ + { + name: "load", + pingSlowness: 100 * time.Millisecond, + pingJSSlow: false, + waitUntil: common.LifecycleEventLoad, + pingRequestTextAssert: func(result string) { + assert.NotEqualValues(t, "Waiting... pong 10 - for loop complete", result) + }, + pingJSTextAssert: func(result string) { + assert.EqualValues(t, "ping.js loaded from server", result) + }, + }, + { + name: "domcontentloaded", + pingSlowness: 100 * time.Millisecond, + pingJSSlow: true, + waitUntil: common.LifecycleEventDOMContentLoad, + pingRequestTextAssert: func(result string) { + assert.NotEqualValues(t, "Waiting... pong 10 - for loop complete", result) + }, + pingJSTextAssert: func(result string) { + assert.EqualValues(t, "Waiting...", result) + }, + }, + { + name: "networkidle", + pingSlowness: 0, + pingJSSlow: false, + waitUntil: common.LifecycleEventNetworkIdle, + pingRequestTextAssert: func(result string) { + assert.EqualValues(t, "Waiting... pong 10 - for loop complete", result) + }, + pingJSTextAssert: func(result string) { + assert.EqualValues(t, "ping.js loaded from server", result) + }, + }, + } - counter++ - fmt.Fprintf(w, "pong %d", counter) - }) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() - tb.withHandler("/ping.js", func(w http.ResponseWriter, _ *http.Request) { - fmt.Fprintf(w, ` - await new Promise(resolve => setTimeout(resolve, 1000)); + tb := newTestBrowser(t, withFileServer()) + p := tb.NewPage(nil) - var pingJSTextOutput = document.getElementById("pingJSText"); - pingJSTextOutput.innerText = "ping.js loaded from server"; - `) - }) + withHomeHandler(t, tb, "lifecycle_main_frame.html") + withSubHandler(t, tb, "lifecycle.html?pingCount=10") + withPingHandler(t, tb, tt.pingSlowness, nil) + withPingJSHandler(t, tb, tt.pingJSSlow, nil, true) - waitUntil := common.LifecycleEventDOMContentLoad - assertHome(t, tb, p, waitUntil, func() { - result := p.TextContent("#pingRequestText", nil) - assert.NotEqualValues(t, "Waiting... pong 10 - for loop complete", result) + assertHome(t, tb, p, tt.waitUntil, func() testPromise { + result := p.TextContent("#subFramePingRequestText", nil) + tt.pingRequestTextAssert(result) - result = p.TextContent("#pingJSText", nil) - assert.EqualValues(t, "Waiting...", result) + result = p.TextContent("#subFramePingJSText", nil) + tt.pingJSTextAssert(result) - opts := tb.toGojaValue(common.PageReloadOptions{ - WaitUntil: waitUntil, - Timeout: 30 * time.Second, + return testPromise{} + }, nil, "") }) - p.Reload(opts) - - result = p.TextContent("#pingRequestText", nil) - assert.NotEqualValues(t, "Waiting... pong 20 - for loop complete", result) - - result = p.TextContent("#pingJSText", nil) - assert.EqualValues(t, "Waiting...", result) - }) + } } -func TestLifecycleReloadNetworkIdle(t *testing.T) { +func TestLifecycleGoto(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("reload_lifecycle.html"), http.StatusMovedPermanently) - }) + // Test description + // + // Steps: + // 1. goto /home and wait for the specified lifecycle event. + // + // Success criteria: + // The web page is in the expected state once we receive + // the specified lifecycle event from the browser. + + tests := []struct { + name string + pingSlowness time.Duration + pingJSSlow bool + waitUntil common.LifecycleEvent + pingRequestTextAssert func(result string) + pingJSTextAssert func(result string) + }{ + { + name: "load", + pingSlowness: 100 * time.Millisecond, + pingJSSlow: false, + waitUntil: common.LifecycleEventLoad, + pingRequestTextAssert: func(result string) { + assert.NotEqualValues(t, "Waiting... pong 10 - for loop complete", result) + }, + pingJSTextAssert: func(result string) { + assert.EqualValues(t, "ping.js loaded from server", result) + }, + }, + { + name: "domcontentloaded", + pingSlowness: 100 * time.Millisecond, + pingJSSlow: true, + waitUntil: common.LifecycleEventDOMContentLoad, + pingRequestTextAssert: func(result string) { + assert.NotEqualValues(t, "Waiting... pong 10 - for loop complete", result) + }, + pingJSTextAssert: func(result string) { + assert.EqualValues(t, "Waiting...", result) + }, + }, + } - var counter int64 - var counterMu sync.Mutex - tb.withHandler("/ping", func(w http.ResponseWriter, _ *http.Request) { - counterMu.Lock() - defer counterMu.Unlock() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() - counter++ - fmt.Fprintf(w, "pong %d", counter) - }) + tb := newTestBrowser(t, withFileServer()) + p := tb.NewPage(nil) - tb.withHandler("/ping.js", func(w http.ResponseWriter, _ *http.Request) { - fmt.Fprintf(w, ` - var pingJSTextOutput = document.getElementById("pingJSText"); - pingJSTextOutput.innerText = "ping.js loaded from server"; - `) - }) + withHomeHandler(t, tb, "lifecycle.html?pingCount=10") + withPingHandler(t, tb, tt.pingSlowness, nil) + withPingJSHandler(t, tb, tt.pingJSSlow, nil, false) - waitUntil := common.LifecycleEventNetworkIdle - assertHome(t, tb, p, waitUntil, func() { - result := p.TextContent("#pingRequestText", nil) - assert.EqualValues(t, "Waiting... pong 10 - for loop complete", result) + assertHome(t, tb, p, tt.waitUntil, func() testPromise { + result := p.TextContent("#pingRequestText", nil) + tt.pingRequestTextAssert(result) - result = p.TextContent("#pingJSText", nil) - assert.EqualValues(t, "ping.js loaded from server", result) + result = p.TextContent("#pingJSText", nil) + tt.pingJSTextAssert(result) - opts := tb.toGojaValue(common.PageReloadOptions{ - WaitUntil: waitUntil, - Timeout: 30 * time.Second, + return testPromise{} + }, nil, "") }) - p.Reload(opts) - - result = p.TextContent("#pingRequestText", nil) - assert.EqualValues(t, "Waiting... pong 20 - for loop complete", result) - - result = p.TextContent("#pingJSText", nil) - assert.EqualValues(t, "ping.js loaded from server", result) - }) + } } -func TestLifecycleNetworkIdle(t *testing.T) { +func TestLifecycleGotoNetworkIdle(t *testing.T) { t.Parallel() t.Run("doesn't timeout waiting for networkIdle", func(t *testing.T) { @@ -393,17 +568,14 @@ func TestLifecycleNetworkIdle(t *testing.T) { `) }) - tb.withHandler("/ping.js", func(w http.ResponseWriter, _ *http.Request) { - fmt.Fprintf(w, ` - var pingJSTextOutput = document.getElementById("pingJSText"); - pingJSTextOutput.innerText = "ping.js loaded from server"; - `) - }) + withPingJSHandler(t, tb, false, nil, false) - assertHome(t, tb, p, common.LifecycleEventNetworkIdle, func() { + assertHome(t, tb, p, common.LifecycleEventNetworkIdle, func() testPromise { result := p.TextContent("#pingJSText", nil) assert.EqualValues(t, "ping.js loaded from server", result) - }) + + return testPromise{} + }, nil, "") }) t.Run("doesn't unblock wait for networkIdle too early", func(t *testing.T) { @@ -411,40 +583,21 @@ func TestLifecycleNetworkIdle(t *testing.T) { 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 + withHomeHandler(t, tb, "lifecycle.html?pingCount=4") ch := make(chan bool) - var counterMu sync.Mutex - tb.withHandler("/ping", func(w http.ResponseWriter, _ *http.Request) { - <-ch - - 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 pingJSTextOutput = document.getElementById("pingJSText"); - pingJSTextOutput.innerText = "ping.js loaded from server"; - `) - close(ch) - }) + withPingHandler(t, tb, 50*time.Millisecond, ch) + withPingJSHandler(t, tb, false, ch, false) - assertHome(t, tb, p, common.LifecycleEventNetworkIdle, func() { + assertHome(t, tb, p, common.LifecycleEventNetworkIdle, func() testPromise { result := p.TextContent("#pingRequestText", nil) assert.EqualValues(t, "Waiting... pong 4 - for loop complete", result) result = p.TextContent("#pingJSText", nil) assert.EqualValues(t, "ping.js loaded from server", result) - }) + + return testPromise{} + }, nil, "") }) t.Run("doesn't unblock wait on networkIdle early when load and domcontentloaded complete at once", func(t *testing.T) { @@ -452,35 +605,89 @@ func TestLifecycleNetworkIdle(t *testing.T) { 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) + withHomeHandler(t, tb, "lifecycle_no_ping_js.html") + withPingHandler(t, tb, 50*time.Millisecond, nil) - counter++ - fmt.Fprintf(w, "pong %d", counter) - }) - - assertHome(t, tb, p, common.LifecycleEventNetworkIdle, func() { + assertHome(t, tb, p, common.LifecycleEventNetworkIdle, func() testPromise { result := p.TextContent("#pingRequestText", nil) assert.EqualValues(t, "Waiting... pong 10 - for loop complete", result) - }) + + return testPromise{} + }, nil, "") + }) +} + +func withHomeHandler(t *testing.T, tb *testBrowser, htmlFile string) { + t.Helper() + + tb.withHandler("/home", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, tb.staticURL(htmlFile), http.StatusMovedPermanently) + }) +} + +func withSubHandler(t *testing.T, tb *testBrowser, htmlFile string) { + t.Helper() + + tb.withHandler("/sub", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, tb.staticURL(htmlFile), http.StatusMovedPermanently) + }) +} + +func withPingHandler(t *testing.T, tb *testBrowser, slow time.Duration, ch chan bool) { + t.Helper() + + var counter int64 + var counterMu sync.Mutex + tb.withHandler("/ping", func(w http.ResponseWriter, _ *http.Request) { + if ch != nil { + <-ch + } + + counterMu.Lock() + defer counterMu.Unlock() + + time.Sleep(slow) + + counter++ + fmt.Fprintf(w, "pong %d", counter) + }) +} + +func withPingJSHandler(t *testing.T, tb *testBrowser, slow bool, ch chan bool, withSubFrame bool) { + t.Helper() + + tb.withHandler("/ping.js", func(w http.ResponseWriter, _ *http.Request) { + script := ` + var pingJSTextOutput = document.getElementById("pingJSText"); + pingJSTextOutput.innerText = "ping.js loaded from server"; + ` + if withSubFrame { + script += ` + + var parentOutputServerMsg = window.parent.document.getElementById('subFramePingJSText'); + parentOutputServerMsg.innerText = pingJSTextOutput.innerText; + ` + } + if slow { + script = ` + await new Promise(resolve => setTimeout(resolve, 1000)); + + ` + script + } + fmt.Fprint(w, script) + + if ch != nil { + close(ch) + } }) } func assertHome( t *testing.T, - tb *testBrowser, - p api.Page, + tb *testBrowser, p api.Page, waitUntil common.LifecycleEvent, - check func(), + check func() testPromise, secondCheck func(), wantError string, ) { t.Helper() @@ -490,18 +697,27 @@ func assertHome( WaitUntil: waitUntil, Timeout: 30 * time.Second, }) - tb.promise(p.Goto(tb.URL("/home"), opts)).then( - func() { - check() + prm := tb.promise(p.Goto(tb.URL("/home"), opts)).then( + func() testPromise { resolved = true + return check() }, func() { rejected = true }, ) + if secondCheck != nil { + prm.then(secondCheck) + } return nil }) + + if wantError != "" { + require.EqualError(t, err, wantError) + return + } + require.NoError(t, err) assert.True(t, resolved) diff --git a/tests/static/wait_for_nav_lifecycle.html b/tests/static/lifecycle.html similarity index 50% rename from tests/static/wait_for_nav_lifecycle.html rename to tests/static/lifecycle.html index d2fd9ad39..c3db753bb 100644 --- a/tests/static/wait_for_nav_lifecycle.html +++ b/tests/static/lifecycle.html @@ -9,18 +9,29 @@ - - - - \ No newline at end of file diff --git a/tests/static/reload_lifecycle.html b/tests/static/reload_lifecycle.html deleted file mode 100644 index bc0349abc..000000000 --- a/tests/static/reload_lifecycle.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - -
Waiting...
-
Waiting...
- - - - - - \ No newline at end of file