diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 6201804d8..412f54878 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -19,8 +19,8 @@ jobs: strategy: matrix: go: [stable, tip] - platform: [ubuntu-latest, windows-latest, macos-latest] - runs-on: ubuntu-latest + platform: [ubuntu-latest-8-cores, windows-latest, macos-latest] + runs-on: ubuntu-latest-8-cores steps: - name: Checkout code uses: actions/checkout@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e01db5cc7..01d91ce3c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - go-version: [1.18.x] + go-version: [1.19.x] platform: [ubuntu-latest-8-cores] runs-on: ${{ matrix.platform }} steps: diff --git a/browser/mapping.go b/browser/mapping.go index 7fd7d830f..2ce97453d 100644 --- a/browser/mapping.go +++ b/browser/mapping.go @@ -8,7 +8,6 @@ import ( "github.com/dop251/goja" "github.com/grafana/xk6-browser/api" - "github.com/grafana/xk6-browser/chromium" "github.com/grafana/xk6-browser/k6error" "github.com/grafana/xk6-browser/k6ext" @@ -33,11 +32,8 @@ func mapBrowserToGoja(vu moduleVU) *goja.Object { var ( rt = vu.Runtime() obj = rt.NewObject() - // TODO: Use k6 LookupEnv instead of OS package methods. - // See https://github.com/grafana/xk6-browser/issues/822. - wsURL, isRemoteBrowser = vu.isRemoteBrowser() ) - for k, v := range mapBrowser(vu, wsURL, isRemoteBrowser) { + for k, v := range mapBrowser(vu) { err := obj.Set(k, rt.ToValue(v)) if err != nil { k6common.Throw(rt, fmt.Errorf("mapping: %w", err)) @@ -676,29 +672,25 @@ func mapBrowserContext(vu moduleVU, bc api.BrowserContext) mapping { } // mapBrowser to the JS module. -func mapBrowser(vu moduleVU, wsURL string, isRemoteBrowser bool) mapping { //nolint:funlen - var ( - rt = vu.Runtime() - ctx = context.Background() - bt = chromium.NewBrowserType(vu) - ) +func mapBrowser(vu moduleVU) mapping { + rt := vu.Runtime() return mapping{ "context": func() (api.BrowserContext, error) { - b, err := getOrInitBrowser(ctx, bt, vu, wsURL, isRemoteBrowser) + b, err := vu.browser() if err != nil { return nil, err } return b.Context(), nil }, "isConnected": func() (bool, error) { - b, err := getOrInitBrowser(ctx, bt, vu, wsURL, isRemoteBrowser) + b, err := vu.browser() if err != nil { return false, err } return b.IsConnected(), nil }, "newContext": func(opts goja.Value) (*goja.Object, error) { - b, err := getOrInitBrowser(ctx, bt, vu, wsURL, isRemoteBrowser) + b, err := vu.browser() if err != nil { return nil, err } @@ -710,21 +702,21 @@ func mapBrowser(vu moduleVU, wsURL string, isRemoteBrowser bool) mapping { //nol return rt.ToValue(m).ToObject(rt), nil }, "userAgent": func() (string, error) { - b, err := getOrInitBrowser(ctx, bt, vu, wsURL, isRemoteBrowser) + b, err := vu.browser() if err != nil { return "", err } return b.UserAgent(), nil }, "version": func() (string, error) { - b, err := getOrInitBrowser(ctx, bt, vu, wsURL, isRemoteBrowser) + b, err := vu.browser() if err != nil { return "", err } return b.Version(), nil }, "newPage": func(opts goja.Value) (mapping, error) { - b, err := getOrInitBrowser(ctx, bt, vu, wsURL, isRemoteBrowser) + b, err := vu.browser() if err != nil { return nil, err } @@ -737,54 +729,6 @@ func mapBrowser(vu moduleVU, wsURL string, isRemoteBrowser bool) mapping { //nol } } -// getOrInitBrowser retrieves the browser for the iteration from the browser registry -// if it is already initialized. Otherwise initializes a new browser for the iteration -// and stores it in the registry. -func getOrInitBrowser( - ctx context.Context, bt *chromium.BrowserType, vu moduleVU, wsURL string, isRemoteBrowser bool, -) (api.Browser, error) { - // Index browser pool per VU-scenario-iteration - id := fmt.Sprintf("%d-%s-%d", - vu.State().VUID, - k6ext.GetScenarioName(vu.Context()), - vu.State().Iteration, - ) - - var ( - ok bool - err error - b api.Browser - ) - - if b, ok = vu.getBrowser(id); ok { - return b, nil - } - - if isRemoteBrowser { - b, err = bt.Connect(ctx, wsURL) - if err != nil { - return nil, err //nolint:wrapcheck - } - } else { - var pid int - b, pid, err = bt.Launch(ctx) - if err != nil { - return nil, err //nolint:wrapcheck - } - vu.registerPid(pid) - } - - vu.setBrowser(id, b) - - go func(ctx context.Context) { - <-ctx.Done() - b.Close() - vu.deleteBrowser(id) - }(vu.Context()) - - return b, nil -} - func panicIfFatalError(ctx context.Context, err error) { if errors.Is(err, k6error.ErrFatal) { k6ext.Abort(ctx, err.Error()) diff --git a/browser/mapping_test.go b/browser/mapping_test.go index 5294bca5e..90b1c8d70 100644 --- a/browser/mapping_test.go +++ b/browser/mapping_test.go @@ -124,7 +124,7 @@ func TestMappings(t *testing.T) { "browser": { apiInterface: (*api.Browser)(nil), mapp: func() mapping { - return mapBrowser(moduleVU{VU: vu}, "", false) + return mapBrowser(moduleVU{VU: vu}) }, }, "browserContext": { diff --git a/browser/module.go b/browser/module.go index d3283d527..1f25e3e8d 100644 --- a/browser/module.go +++ b/browser/module.go @@ -20,10 +20,9 @@ type ( // RootModule is the global module instance that will create module // instances for each VU. RootModule struct { - PidRegistry *pidRegistry - browserRegistry *browserRegistry - remoteRegistry *remoteRegistry - initOnce *sync.Once + PidRegistry *pidRegistry + remoteRegistry *remoteRegistry + initOnce *sync.Once } // JSModule exposes the properties available to the JS script. @@ -46,9 +45,8 @@ var ( // New returns a pointer to a new RootModule instance. func New() *RootModule { return &RootModule{ - PidRegistry: &pidRegistry{}, - browserRegistry: &browserRegistry{}, - initOnce: &sync.Once{}, + PidRegistry: &pidRegistry{}, + initOnce: &sync.Once{}, } } @@ -68,8 +66,7 @@ func (m *RootModule) NewModuleInstance(vu k6modules.VU) k6modules.Instance { Browser: mapBrowserToGoja(moduleVU{ VU: vu, pidRegistry: m.PidRegistry, - browserRegistry: m.browserRegistry, - remoteRegistry: m.remoteRegistry, + browserRegistry: newBrowserRegistry(vu, m.remoteRegistry, m.PidRegistry), }), Devices: common.GetDevices(), }, diff --git a/browser/module_test.go b/browser/module_test.go index 3f39794c6..5724b149d 100644 --- a/browser/module_test.go +++ b/browser/module_test.go @@ -1,18 +1,11 @@ package browser import ( - "context" "testing" - "github.com/dop251/goja" "github.com/stretchr/testify/require" - "github.com/grafana/xk6-browser/env" - - k6common "go.k6.io/k6/js/common" - k6modulestest "go.k6.io/k6/js/modulestest" - k6lib "go.k6.io/k6/lib" - k6metrics "go.k6.io/k6/metrics" + "github.com/grafana/xk6-browser/k6ext/k6test" ) // TestModuleNew tests registering the module. @@ -21,16 +14,7 @@ import ( func TestModuleNew(t *testing.T) { t.Parallel() - vu := &k6modulestest.VU{ - RuntimeField: goja.New(), - InitEnvField: &k6common.InitEnvironment{ - TestPreInitState: &k6lib.TestPreInitState{ - Registry: k6metrics.NewRegistry(), - LookupEnv: env.EmptyLookup, - }, - }, - CtxField: context.Background(), - } + vu := k6test.NewVU(t) m, ok := New().NewModuleInstance(vu).(*ModuleInstance) require.True(t, ok, "NewModuleInstance should return a ModuleInstance") require.NotNil(t, m.mod, "Module should be set") diff --git a/browser/modulevu.go b/browser/modulevu.go index d94ffbac2..bf353c456 100644 --- a/browser/modulevu.go +++ b/browser/modulevu.go @@ -3,6 +3,7 @@ package browser import ( "context" + "github.com/grafana/xk6-browser/api" "github.com/grafana/xk6-browser/k6ext" k6modules "go.k6.io/k6/js/modules" @@ -17,7 +18,11 @@ type moduleVU struct { *pidRegistry *browserRegistry - *remoteRegistry +} + +// browser returns the VU browser instance for the current iteration. +func (vu moduleVU) browser() (api.Browser, error) { + return vu.browserRegistry.getBrowser(vu.State().Iteration) } func (vu moduleVU) Context() context.Context { diff --git a/browser/registry.go b/browser/registry.go index d5f18f8f6..ebebbf743 100644 --- a/browser/registry.go +++ b/browser/registry.go @@ -1,16 +1,24 @@ package browser import ( + "context" "crypto/rand" "encoding/json" + "errors" "fmt" "math/big" "strconv" "strings" "sync" + "sync/atomic" "github.com/grafana/xk6-browser/api" + "github.com/grafana/xk6-browser/chromium" "github.com/grafana/xk6-browser/env" + "github.com/grafana/xk6-browser/k6ext" + + k6event "go.k6.io/k6/event" + k6modules "go.k6.io/k6/js/modules" ) // pidRegistry keeps track of the launched browser process IDs. @@ -148,26 +156,178 @@ func (r *remoteRegistry) isRemoteBrowser() (string, bool) { return wsURL, true } -// browserRegistry stores browser instances indexed per -// iteration as identified by VUID-scenario-iterationID. +// browserRegistry stores a single VU browser instances +// indexed per iteration. type browserRegistry struct { - m sync.Map + vu k6modules.VU + + mu sync.RWMutex + m map[int64]api.Browser + + buildFn browserBuildFunc + + stopped atomic.Bool // testing purposes +} + +type browserBuildFunc func(ctx context.Context) (api.Browser, error) + +func newBrowserRegistry(vu k6modules.VU, remote *remoteRegistry, pids *pidRegistry) *browserRegistry { + bt := chromium.NewBrowserType(vu) + builder := func(ctx context.Context) (api.Browser, error) { + var ( + err error + b api.Browser + wsURL, isRemoteBrowser = remote.isRemoteBrowser() + ) + + if isRemoteBrowser { + b, err = bt.Connect(ctx, wsURL) + if err != nil { + return nil, err //nolint:wrapcheck + } + } else { + var pid int + b, pid, err = bt.Launch(ctx) + if err != nil { + return nil, err //nolint:wrapcheck + } + pids.registerPid(pid) + } + + return b, nil + } + + r := &browserRegistry{ + vu: vu, + m: make(map[int64]api.Browser), + buildFn: builder, + } + + exitSubID, exitCh := vu.Events().Global.Subscribe( + k6event.Exit, + ) + go r.handleExitEvent(exitCh) + + iterSubID, eventsCh := vu.Events().Local.Subscribe( + k6event.IterStart, + k6event.IterEnd, + ) + unsubscribe := func() { + vu.Events().Local.Unsubscribe(iterSubID) + vu.Events().Global.Unsubscribe(exitSubID) + } + go r.handleIterEvents(eventsCh, unsubscribe) + + return r +} + +func (r *browserRegistry) handleIterEvents(eventsCh <-chan *k6event.Event, unsubscribeFn func()) { + var ( + ok bool + data k6event.IterData + ctx = context.Background() + vuCtx = k6ext.WithVU(r.vu.Context(), r.vu) + ) + + for e := range eventsCh { + // If browser module is imported in the test, NewModuleInstance will be called for + // every VU. Because on VU init stage we can not distinguish to which scenario it + // belongs or access its options (because state is nil), we have to always subscribe + // to each VU iter events, including VUs that do not make use of the browser in their + // iterations. + // Therefore, if we get an event that does not correspond to a browser iteration, then + // unsubscribe for the VU events and exit the loop in order to reduce unuseful overhead. + if !isBrowserIter(r.vu) { + unsubscribeFn() + r.stop() + e.Done() + return + } + + if data, ok = e.Data.(k6event.IterData); !ok { + e.Done() + k6ext.Abort(vuCtx, "unexpected iteration event data format: %v", e.Data) + // Continue so we don't block the k6 event system producer. + // Test will be aborted by k6, which will previously send the + // 'Exit' event so browser resources cleanup can be guaranteed. + continue + } + + switch e.Type { //nolint:exhaustive + case k6event.IterStart: + b, err := r.buildFn(ctx) + if err != nil { + e.Done() + k6ext.Abort(vuCtx, "error building browser on IterStart: %v", err) + // Continue so we don't block the k6 event system producer. + // Test will be aborted by k6, which will previously send the + // 'Exit' event so browser resources cleanup can be guaranteed. + continue + } + r.setBrowser(data.Iteration, b) + case k6event.IterEnd: + r.deleteBrowser(data.Iteration) + default: + r.vu.State().Logger.Warnf("received unexpected event type: %v", e.Type) + } + + e.Done() + } +} + +func (r *browserRegistry) handleExitEvent(exitCh <-chan *k6event.Event) { + e, ok := <-exitCh + if !ok { + return + } + defer e.Done() + r.clear() } -func (p *browserRegistry) setBrowser(id string, b api.Browser) { - p.m.Store(id, b) +func (r *browserRegistry) setBrowser(id int64, b api.Browser) { + r.mu.Lock() + defer r.mu.Unlock() + + r.m[id] = b } -func (p *browserRegistry) getBrowser(id string) (b api.Browser, ok bool) { - e, ok := p.m.Load(id) - if ok { - b, ok = e.(api.Browser) - return b, ok +func (r *browserRegistry) getBrowser(id int64) (api.Browser, error) { + r.mu.RLock() + defer r.mu.RUnlock() + + if b, ok := r.m[id]; ok { + return b, nil } - return nil, false + return nil, errors.New("browser not found in registry") +} + +func (r *browserRegistry) deleteBrowser(id int64) { + r.mu.Lock() + defer r.mu.Unlock() + + if b, ok := r.m[id]; ok { + b.Close() + delete(r.m, id) + } +} + +func (r *browserRegistry) clear() { + r.mu.Lock() + defer r.mu.Unlock() + + for id, b := range r.m { + b.Close() + delete(r.m, id) + } +} + +func (r *browserRegistry) stop() { + r.stopped.Store(true) } -func (p *browserRegistry) deleteBrowser(id string) { - p.m.Delete(id) +func isBrowserIter(vu k6modules.VU) bool { + opts := k6ext.GetScenarioOpts(vu.Context(), vu) + _, ok := opts["type"] // Check if browser type option is set + return ok } diff --git a/browser/registry_test.go b/browser/registry_test.go index db21c3f17..41d008445 100644 --- a/browser/registry_test.go +++ b/browser/registry_test.go @@ -1,6 +1,7 @@ package browser import ( + "context" "errors" "strconv" "sync" @@ -10,6 +11,9 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/xk6-browser/env" + "github.com/grafana/xk6-browser/k6ext/k6test" + + k6event "go.k6.io/k6/event" ) func TestPidRegistry(t *testing.T) { @@ -182,3 +186,91 @@ func TestIsRemoteBrowser(t *testing.T) { require.Equal(t, "WS_URL_2", wsURL) }) } + +func TestBrowserRegistry(t *testing.T) { + t.Parallel() + + remoteRegistry, err := newRemoteRegistry(func(key string) (string, bool) { + // No env vars + return "", false + }) + require.NoError(t, err) + + t.Run("init_and_close_browsers_on_iter_events", func(t *testing.T) { + t.Parallel() + + vu := k6test.NewVU(t) + browserRegistry := newBrowserRegistry(vu, remoteRegistry, &pidRegistry{}) + + vu.ActivateVU() + + // Send a few IterStart events + vu.StartIteration(t, k6test.WithIteration(0)) + vu.StartIteration(t, k6test.WithIteration(1)) + vu.StartIteration(t, k6test.WithIteration(2)) + + // Verify browsers are initialized + browserRegistry.mu.RLock() + assert.Equal(t, 3, len(browserRegistry.m)) + browserRegistry.mu.RUnlock() + + // Send IterEnd events + vu.EndIteration(t, k6test.WithIteration(0)) + vu.EndIteration(t, k6test.WithIteration(1)) + vu.EndIteration(t, k6test.WithIteration(2)) + + // Verify there are no browsers left + browserRegistry.mu.RLock() + assert.Equal(t, 0, len(browserRegistry.m)) + browserRegistry.mu.RUnlock() + }) + + t.Run("close_browsers_on_exit_event", func(t *testing.T) { + t.Parallel() + + vu := k6test.NewVU(t) + browserRegistry := newBrowserRegistry(vu, remoteRegistry, &pidRegistry{}) + + vu.ActivateVU() + + // Send a few IterStart events + vu.StartIteration(t, k6test.WithIteration(0)) + vu.StartIteration(t, k6test.WithIteration(1)) + vu.StartIteration(t, k6test.WithIteration(2)) + + // Verify browsers are initialized + browserRegistry.mu.RLock() + assert.Equal(t, 3, len(browserRegistry.m)) + browserRegistry.mu.RUnlock() + + // Send Exit event + events, ok := vu.EventsField.Global.(*k6event.System) + require.True(t, ok, "want *k6event.System; got %T", events) + waitDone := events.Emit(&k6event.Event{ + Type: k6event.Exit, + }) + require.NoError(t, waitDone(context.Background()), "error waiting on Exit done") + + // Verify there are no browsers left + browserRegistry.mu.RLock() + assert.Equal(t, 0, len(browserRegistry.m)) + browserRegistry.mu.RUnlock() + }) + + t.Run("unsubscribe_on_non_browser_vu", func(t *testing.T) { + t.Parallel() + + vu := k6test.NewVU(t) + browserRegistry := newBrowserRegistry(vu, remoteRegistry, &pidRegistry{}) + + vu.ActivateVU() + + // Unset browser type option in scenario options in order to represent that VU is not + // a browser test VU + delete(vu.StateField.Options.Scenarios["default"].GetScenarioOptions().Browser, "type") + + vu.StartIteration(t) + + assert.True(t, browserRegistry.stopped.Load()) + }) +} diff --git a/go.mod b/go.mod index e391952f8..1fa8c70e8 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,14 @@ go 1.19 require ( github.com/chromedp/cdproto v0.0.0-20221023212508-67ada9507fb2 - github.com/dop251/goja v0.0.0-20230427124612-428fc442ff5f + github.com/dop251/goja v0.0.0-20230621100801-7749907a8a20 github.com/gorilla/websocket v1.5.0 github.com/mailru/easyjson v0.7.7 github.com/mccutchen/go-httpbin v1.1.2-0.20190116014521-c5cb2f4802fa - github.com/sirupsen/logrus v1.9.0 - github.com/stretchr/testify v1.8.2 - go.k6.io/k6 v0.44.2-0.20230525101951-06e162830f4f - golang.org/x/net v0.10.0 + github.com/sirupsen/logrus v1.9.3 + github.com/stretchr/testify v1.8.4 + go.k6.io/k6 v0.45.1-0.20230630103240-6137c5e25033 + golang.org/x/net v0.11.0 golang.org/x/sync v0.2.0 gopkg.in/guregu/null.v3 v3.3.0 ) @@ -30,9 +30,9 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.16.5 // indirect + github.com/klauspost/compress v1.16.6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/mstoykov/atlas v0.0.0-20220811071828-388f114305dd // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/onsi/ginkgo v1.16.5 // indirect @@ -43,12 +43,12 @@ require ( github.com/tidwall/gjson v1.14.4 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect - golang.org/x/crypto v0.9.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/crypto v0.10.0 // indirect + golang.org/x/sys v0.9.0 // indirect + golang.org/x/text v0.10.0 // indirect golang.org/x/time v0.3.0 // indirect - google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect - google.golang.org/grpc v1.55.0 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/grpc v1.56.1 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e5d537a4a..1d5900cdc 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnm github.com/dlclark/regexp2 v1.9.0 h1:pTK/l/3qYIKaRXuHnEnIf7Y5NxfRPfpb7dis6/gdlVI= github.com/dlclark/regexp2 v1.9.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= -github.com/dop251/goja v0.0.0-20230427124612-428fc442ff5f h1:3Z9NjtffvA8Qoh8xjgUpPmyKawJw/mDRcJlR9oPCvqI= -github.com/dop251/goja v0.0.0-20230427124612-428fc442ff5f/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= +github.com/dop251/goja v0.0.0-20230621100801-7749907a8a20 h1:gcmFd1xefhuBETzu0XcDETw72GQ7rL7GA41Tfi1JiqY= +github.com/dop251/goja v0.0.0-20230621100801-7749907a8a20/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= @@ -61,6 +61,7 @@ github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8q github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/xk6-grpc v0.1.2 h1:gNN3PYV2dIPoq1zTVz8YOxrWhl1D15jhRR0EA9ZYhBw= github.com/grafana/xk6-redis v0.1.1 h1:rvWnLanRB2qzDwuY6NMBe6PXei3wJ3kjYvfCwRJ+q+8= github.com/grafana/xk6-timers v0.1.2 h1:YVM6hPDgvy4SkdZQpd+/r9M0kDi1g+QdbSxW5ClfwDk= github.com/grafana/xk6-webcrypto v0.1.0 h1:StrQZkUi4vo3bAMmBUHvIQ8P+zNKCH3AwN22TZdDwHs= @@ -70,8 +71,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1: github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= -github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk= +github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -85,8 +86,8 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mccutchen/go-httpbin v1.1.2-0.20190116014521-c5cb2f4802fa h1:lx8ZnNPwjkXSzOROz0cg69RlErRXs+L3eDkggASWKLo= github.com/mccutchen/go-httpbin v1.1.2-0.20190116014521-c5cb2f4802fa/go.mod h1:fhpOYavp5g2K74XDl/ao2y4KvhqVtKlkg1e+0UaQv7I= github.com/mstoykov/atlas v0.0.0-20220811071828-388f114305dd h1:AC3N94irbx2kWGA8f/2Ks7EQl2LxKIRQYuT9IJDwgiI= @@ -112,19 +113,15 @@ github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBO github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e h1:zWKUYT07mGmVBH+9UgnHXd/ekCK99C8EbDSAt5qsjXE= github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -134,14 +131,14 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.k6.io/k6 v0.44.2-0.20230525101951-06e162830f4f h1:7+bUJ2hF9upyFhbEVyR1ULr3+Tf/OYsnWAMZkZwdd+s= -go.k6.io/k6 v0.44.2-0.20230525101951-06e162830f4f/go.mod h1:KJdE8JIa1i6fcrX9flX63CuK3YcZGaSF/pBk8gpwu+U= +go.k6.io/k6 v0.45.1-0.20230630103240-6137c5e25033 h1:/o+32sv8wySgu0v0Ep9p1pd6Tj0pNf93Ivk3fHMY04U= +go.k6.io/k6 v0.45.1-0.20230630103240-6137c5e25033/go.mod h1:DfLtnI98RTrjtfZErozXVY7kVS2P+VKTbjjO4tJXhcI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -153,8 +150,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -180,8 +177,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -191,8 +188,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -203,10 +200,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ= +google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/k6ext/k6test/vu.go b/k6ext/k6test/vu.go index 33622a2c3..90140dec8 100644 --- a/k6ext/k6test/vu.go +++ b/k6ext/k6test/vu.go @@ -1,6 +1,7 @@ package k6test import ( + "context" "testing" "github.com/dop251/goja" @@ -10,6 +11,9 @@ import ( "github.com/grafana/xk6-browser/env" "github.com/grafana/xk6-browser/k6ext" + "go.k6.io/k6/event" + k6event "go.k6.io/k6/event" + k6common "go.k6.io/k6/js/common" k6eventloop "go.k6.io/k6/js/eventloop" k6modulestest "go.k6.io/k6/js/modulestest" k6lib "go.k6.io/k6/lib" @@ -54,6 +58,70 @@ func (v *VU) AssertSamples(assertSample func(s k6metrics.Sample)) int { return n } +// WithScenarioName is used to set the scenario name in the IterData +// for the 'IterStart' event. +type WithScenarioName = string + +// WithVUID is used to set the VU id in the IterData for the 'IterStart' +// event. +type WithVUID = uint64 + +// WithIteration is used to set the iteration in the IterData for the +// 'IterStart' event. +type WithIteration = int64 + +// StartIteration generates a new IterStart event through the VU event system. +// +// opts can be used to parameterize the iteration data such as: +// - WithScenarioName: sets the scenario name (default is 'default'). +// - WithVUID: sets the VUID (default 1). +// - WithIteration: sets the iteration (default 0). +func (v *VU) StartIteration(tb testing.TB, opts ...any) { + tb.Helper() + v.iterEvent(tb, k6event.IterStart, "IterStart", opts...) +} + +// EndIteration generates a new IterEnd event through the VU event system. +// +// opts can be used to parameterize the iteration data such as: +// - WithScenarioName: sets the scenario name (default is 'default'). +// - WithVUID: sets the VUID (default 1). +// - WithIteration: sets the iteration (default 0). +func (v *VU) EndIteration(tb testing.TB, opts ...any) { + tb.Helper() + v.iterEvent(tb, k6event.IterEnd, "IterEnd", opts...) +} + +// iterEvent generates an iteration event for the VU. +func (v *VU) iterEvent(tb testing.TB, eventType event.Type, eventName string, opts ...any) { + tb.Helper() + + data := k6event.IterData{ + Iteration: 0, + VUID: 1, + ScenarioName: "default", + } + + for _, opt := range opts { + switch opt := opt.(type) { + case WithScenarioName: + data.ScenarioName = opt + case WithVUID: + data.VUID = opt + case WithIteration: + data.Iteration = opt + } + } + + events, ok := v.EventsField.Local.(*k6event.System) + require.True(tb, ok, "want *k6event.System; got %T", events) + waitDone := events.Emit(&k6event.Event{ + Type: eventType, + Data: data, + }) + require.NoError(tb, waitDone(context.Background()), "error waiting on %s done", eventName) +} + // WithSamples is used to indicate we want to use a bidirectional channel // so that the test can read the metrics being emitted to the channel. type WithSamples chan k6metrics.SampleContainer @@ -79,11 +147,17 @@ func NewVU(tb testing.TB, opts ...any) *VU { } } + logger := k6testutils.NewLogger(tb) + root, err := k6lib.NewGroup("", nil) require.NoError(tb, err) testRT := k6modulestest.NewRuntime(tb) testRT.VU.InitEnvField.LookupEnv = lookupFunc + testRT.VU.EventsField = k6common.Events{ + Global: k6event.NewEventSystem(100, logger), + Local: k6event.NewEventSystem(100, logger), + } tags := testRT.VU.InitEnvField.Registry.RootTagSet() @@ -108,7 +182,7 @@ func NewVU(tb testing.TB, opts ...any) *VU { }, }, }, - Logger: k6testutils.NewLogger(tb), + Logger: logger, Group: root, BufferPool: k6lib.NewBufferPool(), Samples: samples, diff --git a/tests/browser_test.go b/tests/browser_test.go index 138a941f4..a9474f78d 100644 --- a/tests/browser_test.go +++ b/tests/browser_test.go @@ -177,6 +177,7 @@ func TestBrowserCrashErr(t *testing.T) { require.Truef(t, ok, "unexpected default mod export type %T", mod.Exports().Default) vu.ActivateVU() + vu.StartIteration(t) rt := vu.Runtime() require.NoError(t, rt.Set("browser", jsMod.Browser)) diff --git a/tests/browser_type_test.go b/tests/browser_type_test.go index 260ab4c34..ba565466e 100644 --- a/tests/browser_type_test.go +++ b/tests/browser_type_test.go @@ -43,6 +43,7 @@ func TestBrowserTypeLaunchToConnect(t *testing.T) { require.Truef(t, ok, "unexpected default mod export type %T", mod.Exports().Default) vu.ActivateVU() + vu.StartIteration(t) rt := vu.Runtime() require.NoError(t, rt.Set("browser", jsMod.Browser))