From a3b009350617f25748c31547a11fb700220b001b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0nan=C3=A7=20G=C3=BCm=C3=BC=C5=9F?= Date: Tue, 7 May 2024 17:09:03 +0300 Subject: [PATCH] Async browser.newPage To run everything race-free, we run all JS code on the event loop. --- browser/browser_mapping.go | 28 ++++--- tests/browser_context_test.go | 40 ++++----- tests/browser_test.go | 8 +- tests/browser_type_test.go | 8 +- tests/page_test.go | 148 +++++++++++++++++++--------------- tests/tracing_test.go | 2 +- 6 files changed, 132 insertions(+), 102 deletions(-) diff --git a/browser/browser_mapping.go b/browser/browser_mapping.go index d29825703..3ebeccc4a 100644 --- a/browser/browser_mapping.go +++ b/browser/browser_mapping.go @@ -69,21 +69,23 @@ func mapBrowser(vu moduleVU) mapping { //nolint:funlen,cyclop } return b.Version(), nil }, - "newPage": func(opts goja.Value) (mapping, error) { - b, err := vu.browser() - if err != nil { - return nil, err - } - page, err := b.NewPage(opts) - if err != nil { - return nil, err //nolint:wrapcheck - } + "newPage": func(opts goja.Value) *goja.Promise { + return k6ext.Promise(vu.Context(), func() (any, error) { + b, err := vu.browser() + if err != nil { + return nil, err + } + page, err := b.NewPage(opts) + if err != nil { + return nil, err //nolint:wrapcheck + } - if err := initBrowserContext(b.Context(), vu.testRunID); err != nil { - return nil, err - } + if err := initBrowserContext(b.Context(), vu.testRunID); err != nil { + return nil, err + } - return mapPage(vu, page), nil + return mapPage(vu, page), nil + }) }, } } diff --git a/tests/browser_context_test.go b/tests/browser_context_test.go index 294e1a664..e08159e6c 100644 --- a/tests/browser_context_test.go +++ b/tests/browser_context_test.go @@ -657,28 +657,31 @@ func TestK6Object(t *testing.T) { // First test with browser.newPage got, err := vu.TestRT.RunOnEventLoop(` - const p = browser.newPage(); - p.goto("about:blank"); - const o = p.evaluate(() => window.k6); - JSON.stringify(o); + (async function() { + const p = await browser.newPage(); + p.goto("about:blank"); + const o = p.evaluate(() => window.k6); + return JSON.stringify(o); + })(); `) require.NoError(t, err) - assert.Equal(t, tt.want, got.String()) + p, ok := got.Export().(*goja.Promise) + require.Truef(t, ok, "got: %T, want *goja.Promise", got.Export()) + assert.Equal(t, tt.want, p.Result().String()) // Now test with browser.newContext got, err = vu.TestRT.RunOnEventLoop(` - const test = async function() { + (async function() { await browser.closeContext(); const c = await browser.newContext(); - const p2 = c.newPage(); + const p2 = await c.newPage(); p2.goto("about:blank"); const o2 = p2.evaluate(() => window.k6); return JSON.stringify(o2); - } - test(); + })(); `) require.NoError(t, err) - p, ok := got.Export().(*goja.Promise) + p, ok = got.Export().(*goja.Promise) require.Truef(t, ok, "got: %T, want *goja.Promise", got.Export()) assert.Equal(t, tt.want, p.Result().String()) }) @@ -704,17 +707,18 @@ func TestNewTab(t *testing.T) { mux.Handle(path, http.StripPrefix(path, fs)) // Start the iteration - _, rt, _, cleanUp := startIteration(t, env.ConstLookup(env.K6TestRunID, "12345")) + vu, _, _, cleanUp := startIteration(t, env.ConstLookup(env.K6TestRunID, "12345")) defer cleanUp() // Run the test script - _, err := rt.RunString(fmt.Sprintf(` - const p = browser.newPage() - p.goto("%s/%s/ping.html") - - const p2 = browser.context().newPage() - p2.goto("%s/%s/ping.html") - `, s.URL, testBrowserStaticDir, s.URL, testBrowserStaticDir)) + _, err := vu.TestRT.RunOnEventLoop(fmt.Sprintf(` + (async function() { + const p = await browser.newPage() + p.goto("%s/%s/ping.html") + + const p2 = browser.context().newPage() + p2.goto("%s/%s/ping.html") + })()`, s.URL, testBrowserStaticDir, s.URL, testBrowserStaticDir)) require.NoError(t, err) } diff --git a/tests/browser_test.go b/tests/browser_test.go index 8f3f150e0..3c62225dc 100644 --- a/tests/browser_test.go +++ b/tests/browser_test.go @@ -232,9 +232,11 @@ func TestBrowserCrashErr(t *testing.T) { rt := vu.Runtime() require.NoError(t, rt.Set("browser", jsMod.Browser)) - _, err := rt.RunString(` - const p = browser.newPage(); - p.close(); + _, err := vu.TestRT.RunOnEventLoop(` + (async function() { + const p = await browser.newPage(); + p.close(); + })(); `) assert.ErrorContains(t, err, "launching browser: Invalid devtools server port") } diff --git a/tests/browser_type_test.go b/tests/browser_type_test.go index 82b877060..1c5155997 100644 --- a/tests/browser_type_test.go +++ b/tests/browser_type_test.go @@ -51,9 +51,11 @@ func TestBrowserTypeLaunchToConnect(t *testing.T) { rt := vu.Runtime() require.NoError(t, rt.Set("browser", jsMod.Browser)) - _, err := rt.RunString(` - const p = browser.newPage(); - p.close(); + _, err := vu.TestRT.RunOnEventLoop(` + (async function() { + const p = await browser.newPage(); + p.close(); + })(); `) require.NoError(t, err) diff --git a/tests/page_test.go b/tests/page_test.go index b28b8a918..5e9f19902 100644 --- a/tests/page_test.go +++ b/tests/page_test.go @@ -216,23 +216,32 @@ func TestPageEvaluateMapping(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - _, rt, _, cleanUp := startIteration(t) + vu, _, _, cleanUp := startIteration(t) defer cleanUp() // Test script as non string input - got, err := rt.RunString(fmt.Sprintf(` - const p = browser.newPage() - p.evaluate(%s) + got, err := vu.TestRT.RunOnEventLoop(fmt.Sprintf(` + let p; + (async function() { + p = await browser.newPage() + return p.evaluate(%s) + })(); `, tt.script)) assert.NoError(t, err) - assert.Equal(t, rt.ToValue(tt.want), got) + p, ok := got.Export().(*goja.Promise) + require.Truef(t, ok, "got: %T, want *goja.Promise", got.Export()) + assert.Equal(t, vu.ToGojaValue(tt.want), p.Result()) // Test script as string input - got, err = rt.RunString(fmt.Sprintf(` - p.evaluate("%s") + got, err = vu.TestRT.RunOnEventLoop(fmt.Sprintf(` + (async function() { + return p.evaluate("%s") + })(); `, tt.script)) assert.NoError(t, err) - assert.Equal(t, rt.ToValue(tt.want), got) + p, ok = got.Export().(*goja.Promise) + require.Truef(t, ok, "got: %T, want *goja.Promise", got.Export()) + assert.Equal(t, vu.ToGojaValue(tt.want), p.Result()) }) } } @@ -262,18 +271,21 @@ func TestPageEvaluateMappingError(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - _, rt, _, cleanUp := startIteration(t) + vu, _, _, cleanUp := startIteration(t) defer cleanUp() // Test script as non string input - _, err := rt.RunString(fmt.Sprintf(` - const p = browser.newPage() - p.evaluate(%s) + _, err := vu.TestRT.RunOnEventLoop(fmt.Sprintf(` + let p; + (async function() { + p = await browser.newPage() + p.evaluate(%s) + })(); `, tt.script)) assert.ErrorContains(t, err, tt.wantErr) // Test script as string input - _, err = rt.RunString(fmt.Sprintf(` + _, err = vu.TestRT.RunOnEventLoop(fmt.Sprintf(` p.evaluate("%s") `, tt.script)) assert.ErrorContains(t, err, tt.wantErr) @@ -648,21 +660,19 @@ func TestPageWaitForFunction(t *testing.T) { // waitForFunction. script := ` var page; - const test = async function() { - page = browser.newPage(); + (async function() { + page = await browser.newPage(); let resp = await page.waitForFunction(%s, %s, %s); log('ok: '+resp); - } - test(); - ` + })();` t.Run("ok_func_raf_default", func(t *testing.T) { t.Parallel() - vu, rt, log, cleanUp := startIteration(t) + vu, _, log, cleanUp := startIteration(t) defer cleanUp() - _, err := rt.RunString(`fn = () => { + _, err := vu.TestRT.RunOnEventLoop(`fn = () => { if (typeof window._cnt == 'undefined') window._cnt = 0; if (window._cnt >= 50) return true; window._cnt++; @@ -681,7 +691,7 @@ func TestPageWaitForFunction(t *testing.T) { vu, rt, log, cleanUp := startIteration(t) defer cleanUp() - _, err := rt.RunString(`fn = arg => { + _, err := vu.TestRT.RunOnEventLoop(`fn = arg => { window._arg = arg; return true; }`) @@ -692,7 +702,7 @@ func TestPageWaitForFunction(t *testing.T) { require.NoError(t, err) assert.Contains(t, *log, "ok: null") - argEval, err := rt.RunString(`page.evaluate(() => window._arg);`) + argEval, err := vu.TestRT.RunOnEventLoop(`page.evaluate(() => window._arg);`) require.NoError(t, err) var gotArg string @@ -706,7 +716,7 @@ func TestPageWaitForFunction(t *testing.T) { vu, rt, log, cleanUp := startIteration(t) defer cleanUp() - _, err := rt.RunString(`fn = (...args) => { + _, err := vu.TestRT.RunOnEventLoop(`fn = (...args) => { window._args = args; return true; }`) @@ -720,7 +730,7 @@ func TestPageWaitForFunction(t *testing.T) { require.NoError(t, err) assert.Contains(t, *log, "ok: null") - argEval, err := rt.RunString(`page.evaluate(() => window._args);`) + argEval, err := vu.TestRT.RunOnEventLoop(`page.evaluate(() => window._args);`) require.NoError(t, err) var gotArgs []int @@ -734,7 +744,7 @@ func TestPageWaitForFunction(t *testing.T) { vu, _, _, cleanUp := startIteration(t) defer cleanUp() - _, err := vu.TestRT.RunOnEventLoop(fmt.Sprintf(script, "false", "{ polling: 'raf', timeout: 500, }", "null")) + _, err := vu.TestRT.RunOnEventLoop(fmt.Sprintf(script, "false", "{ polling: 'raf', timeout: 500 }", "null")) require.ErrorContains(t, err, "timed out after 500ms") }) @@ -754,18 +764,22 @@ func TestPageWaitForFunction(t *testing.T) { t.Run("ok_expr_poll_interval", func(t *testing.T) { t.Parallel() - vu, rt, log, cleanUp := startIteration(t) + vu, _, log, cleanUp := startIteration(t) defer cleanUp() - _, err := rt.RunString(` - const page = browser.newPage(); - page.evaluate(() => { - setTimeout(() => { - const el = document.createElement('h1'); - el.innerHTML = 'Hello'; - document.body.appendChild(el); - }, 1000); - });`) + _, err := vu.TestRT.RunOnEventLoop(` + let page; + + (async function() { + page = await browser.newPage(); + page.evaluate(() => { + setTimeout(() => { + const el = document.createElement('h1'); + el.innerHTML = 'Hello'; + document.body.appendChild(el); + }, 1000); + }); + })();`) require.NoError(t, err) script := ` @@ -788,30 +802,33 @@ func TestPageWaitForFunction(t *testing.T) { t.Run("ok_func_poll_mutation", func(t *testing.T) { t.Parallel() - vu, rt, log, cleanUp := startIteration(t) + vu, _, log, cleanUp := startIteration(t) defer cleanUp() - _, err := rt.RunString(` - fn = () => document.querySelector('h1') !== null - - const page = browser.newPage(); - page.evaluate(() => { - console.log('calling setTimeout...'); - setTimeout(() => { - console.log('creating element...'); - const el = document.createElement('h1'); - el.innerHTML = 'Hello'; - document.body.appendChild(el); - }, 1000); - })`) + _, err := vu.TestRT.RunOnEventLoop(` + let page; + + (async function() { + fn = () => document.querySelector('h1') !== null + + page = await browser.newPage(); + page.evaluate(() => { + console.log('calling setTimeout...'); + setTimeout(() => { + console.log('creating element...'); + const el = document.createElement('h1'); + el.innerHTML = 'Hello'; + document.body.appendChild(el); + }, 1000); + }) + })();`) require.NoError(t, err) script := ` - const test = async function() { + (async function() { let resp = await page.waitForFunction(%s, %s, %s); log('ok: '+resp); - } - test();` + })()` s := fmt.Sprintf(script, "fn", "{ polling: 'mutation', timeout: 2000, }", "null") _, err = vu.TestRT.RunOnEventLoop(s) @@ -1679,23 +1696,26 @@ func TestShadowDOMAndDocumentFragment(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - _, rt, _, cleanUp := startIteration(t) + vu, _, _, cleanUp := startIteration(t) defer cleanUp() - got, err := rt.RunString(fmt.Sprintf(` - const p = browser.newPage() - p.goto("%s/%s/shadow_and_doc_frag.html") + got, err := vu.TestRT.RunOnEventLoop(fmt.Sprintf(` + (async function() { + const p = await browser.newPage() + p.goto("%s/%s/shadow_and_doc_frag.html") - const s = p.locator('%s') - s.waitFor({ - timeout: 1000, - state: 'attached', - }); + const s = p.locator('%s') + s.waitFor({ + timeout: 1000, + state: 'attached', + }); - s.innerText(); - `, s.URL, testBrowserStaticDir, tt.selector)) + return s.innerText(); + })()`, s.URL, testBrowserStaticDir, tt.selector)) assert.NoError(t, err) - assert.Equal(t, tt.want, got.String()) + p, ok := got.Export().(*goja.Promise) + require.Truef(t, ok, "got: %T, want *goja.Promise", got.Export()) + assert.Equal(t, tt.want, p.Result().String()) }) } } diff --git a/tests/tracing_test.go b/tests/tracing_test.go index b0bcb8440..c7e884119 100644 --- a/tests/tracing_test.go +++ b/tests/tracing_test.go @@ -92,7 +92,7 @@ func TestTracing(t *testing.T) { }{ { name: "browser.newPage", - js: "page = browser.newPage()", + js: "page = await browser.newPage()", spans: []string{ "browser.newPage", "browser.newContext",