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

Commit b11bde2

Browse files
authored
Merge pull request #141 from grafana/refactor/tests
Refactor: Tests
2 parents 52a46c3 + 23b236d commit b11bde2

33 files changed

+682
-839
lines changed

chromium/browser_type.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ func (b *BrowserType) Launch(opts goja.Value) api.Browser {
7979

8080
launchOpts := common.NewLaunchOptions()
8181
launchOpts.Parse(b.Ctx, opts)
82+
8283
b.Ctx = common.WithLaunchOptions(b.Ctx, launchOpts)
8384

8485
envs := make([]string, 0, len(launchOpts.Env))

common/browser.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ func (b *Browser) NewContext(opts goja.Value) api.BrowserContext {
380380
browserContextID, err := action.Do(cdp.WithExecutor(b.ctx, b.conn))
381381
b.logger.Debugf("Browser:NewContext", "browserContextID: %v", browserContextID)
382382
if err != nil {
383-
k6Throw(b.ctx, "unable to execute %T: %w", action, err)
383+
k6Throw(b.ctx, "cannot create browser context (%s): %w", browserContextID, err)
384384
}
385385

386386
browserCtxOpts := NewBrowserContextOptions()

common/connection_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,13 @@ import (
3030
"github.com/chromedp/cdproto/cdp"
3131
"github.com/chromedp/cdproto/target"
3232
"github.com/gorilla/websocket"
33-
"github.com/grafana/xk6-browser/testutils"
3433
"github.com/mailru/easyjson"
3534
"github.com/stretchr/testify/assert"
3635
"github.com/stretchr/testify/require"
3736
)
3837

3938
func TestConnection(t *testing.T) {
40-
server := testutils.NewWSTestServerWithEcho(t)
39+
server := NewWSServerWithEcho(t)
4140
defer server.Cleanup()
4241

4342
t.Run("connect", func(t *testing.T) {
@@ -52,7 +51,7 @@ func TestConnection(t *testing.T) {
5251
}
5352

5453
func TestConnectionClosureAbnormal(t *testing.T) {
55-
server := testutils.NewWSTestServerWithClosureAbnormal(t)
54+
server := NewWSServerWithClosureAbnormal(t)
5655
defer server.Cleanup()
5756

5857
t.Run("closure abnormal", func(t *testing.T) {
@@ -70,7 +69,7 @@ func TestConnectionClosureAbnormal(t *testing.T) {
7069
}
7170

7271
func TestConnectionSendRecv(t *testing.T) {
73-
server := testutils.NewWSTestServerWithCDPHandler(t, testutils.CDPDefaultHandler, nil)
72+
server := NewWSServerWithCDPHandler(t, CDPDefaultHandler, nil)
7473
defer server.Cleanup()
7574

7675
t.Run("send command with empty reply", func(t *testing.T) {
@@ -134,7 +133,7 @@ func TestConnectionCreateSession(t *testing.T) {
134133
}
135134
}
136135

137-
server := testutils.NewWSTestServerWithCDPHandler(t, handler, &cmdsReceived)
136+
server := NewWSServerWithCDPHandler(t, handler, &cmdsReceived)
138137
defer server.Cleanup()
139138

140139
t.Run("create session for target", func(t *testing.T) {

common/helpers.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"os"
3030
"reflect"
3131
"strings"
32+
"sync"
3233
"time"
3334

3435
cdpruntime "github.com/chromedp/cdproto/runtime"
@@ -235,6 +236,15 @@ func waitForEvent(ctx context.Context, emitter EventEmitter, events []string, pr
235236
// browser process from the context and kills it if it still exists.
236237
// TODO: test
237238
func k6Throw(ctx context.Context, format string, a ...interface{}) {
239+
k6ThrowOnce.Do(func() {
240+
k6ThrowUnsafe(ctx, format, a...)
241+
})
242+
}
243+
244+
// k6ThrowUnsafe is the real k6Throw but it's not protected from concurrency
245+
// errors.
246+
// see: k6ThrowOnce
247+
func k6ThrowUnsafe(ctx context.Context, format string, a ...interface{}) {
238248
rt := k6common.GetRuntime(ctx)
239249
if rt == nil {
240250
// this should never happen unless a programmer error
@@ -258,3 +268,12 @@ func k6Throw(ctx context.Context, format string, a ...interface{}) {
258268
_ = p.Release()
259269
_ = p.Kill()
260270
}
271+
272+
// There is a data-race in Goja, and this prevents it from happening.
273+
//
274+
// Reproduce with an integration test in tests/:
275+
//
276+
// ctx, cancel := context.WithCancel(context.Background())
277+
// tb := testBrowser(t, withContext(ctx))
278+
// cancel()
279+
var k6ThrowOnce sync.Once

testutils/cdp.go renamed to common/protocol_test.go

Lines changed: 132 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,31 @@
1818
*
1919
*/
2020

21-
// Package testutils is indended only for use in tests, do not import in production code!
22-
package testutils
21+
package common
2322

2423
import (
24+
"context"
2525
"fmt"
26+
"io"
27+
"net"
2628
"net/http"
29+
"net/http/httptest"
30+
"net/url"
2731
"testing"
32+
"time"
2833

2934
"github.com/chromedp/cdproto"
3035
"github.com/gorilla/websocket"
3136
"github.com/mailru/easyjson"
3237
"github.com/mailru/easyjson/jlexer"
3338
"github.com/mailru/easyjson/jwriter"
39+
"github.com/mccutchen/go-httpbin/httpbin"
40+
"github.com/stretchr/testify/require"
41+
"golang.org/x/net/http2"
42+
43+
k6lib "go.k6.io/k6/lib"
44+
k6netext "go.k6.io/k6/lib/netext"
45+
k6types "go.k6.io/k6/lib/types"
3446
)
3547

3648
const (
@@ -106,12 +118,12 @@ func getWebsocketHandlerCDP(
106118
})
107119
}
108120

109-
// NewWSTestServerWithCDPHandler creates a WS test server with a custom CDP handler function
110-
func NewWSTestServerWithCDPHandler(
121+
// NewWSServerWithCDPHandler creates a WS test server with a custom CDP handler function
122+
func NewWSServerWithCDPHandler(
111123
t testing.TB,
112124
fn func(conn *websocket.Conn, msg *cdproto.Message, writeCh chan cdproto.Message, done chan struct{}),
113125
cmdsReceived *[]cdproto.MethodType) *WSTestServer {
114-
return NewWSTestServer(t, "/cdp", getWebsocketHandlerCDP(fn, cmdsReceived))
126+
return NewWSServer(t, "/cdp", getWebsocketHandlerCDP(fn, cmdsReceived))
115127
}
116128

117129
// CDPDefaultCDPHandler is a default handler for the CDP WS server
@@ -182,3 +194,118 @@ func CDPWriteMsg(conn *websocket.Conn, msg *cdproto.Message) {
182194
return
183195
}
184196
}
197+
198+
const httpDomain = "wsbin.local"
199+
200+
// WSTestServer can be used as a test alternative to a real CDP compatible browser.
201+
type WSTestServer struct {
202+
Mux *http.ServeMux
203+
ServerHTTP *httptest.Server
204+
Dialer *k6netext.Dialer
205+
HTTPTransport *http.Transport
206+
Context context.Context
207+
Cleanup func()
208+
}
209+
210+
func getWebsocketHandlerAbnormalClosure() http.Handler {
211+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
212+
conn, err := (&websocket.Upgrader{}).Upgrade(w, req, w.Header())
213+
if err != nil {
214+
return
215+
}
216+
err = conn.Close() // This forces a connection closure without a proper WS close message exchange
217+
if err != nil {
218+
return
219+
}
220+
})
221+
}
222+
223+
func getWebsocketHandlerEcho() http.Handler {
224+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
225+
conn, err := (&websocket.Upgrader{}).Upgrade(w, req, w.Header())
226+
if err != nil {
227+
return
228+
}
229+
messageType, r, e := conn.NextReader()
230+
if e != nil {
231+
return
232+
}
233+
var wc io.WriteCloser
234+
wc, err = conn.NextWriter(messageType)
235+
if err != nil {
236+
return
237+
}
238+
if _, err = io.Copy(wc, r); err != nil {
239+
return
240+
}
241+
if err = wc.Close(); err != nil {
242+
return
243+
}
244+
err = conn.WriteControl(websocket.CloseMessage,
245+
websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""),
246+
time.Now().Add(10*time.Second),
247+
)
248+
if err != nil {
249+
return
250+
}
251+
})
252+
}
253+
254+
// NewWSServerWithClosureAbnormal creates a WS test server with abnormal closure behavior
255+
func NewWSServerWithClosureAbnormal(t testing.TB) *WSTestServer {
256+
return NewWSServer(t, "/closure-abnormal", getWebsocketHandlerAbnormalClosure())
257+
}
258+
259+
// NewWSServerWithEcho creates a WS test server with an echo handler
260+
func NewWSServerWithEcho(t testing.TB) *WSTestServer {
261+
return NewWSServer(t, "/echo", getWebsocketHandlerEcho())
262+
}
263+
264+
// NewWSServer returns a fully configured and running WS test server
265+
func NewWSServer(t testing.TB, path string, handler http.Handler) *WSTestServer {
266+
t.Helper()
267+
268+
// Create a http.ServeMux and set the httpbin handler as the default
269+
mux := http.NewServeMux()
270+
mux.Handle(path, handler)
271+
mux.Handle("/", httpbin.New().Handler())
272+
273+
// Initialize the HTTP server and get its details
274+
httpSrv := httptest.NewServer(mux)
275+
httpURL, err := url.Parse(httpSrv.URL)
276+
require.NoError(t, err)
277+
httpIP := net.ParseIP(httpURL.Hostname())
278+
require.NotNil(t, httpIP)
279+
280+
httpDomainValue, err := k6lib.NewHostAddress(httpIP, "")
281+
require.NoError(t, err)
282+
283+
// Set up the dialer with shorter timeouts and the custom domains
284+
dialer := k6netext.NewDialer(net.Dialer{
285+
Timeout: 2 * time.Second,
286+
KeepAlive: 10 * time.Second,
287+
DualStack: true,
288+
}, k6netext.NewResolver(net.LookupIP, 0, k6types.DNSfirst, k6types.DNSpreferIPv4))
289+
dialer.Hosts = map[string]*k6lib.HostAddress{
290+
httpDomain: httpDomainValue,
291+
}
292+
293+
// Pre-configure the HTTP client transport with the dialer and TLS config (incl. HTTP2 support)
294+
transport := &http.Transport{
295+
DialContext: dialer.DialContext,
296+
}
297+
require.NoError(t, http2.ConfigureTransport(transport))
298+
299+
ctx, ctxCancel := context.WithCancel(context.Background())
300+
return &WSTestServer{
301+
Mux: mux,
302+
ServerHTTP: httpSrv,
303+
Dialer: dialer,
304+
HTTPTransport: transport,
305+
Context: ctx,
306+
Cleanup: func() {
307+
httpSrv.Close()
308+
ctxCancel()
309+
},
310+
}
311+
}

common/session_test.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import (
3131
cdppage "github.com/chromedp/cdproto/page"
3232
"github.com/chromedp/cdproto/target"
3333
"github.com/gorilla/websocket"
34-
"github.com/grafana/xk6-browser/testutils"
3534
"github.com/mailru/easyjson"
3635
"github.com/stretchr/testify/assert"
3736
"github.com/stretchr/testify/require"
@@ -60,18 +59,18 @@ func TestSessionCreateSession(t *testing.T) {
6059
case cdproto.MethodType(cdproto.CommandTargetAttachToTarget):
6160
writeCh <- cdproto.Message{
6261
Method: cdproto.EventTargetAttachedToTarget,
63-
Params: easyjson.RawMessage([]byte(testutils.DEFAULT_CDP_TARGET_ATTACHED_TO_TARGET_MSG)),
62+
Params: easyjson.RawMessage([]byte(DEFAULT_CDP_TARGET_ATTACHED_TO_TARGET_MSG)),
6463
}
6564
writeCh <- cdproto.Message{
6665
ID: msg.ID,
6766
SessionID: msg.SessionID,
68-
Result: easyjson.RawMessage([]byte(testutils.DEFAULT_CDP_TARGET_ATTACH_TO_TARGET_RESPONSE)),
67+
Result: easyjson.RawMessage([]byte(DEFAULT_CDP_TARGET_ATTACH_TO_TARGET_RESPONSE)),
6968
}
7069
}
7170
}
7271
}
7372

74-
server := testutils.NewWSTestServerWithCDPHandler(t, handler, &cmdsReceived)
73+
server := NewWSServerWithCDPHandler(t, handler, &cmdsReceived)
7574
defer server.Cleanup()
7675

7776
t.Run("send and recv session commands", func(t *testing.T) {
@@ -82,9 +81,9 @@ func TestSessionCreateSession(t *testing.T) {
8281

8382
if assert.NoError(t, err) {
8483
session, err := conn.createSession(&target.Info{
85-
TargetID: testutils.DEFAULT_CDP_TARGET_ID,
84+
TargetID: DEFAULT_CDP_TARGET_ID,
8685
Type: "page",
87-
BrowserContextID: testutils.DEFAULT_CDP_BROWSER_CONTEXT_ID,
86+
BrowserContextID: DEFAULT_CDP_BROWSER_CONTEXT_ID,
8887
})
8988

9089
if assert.NoError(t, err) {

tests/browser_context_options_test.go

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,13 @@ import (
2626
"testing"
2727

2828
"github.com/grafana/xk6-browser/common"
29-
"github.com/grafana/xk6-browser/testutils/browsertest"
3029
"github.com/stretchr/testify/assert"
3130
"github.com/stretchr/testify/require"
3231
)
3332

34-
func TestBrowserContextOptions(t *testing.T) {
35-
bt := browsertest.NewBrowserTest(t)
36-
t.Cleanup(bt.Browser.Close)
33+
func TestBrowserContextOptionsDefaultValues(t *testing.T) {
34+
t.Parallel()
3735

38-
t.Run("BrowserContextOptions", func(t *testing.T) {
39-
t.Run("should have correct default values", func(t *testing.T) { testBrowserContextOptionsDefaultValues(t, bt) })
40-
t.Run("should correctly set default viewport", func(t *testing.T) { testBrowserContextOptionsDefaultViewport(t, bt) })
41-
t.Run("should correctly set custom viewport", func(t *testing.T) { testBrowserContextOptionsSetViewport(t, bt) })
42-
t.Run("should correctly set extra headers", func(t *testing.T) { testBrowserContextOptionsExtraHTTPHeaders(t, bt) })
43-
})
44-
}
45-
46-
func testBrowserContextOptionsDefaultValues(t *testing.T, bt *browsertest.BrowserTest) {
4736
opts := common.NewBrowserContextOptions()
4837
assert.False(t, opts.AcceptDownloads)
4938
assert.False(t, opts.BypassCSP)
@@ -66,17 +55,17 @@ func testBrowserContextOptionsDefaultValues(t *testing.T, bt *browsertest.Browse
6655
assert.Equal(t, &common.Viewport{Width: common.DefaultScreenWidth, Height: common.DefaultScreenHeight}, opts.Viewport)
6756
}
6857

69-
func testBrowserContextOptionsDefaultViewport(t *testing.T, bt *browsertest.BrowserTest) {
70-
p := bt.Browser.NewPage(nil)
71-
t.Cleanup(func() { p.Close(nil) })
58+
func TestBrowserContextOptionsDefaultViewport(t *testing.T) {
59+
p := newTestBrowser(t).NewPage(nil)
7260

7361
viewportSize := p.ViewportSize()
7462
assert.Equal(t, float64(common.DefaultScreenWidth), viewportSize["width"])
7563
assert.Equal(t, float64(common.DefaultScreenHeight), viewportSize["height"])
7664
}
7765

78-
func testBrowserContextOptionsSetViewport(t *testing.T, bt *browsertest.BrowserTest) {
79-
bctx := bt.Browser.NewContext(bt.Runtime.ToValue(struct {
66+
func TestBrowserContextOptionsSetViewport(t *testing.T) {
67+
tb := newTestBrowser(t)
68+
bctx := tb.NewContext(tb.rt.ToValue(struct {
8069
Viewport common.Viewport `js:"viewport"`
8170
}{
8271
Viewport: common.Viewport{
@@ -92,8 +81,9 @@ func testBrowserContextOptionsSetViewport(t *testing.T, bt *browsertest.BrowserT
9281
assert.Equal(t, float64(600), viewportSize["height"])
9382
}
9483

95-
func testBrowserContextOptionsExtraHTTPHeaders(t *testing.T, bt *browsertest.BrowserTest) {
96-
bctx := bt.Browser.NewContext(bt.Runtime.ToValue(struct {
84+
func TestBrowserContextOptionsExtraHTTPHeaders(t *testing.T) {
85+
tb := newTestBrowser(t, withHTTPServer())
86+
bctx := tb.NewContext(tb.rt.ToValue(struct {
9787
ExtraHTTPHeaders map[string]string `js:"extraHTTPHeaders"`
9888
}{
9989
ExtraHTTPHeaders: map[string]string{
@@ -103,7 +93,7 @@ func testBrowserContextOptionsExtraHTTPHeaders(t *testing.T, bt *browsertest.Bro
10393
t.Cleanup(bctx.Close)
10494

10595
p := bctx.NewPage()
106-
resp := p.Goto(bt.HTTPMultiBin.ServerHTTP.URL+"/get", nil)
96+
resp := p.Goto(tb.URL("/get"), nil)
10797

10898
require.NotNil(t, resp)
10999
var body struct{ Headers map[string][]string }

0 commit comments

Comments
 (0)