Skip to content

Commit

Permalink
Add tracing integration test
Browse files Browse the repository at this point in the history
  • Loading branch information
ka3de committed Nov 16, 2023
1 parent 7070526 commit 37a5123
Showing 1 changed file with 239 additions and 0 deletions.
239 changes: 239 additions & 0 deletions tests/tracing_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package tests

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"

"github.com/dop251/goja"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace"

"github.com/grafana/xk6-browser/browser"
"github.com/grafana/xk6-browser/k6ext/k6test"
browsertrace "github.com/grafana/xk6-browser/trace"

k6lib "go.k6.io/k6/lib"
)

const html = `
<!DOCTYPE html>
<html>
<head>
<title>Clickable link test</title>
</head>
<body>
<div class="main">
<h3>Click Counter</h3>
<button id="clickme">Click me: 0</button>
<h3>Type input</h3>
<input type="text" id="typeme">
</div>
<script>
var button = document.getElementById("clickme"),
count = 0;
button.onclick = function() {
count += 1;
button.innerHTML = "Click me: " + count;
};
</script>
</body>
</html>
`

// TestTracing verifies that all methods instrumented to generate
// traces behave correctly.
func TestTracing(t *testing.T) {
t.Parallel()

// Init tracing mocks
tracer := &mockTracer{
spans: make(map[string]struct{}),
}
tp := &mockTracerProvider{
tracer: tracer,
}
// Start test server
ts := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, html)
},
))

// Initialize VU and browser module
vu := k6test.NewVU(t, k6test.WithTracerProvider(tp))

rt := vu.Runtime()
root := browser.New()
mod := root.NewModuleInstance(vu)
jsMod, ok := mod.Exports().Default.(*browser.JSModule)
require.Truef(t, ok, "unexpected default mod export type %T", mod.Exports().Default)
require.NoError(t, rt.Set("browser", jsMod.Browser))
vu.ActivateVU()

// Run the test
vu.StartIteration(t)
require.NoError(t, tracer.verifySpans("iteration"))
setupTestTracing(t, rt)

testCases := []struct {
name string
js string
spans []string
}{
{
name: "browser.newPage",
js: "page = browser.newPage()",
spans: []string{
"browser.newPage",
"browser.newContext",
"browserContext.newPage",
},
},
{
name: "page.goto",
js: fmt.Sprintf("page.goto('%s')", ts.URL),
spans: []string{
"page.goto",
fmt.Sprintf("%s/", ts.URL), // Navigation span
},
},
{
name: "web_vital",
js: "sleep(100);", // Wait for async WebVitals processing
spans: []string{
"web_vital",
},
},
{
name: "page.screenshot",
js: "page.screenshot();",
spans: []string{
"page.screenshot",
},
},
{
name: "locator.click",
js: "page.locator('#clickme').click();",
spans: []string{
"locator.click",
},
},
{
name: "locator.type",
js: "page.locator('input#typeme').type('test');",
spans: []string{
"locator.type",
},
},
{
name: "page.reload",
js: `await Promise.all([
page.waitForNavigation(),
page.reload(),
]);`,
spans: []string{
"page.reload",
"page.waitForNavigation",
},
},
{
name: "page.close",
js: "page.close()",
spans: []string{
"page.close",
},
},
}

for _, tc := range testCases {
assertJSInEventLoop(t, vu, tc.js)
require.NoError(t, tracer.verifySpans(tc.spans...))
}
}

func setupTestTracing(t *testing.T, rt *goja.Runtime) {
t.Helper()

// Declare a global page var that we can use
// throughout the test cases
_, err := rt.RunString("var page;")
require.NoError(t, err)

// Set a sleep function so we can use it to wait
// for async WebVitals processing
err = rt.Set("sleep", func(d int) {
time.Sleep(time.Duration(d) * time.Millisecond)
})
require.NoError(t, err)
}

func assertJSInEventLoop(t *testing.T, vu *k6test.VU, js string) {
t.Helper()

f := fmt.Sprintf(
"test = async function() { %s; }",
js)

rt := vu.Runtime()
_, err := rt.RunString(f)
require.NoError(t, err)

test, ok := goja.AssertFunction(rt.Get("test"))
require.True(t, ok)

err = vu.Loop.Start(func() error {
_, err := test(goja.Undefined())
return err
})
require.NoError(t, err)
}

type mockTracerProvider struct {
k6lib.TracerProvider

tracer trace.Tracer
}

func (m *mockTracerProvider) Tracer(
name string, options ...trace.TracerOption,
) trace.Tracer {
return m.tracer
}

type mockTracer struct {
mu sync.Mutex
spans map[string]struct{}
}

func (m *mockTracer) Start(
ctx context.Context, spanName string, opts ...trace.SpanStartOption,
) (context.Context, trace.Span) {
m.mu.Lock()
defer m.mu.Unlock()

m.spans[spanName] = struct{}{}

return ctx, browsertrace.NoopSpan{}
}

func (m *mockTracer) verifySpans(spanNames ...string) error {
m.mu.Lock()
defer m.mu.Unlock()

for _, sn := range spanNames {
if _, ok := m.spans[sn]; !ok {
return fmt.Errorf("%q span was not found", sn)
}
delete(m.spans, sn)
}

return nil
}

0 comments on commit 37a5123

Please sign in to comment.