From a43b4903aa1940100f4ce5793ee1044cd7e31cc1 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 10 Oct 2023 15:56:47 +0000 Subject: [PATCH 01/10] fix(scaletest/dashboard): handle hanging when elem not visible --- cli/exp_scaletest.go | 7 ++++-- scaletest/dashboard/chromedp.go | 40 +++++++++++++++++++++------------ scaletest/dashboard/config.go | 2 +- scaletest/dashboard/run.go | 8 ++++++- scaletest/dashboard/run_test.go | 3 ++- 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/cli/exp_scaletest.go b/cli/exp_scaletest.go index 063cc827f06ff..0da1fb601c164 100644 --- a/cli/exp_scaletest.go +++ b/cli/exp_scaletest.go @@ -1099,6 +1099,9 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd { } ctx := inv.Context() logger := slog.Make(sloghuman.Sink(inv.Stdout)).Leveled(slog.LevelInfo) + if r.verbose { + logger = logger.Leveled(slog.LevelDebug) + } tracerProvider, closeTracing, tracingEnabled, err := tracingFlags.provider(ctx) if err != nil { return xerrors.Errorf("create tracer provider: %w", err) @@ -1200,14 +1203,14 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd { { Flag: "interval", Env: "CODER_SCALETEST_DASHBOARD_INTERVAL", - Default: "3s", + Default: "10s", Description: "Interval between actions.", Value: clibase.DurationOf(&interval), }, { Flag: "jitter", Env: "CODER_SCALETEST_DASHBOARD_JITTER", - Default: "2s", + Default: "5s", Description: "Jitter between actions.", Value: clibase.DurationOf(&jitter), }, diff --git a/scaletest/dashboard/chromedp.go b/scaletest/dashboard/chromedp.go index 6f90d6333907e..e0693e852a0e6 100644 --- a/scaletest/dashboard/chromedp.go +++ b/scaletest/dashboard/chromedp.go @@ -90,13 +90,13 @@ var defaultTargets = []Target{ // If no elements are found, an error is returned. // If more than one element is found, one is chosen at random. // The label of the clicked element is returned. -func ClickRandomElement(ctx context.Context, randIntn func(int) int) (Label, Action, error) { +func ClickRandomElement(ctx context.Context, log slog.Logger, randIntn func(int) int, deadline time.Time) (Label, Action, error) { var xpath Selector var found bool var err error matches := make([]Target, 0) for _, tgt := range defaultTargets { - xpath, found, err = randMatch(ctx, tgt.ClickOn, randIntn) + xpath, found, err = randMatch(ctx, log, tgt.ClickOn, randIntn, deadline) if err != nil { return "", nil, xerrors.Errorf("find matches for %q: %w", tgt.ClickOn, err) } @@ -111,14 +111,20 @@ func ClickRandomElement(ctx context.Context, randIntn func(int) int) (Label, Act } if len(matches) == 0 { + log.Debug(ctx, "no matches found this time") return "", nil, xerrors.Errorf("no matches found") } match := pick(matches, randIntn) - // rely on map iteration order being random act := func(actx context.Context) error { - if err := clickAndWait(actx, match.ClickOn, match.WaitFor); err != nil { + log.Debug(ctx, "clicking", slog.F("label", match.Label), slog.F("xpath", match.ClickOn)) + if err := runWithDeadline(ctx, deadline, chromedp.Click(match.ClickOn, chromedp.NodeReady)); err != nil { + log.Error(ctx, "click failed", slog.F("label", match.Label), slog.F("xpath", match.ClickOn), slog.Error(err)) return xerrors.Errorf("click %q: %w", match.ClickOn, err) } + if err := runWithDeadline(ctx, deadline, chromedp.WaitReady(match.WaitFor)); err != nil { + log.Error(ctx, "wait failed", slog.F("label", match.Label), slog.F("xpath", match.WaitFor), slog.Error(err)) + return xerrors.Errorf("wait for %q: %w", match.WaitFor, err) + } return nil } return match.Label, act, nil @@ -128,26 +134,32 @@ func ClickRandomElement(ctx context.Context, randIntn func(int) int) (Label, Act // The returned selector is the full XPath of the matched node. // If no matches are found, an error is returned. // If multiple matches are found, one is chosen at random. -func randMatch(ctx context.Context, s Selector, randIntn func(int) int) (Selector, bool, error) { +func randMatch(ctx context.Context, log slog.Logger, s Selector, randIntn func(int) int, deadline time.Time) (Selector, bool, error) { var nodes []*cdp.Node - err := chromedp.Run(ctx, chromedp.Nodes(s, &nodes, chromedp.NodeVisible, chromedp.AtLeast(0))) - if err != nil { + log.Debug(ctx, "getting nodes for selector", slog.F("selector", s)) + if err := runWithDeadline(ctx, deadline, chromedp.Nodes(s, &nodes, chromedp.NodeReady, chromedp.AtLeast(0))); err != nil { + log.Debug(ctx, "failed to get nodes for selector", slog.F("selector", s), slog.Error(err)) return "", false, xerrors.Errorf("get nodes for selector %q: %w", s, err) } if len(nodes) == 0 { + log.Debug(ctx, "no nodes found for selector", slog.F("selector", s)) return "", false, nil } n := pick(nodes, randIntn) + log.Debug(ctx, "found node", slog.F("node", n.FullXPath())) return Selector(n.FullXPath()), true, nil } -// clickAndWait clicks the given selector and waits for the page to finish loading. -// The page is considered loaded when the network event "LoadingFinished" is received. -func clickAndWait(ctx context.Context, clickOn, waitFor Selector) error { - return chromedp.Run(ctx, chromedp.Tasks{ - chromedp.Click(clickOn, chromedp.NodeVisible), - chromedp.WaitVisible(waitFor, chromedp.NodeVisible), - }) +func waitForWorkspacesPageLoaded(ctx context.Context, deadline time.Time) error { + return runWithDeadline(ctx, deadline, chromedp.WaitReady(`tbody.MuiTableBody-root`)) +} + +func runWithDeadline(ctx context.Context, deadline time.Time, acts ...chromedp.Action) error { + deadlineCtx, deadlineCancel := context.WithDeadline(ctx, deadline) + defer deadlineCancel() + c := chromedp.FromContext(ctx) + tasks := chromedp.Tasks(acts) + return tasks.Do(cdp.WithExecutor(deadlineCtx, c.Target)) } // initChromeDPCtx initializes a chromedp context with the given session token cookie diff --git a/scaletest/dashboard/config.go b/scaletest/dashboard/config.go index a2fd6255359e3..43338a7b44f91 100644 --- a/scaletest/dashboard/config.go +++ b/scaletest/dashboard/config.go @@ -21,7 +21,7 @@ type Config struct { // Headless controls headless mode for chromedp. Headless bool `json:"headless"` // ActionFunc is a function that returns an action to run. - ActionFunc func(ctx context.Context, randIntn func(int) int) (Label, Action, error) `json:"-"` + ActionFunc func(ctx context.Context, log slog.Logger, randIntn func(int) int, deadline time.Time) (Label, Action, error) `json:"-"` // RandIntn is a function that returns a random number between 0 and n-1. RandIntn func(int) int `json:"-"` } diff --git a/scaletest/dashboard/run.go b/scaletest/dashboard/run.go index 3210944882c04..15f59e7d57c1b 100644 --- a/scaletest/dashboard/run.go +++ b/scaletest/dashboard/run.go @@ -53,6 +53,11 @@ func (r *Runner) Run(ctx context.Context, _ string, _ io.Writer) error { defer cdpCancel() t := time.NewTicker(1) // First one should be immediate defer t.Stop() + r.cfg.Logger.Info(ctx, "waiting for workspaces page to load") + loadWorkspacePageDeadline := time.Now().Add(r.cfg.Interval) + if err := waitForWorkspacesPageLoaded(cdpCtx, loadWorkspacePageDeadline); err != nil { + return xerrors.Errorf("wait for workspaces page to load: %w", err) + } for { select { case <-cdpCtx.Done(): @@ -63,8 +68,9 @@ func (r *Runner) Run(ctx context.Context, _ string, _ io.Writer) error { offset = time.Duration(r.cfg.RandIntn(int(2*r.cfg.Jitter)) - int(r.cfg.Jitter)) } wait := r.cfg.Interval + offset + actionCompleteByDeadline := time.Now().Add(wait) t.Reset(wait) - l, act, err := r.cfg.ActionFunc(cdpCtx, r.cfg.RandIntn) + l, act, err := r.cfg.ActionFunc(cdpCtx, r.cfg.Logger, r.cfg.RandIntn, actionCompleteByDeadline) if err != nil { r.cfg.Logger.Error(ctx, "calling ActionFunc", slog.Error(err)) continue diff --git a/scaletest/dashboard/run_test.go b/scaletest/dashboard/run_test.go index 21850978d0510..3804dac4f0202 100644 --- a/scaletest/dashboard/run_test.go +++ b/scaletest/dashboard/run_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/scaletest/dashboard" @@ -51,7 +52,7 @@ func Test_Run(t *testing.T) { Jitter: 100 * time.Millisecond, Logger: log, Headless: true, - ActionFunc: func(_ context.Context, rnd func(int) int) (dashboard.Label, dashboard.Action, error) { + ActionFunc: func(_ context.Context, _ slog.Logger, rnd func(int) int, _ time.Time) (dashboard.Label, dashboard.Action, error) { if rnd(2) == 0 { return "fails", failAction, nil } From 4fece30dfe0cd80c3673b29691060d89857934c0 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 10 Oct 2023 18:11:32 +0000 Subject: [PATCH 02/10] feat(scaletest/dashboard): write screenshot on error --- scaletest/dashboard/chromedp.go | 22 ++++++++++++++++++++++ scaletest/dashboard/run.go | 10 ++++++++++ 2 files changed, 32 insertions(+) diff --git a/scaletest/dashboard/chromedp.go b/scaletest/dashboard/chromedp.go index e0693e852a0e6..1c5ed8f08bfff 100644 --- a/scaletest/dashboard/chromedp.go +++ b/scaletest/dashboard/chromedp.go @@ -2,8 +2,10 @@ package dashboard import ( "context" + "fmt" "net/url" "os" + "path/filepath" "time" "github.com/chromedp/cdproto/cdp" @@ -221,6 +223,26 @@ func visitMainPage(ctx context.Context, u *url.URL) error { return chromedp.Run(ctx, chromedp.Navigate(u.String())) } +func screenshot(ctx context.Context, name string) (string, error) { + var buf []byte + if err := chromedp.Run(ctx, chromedp.CaptureScreenshot(&buf)); err != nil { + return "", xerrors.Errorf("capture screenshot: %w", err) + } + fname := fmt.Sprintf("scaletest-dashboard-%s-%s.png", name, time.Now().Format("20060102-150405")) + pwd := os.Getenv("PWD") + fpath := filepath.Join(pwd, fname) + f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return "", xerrors.Errorf("open file: %w", err) + } + defer f.Close() + if _, err := f.Write(buf); err != nil { + return "", xerrors.Errorf("write file: %w", err) + } + + return fpath, nil +} + // pick chooses a random element from a slice. // If the slice is empty, it returns the zero value of the type. func pick[T any](s []T, randIntn func(int) int) T { diff --git a/scaletest/dashboard/run.go b/scaletest/dashboard/run.go index 15f59e7d57c1b..89c06f1eb3e19 100644 --- a/scaletest/dashboard/run.go +++ b/scaletest/dashboard/run.go @@ -73,6 +73,11 @@ func (r *Runner) Run(ctx context.Context, _ string, _ io.Writer) error { l, act, err := r.cfg.ActionFunc(cdpCtx, r.cfg.Logger, r.cfg.RandIntn, actionCompleteByDeadline) if err != nil { r.cfg.Logger.Error(ctx, "calling ActionFunc", slog.Error(err)) + sPath, sErr := screenshot(cdpCtx, me.Username) + if sErr != nil { + r.cfg.Logger.Error(ctx, "screenshot failed", slog.Error(sErr)) + } + r.cfg.Logger.Info(ctx, "screenshot saved", slog.F("path", sPath)) continue } start := time.Now() @@ -83,6 +88,11 @@ func (r *Runner) Run(ctx context.Context, _ string, _ io.Writer) error { r.metrics.IncErrors(string(l)) //nolint:gocritic r.cfg.Logger.Error(ctx, "action failed", slog.F("label", l), slog.Error(err)) + sPath, sErr := screenshot(cdpCtx, me.Username+"-"+string(l)) + if sErr != nil { + r.cfg.Logger.Error(ctx, "screenshot failed", slog.Error(sErr)) + } + r.cfg.Logger.Info(ctx, "screenshot saved", slog.F("path", sPath)) } else { //nolint:gocritic r.cfg.Logger.Info(ctx, "action success", slog.F("label", l)) From 1fe04018b15447ce6e241c93b07f39fbb84376ad Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 10 Oct 2023 18:11:48 +0000 Subject: [PATCH 03/10] fix logging tags --- cli/exp_scaletest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/exp_scaletest.go b/cli/exp_scaletest.go index 0da1fb601c164..f6ef0f09883d3 100644 --- a/cli/exp_scaletest.go +++ b/cli/exp_scaletest.go @@ -1160,7 +1160,7 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd { RandIntn: rndGen.Intn, } //nolint:gocritic - logger.Info(ctx, "runner config", slog.F("min_wait", interval), slog.F("max_wait", jitter), slog.F("headless", headless), slog.F("trace", tracingEnabled)) + logger.Info(ctx, "runner config", slog.F("interval", interval), slog.F("jitter", jitter), slog.F("headless", headless), slog.F("trace", tracingEnabled)) if err := config.Validate(); err != nil { return err } From e284d8c49feb739bf9c4b7ddcb681d74c39e6dd0 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 10 Oct 2023 18:12:02 +0000 Subject: [PATCH 04/10] force viewport size to avoid responsive mode --- scaletest/dashboard/chromedp.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scaletest/dashboard/chromedp.go b/scaletest/dashboard/chromedp.go index 1c5ed8f08bfff..e2a8e6cfa989f 100644 --- a/scaletest/dashboard/chromedp.go +++ b/scaletest/dashboard/chromedp.go @@ -192,6 +192,13 @@ func initChromeDPCtx(ctx context.Context, log slog.Logger, u *url.URL, sessionTo } } + // force a viewport size of 1024x768 so we don't go into mobile mode + if err := chromedp.Run(cdpCtx, chromedp.EmulateViewport(1024, 768)); err != nil { + cancelFunc() + allocCtxCancel() + return nil, nil, xerrors.Errorf("set viewport size: %w", err) + } + // set cookies if err := setSessionTokenCookie(cdpCtx, sessionToken, u.Host); err != nil { cancelFunc() From 87c67dfbf0d6c6c8d63a19987f294f63f39cad97 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 10 Oct 2023 21:55:44 +0000 Subject: [PATCH 05/10] "fix" dashboard test --- cli/exp_scaletest.go | 13 ++++++------- scaletest/dashboard/chromedp.go | 4 ++-- scaletest/dashboard/config.go | 4 ++++ scaletest/dashboard/run.go | 26 +++++++++++++++++++++++--- scaletest/dashboard/run_test.go | 13 +++++++++++++ 5 files changed, 48 insertions(+), 12 deletions(-) diff --git a/cli/exp_scaletest.go b/cli/exp_scaletest.go index f6ef0f09883d3..ce2ab7bfa59cd 100644 --- a/cli/exp_scaletest.go +++ b/cli/exp_scaletest.go @@ -1151,13 +1151,12 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd { userClient.SetSessionToken(userTokResp.Key) config := dashboard.Config{ - Interval: interval, - Jitter: jitter, - Trace: tracingEnabled, - Logger: logger.Named(name), - Headless: headless, - ActionFunc: dashboard.ClickRandomElement, - RandIntn: rndGen.Intn, + Interval: interval, + Jitter: jitter, + Trace: tracingEnabled, + Logger: logger.Named(name), + Headless: headless, + RandIntn: rndGen.Intn, } //nolint:gocritic logger.Info(ctx, "runner config", slog.F("interval", interval), slog.F("jitter", jitter), slog.F("headless", headless), slog.F("trace", tracingEnabled)) diff --git a/scaletest/dashboard/chromedp.go b/scaletest/dashboard/chromedp.go index e2a8e6cfa989f..23ed4eb50ae1b 100644 --- a/scaletest/dashboard/chromedp.go +++ b/scaletest/dashboard/chromedp.go @@ -88,11 +88,11 @@ var defaultTargets = []Target{ }, } -// ClickRandomElement returns an action that will click an element from defaultTargets. +// clickRandomElement returns an action that will click an element from defaultTargets. // If no elements are found, an error is returned. // If more than one element is found, one is chosen at random. // The label of the clicked element is returned. -func ClickRandomElement(ctx context.Context, log slog.Logger, randIntn func(int) int, deadline time.Time) (Label, Action, error) { +func clickRandomElement(ctx context.Context, log slog.Logger, randIntn func(int) int, deadline time.Time) (Label, Action, error) { var xpath Selector var found bool var err error diff --git a/scaletest/dashboard/config.go b/scaletest/dashboard/config.go index 43338a7b44f91..0397f52e390c5 100644 --- a/scaletest/dashboard/config.go +++ b/scaletest/dashboard/config.go @@ -22,6 +22,10 @@ type Config struct { Headless bool `json:"headless"` // ActionFunc is a function that returns an action to run. ActionFunc func(ctx context.Context, log slog.Logger, randIntn func(int) int, deadline time.Time) (Label, Action, error) `json:"-"` + // WaitLoaded is a function that waits for the page to be loaded. + WaitLoaded func(ctx context.Context, deadline time.Time) error + // Screenshot is a function that takes a screenshot. + Screenshot func(ctx context.Context, filename string) (string, error) // RandIntn is a function that returns a random number between 0 and n-1. RandIntn func(int) int `json:"-"` } diff --git a/scaletest/dashboard/run.go b/scaletest/dashboard/run.go index 89c06f1eb3e19..50c1f9ef7668a 100644 --- a/scaletest/dashboard/run.go +++ b/scaletest/dashboard/run.go @@ -2,6 +2,7 @@ package dashboard import ( "context" + "errors" "io" "time" @@ -25,6 +26,15 @@ var ( func NewRunner(client *codersdk.Client, metrics Metrics, cfg Config) *Runner { client.Trace = cfg.Trace + if cfg.WaitLoaded == nil { + cfg.WaitLoaded = waitForWorkspacesPageLoaded + } + if cfg.ActionFunc == nil { + cfg.ActionFunc = clickRandomElement + } + if cfg.Screenshot == nil { + cfg.Screenshot = screenshot + } return &Runner{ client: client, cfg: cfg, @@ -33,6 +43,16 @@ func NewRunner(client *codersdk.Client, metrics Metrics, cfg Config) *Runner { } func (r *Runner) Run(ctx context.Context, _ string, _ io.Writer) error { + err := r._run(ctx) + // If the context deadline exceeded, don't return an error. + // This just means the test finished. + if err == nil || errors.Is(err, context.DeadlineExceeded) { + return nil + } + return err +} + +func (r *Runner) _run(ctx context.Context) error { if r.client == nil { return xerrors.Errorf("client is nil") } @@ -55,7 +75,7 @@ func (r *Runner) Run(ctx context.Context, _ string, _ io.Writer) error { defer t.Stop() r.cfg.Logger.Info(ctx, "waiting for workspaces page to load") loadWorkspacePageDeadline := time.Now().Add(r.cfg.Interval) - if err := waitForWorkspacesPageLoaded(cdpCtx, loadWorkspacePageDeadline); err != nil { + if err := r.cfg.WaitLoaded(cdpCtx, loadWorkspacePageDeadline); err != nil { return xerrors.Errorf("wait for workspaces page to load: %w", err) } for { @@ -73,7 +93,7 @@ func (r *Runner) Run(ctx context.Context, _ string, _ io.Writer) error { l, act, err := r.cfg.ActionFunc(cdpCtx, r.cfg.Logger, r.cfg.RandIntn, actionCompleteByDeadline) if err != nil { r.cfg.Logger.Error(ctx, "calling ActionFunc", slog.Error(err)) - sPath, sErr := screenshot(cdpCtx, me.Username) + sPath, sErr := r.cfg.Screenshot(cdpCtx, me.Username) if sErr != nil { r.cfg.Logger.Error(ctx, "screenshot failed", slog.Error(sErr)) } @@ -88,7 +108,7 @@ func (r *Runner) Run(ctx context.Context, _ string, _ io.Writer) error { r.metrics.IncErrors(string(l)) //nolint:gocritic r.cfg.Logger.Error(ctx, "action failed", slog.F("label", l), slog.Error(err)) - sPath, sErr := screenshot(cdpCtx, me.Username+"-"+string(l)) + sPath, sErr := r.cfg.Screenshot(cdpCtx, me.Username+"-"+string(l)) if sErr != nil { r.cfg.Logger.Error(ctx, "screenshot failed", slog.Error(sErr)) } diff --git a/scaletest/dashboard/run_test.go b/scaletest/dashboard/run_test.go index 3804dac4f0202..af9725e201085 100644 --- a/scaletest/dashboard/run_test.go +++ b/scaletest/dashboard/run_test.go @@ -5,6 +5,7 @@ import ( "math/rand" "runtime" "sync" + "sync/atomic" "testing" "time" @@ -47,17 +48,29 @@ func Test_Run(t *testing.T) { IgnoreErrors: true, }) m := &testMetrics{} + var ( + waitLoadedCalled atomic.Bool + screenshotCalled atomic.Bool + ) cfg := dashboard.Config{ Interval: 500 * time.Millisecond, Jitter: 100 * time.Millisecond, Logger: log, Headless: true, + WaitLoaded: func(_ context.Context, _ time.Time) error { + waitLoadedCalled.Store(true) + return nil + }, ActionFunc: func(_ context.Context, _ slog.Logger, rnd func(int) int, _ time.Time) (dashboard.Label, dashboard.Action, error) { if rnd(2) == 0 { return "fails", failAction, nil } return "succeeds", successAction, nil }, + Screenshot: func(_ context.Context, name string) (string, error) { + screenshotCalled.Store(true) + return "/fake/path/to/" + name + ".png", nil + }, RandIntn: rg.Intn, } r := dashboard.NewRunner(client, m, cfg) From 4be6c51436d195f9220c735dec2f98ffac0f5cc7 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 10 Oct 2023 22:00:07 +0000 Subject: [PATCH 06/10] only screenshot in verbose mode --- cli/exp_scaletest.go | 5 +++++ scaletest/dashboard/chromedp.go | 2 +- scaletest/dashboard/run.go | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cli/exp_scaletest.go b/cli/exp_scaletest.go index ce2ab7bfa59cd..3154e96ddb7ef 100644 --- a/cli/exp_scaletest.go +++ b/cli/exp_scaletest.go @@ -1158,6 +1158,11 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd { Headless: headless, RandIntn: rndGen.Intn, } + // Only take a screenshot if we're in verbose mode. + // This could be useful for debugging, but it will blow up the disk. + if r.verbose { + config.Screenshot = dashboard.Screenshot + } //nolint:gocritic logger.Info(ctx, "runner config", slog.F("interval", interval), slog.F("jitter", jitter), slog.F("headless", headless), slog.F("trace", tracingEnabled)) if err := config.Validate(); err != nil { diff --git a/scaletest/dashboard/chromedp.go b/scaletest/dashboard/chromedp.go index 23ed4eb50ae1b..9affaeb6c3317 100644 --- a/scaletest/dashboard/chromedp.go +++ b/scaletest/dashboard/chromedp.go @@ -230,7 +230,7 @@ func visitMainPage(ctx context.Context, u *url.URL) error { return chromedp.Run(ctx, chromedp.Navigate(u.String())) } -func screenshot(ctx context.Context, name string) (string, error) { +func Screenshot(ctx context.Context, name string) (string, error) { var buf []byte if err := chromedp.Run(ctx, chromedp.CaptureScreenshot(&buf)); err != nil { return "", xerrors.Errorf("capture screenshot: %w", err) diff --git a/scaletest/dashboard/run.go b/scaletest/dashboard/run.go index 50c1f9ef7668a..6fde88d350aa6 100644 --- a/scaletest/dashboard/run.go +++ b/scaletest/dashboard/run.go @@ -33,7 +33,7 @@ func NewRunner(client *codersdk.Client, metrics Metrics, cfg Config) *Runner { cfg.ActionFunc = clickRandomElement } if cfg.Screenshot == nil { - cfg.Screenshot = screenshot + cfg.Screenshot = Screenshot } return &Runner{ client: client, From 47b4bfc2dbcf24bc620c78c9542cf752eef3daea Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 10 Oct 2023 22:02:11 +0000 Subject: [PATCH 07/10] fumpt --- scaletest/dashboard/chromedp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scaletest/dashboard/chromedp.go b/scaletest/dashboard/chromedp.go index 9affaeb6c3317..0b5dd771bf64e 100644 --- a/scaletest/dashboard/chromedp.go +++ b/scaletest/dashboard/chromedp.go @@ -238,7 +238,7 @@ func Screenshot(ctx context.Context, name string) (string, error) { fname := fmt.Sprintf("scaletest-dashboard-%s-%s.png", name, time.Now().Format("20060102-150405")) pwd := os.Getenv("PWD") fpath := filepath.Join(pwd, fname) - f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, 0644) + f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return "", xerrors.Errorf("open file: %w", err) } From 017c57e8170736228ff6cdf4e7ba7a6819883242 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 11 Oct 2023 10:26:45 +0100 Subject: [PATCH 08/10] add random ext to screenshots --- scaletest/dashboard/chromedp.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scaletest/dashboard/chromedp.go b/scaletest/dashboard/chromedp.go index 0b5dd771bf64e..1c6ba570b5d0b 100644 --- a/scaletest/dashboard/chromedp.go +++ b/scaletest/dashboard/chromedp.go @@ -13,6 +13,8 @@ import ( "github.com/chromedp/chromedp" "golang.org/x/xerrors" + "github.com/coder/coder/v2/cryptorand" + "cdr.dev/slog" ) @@ -235,7 +237,12 @@ func Screenshot(ctx context.Context, name string) (string, error) { if err := chromedp.Run(ctx, chromedp.CaptureScreenshot(&buf)); err != nil { return "", xerrors.Errorf("capture screenshot: %w", err) } - fname := fmt.Sprintf("scaletest-dashboard-%s-%s.png", name, time.Now().Format("20060102-150405")) + randExt, err := cryptorand.String(4) + if err != nil { + // this should never happen + return "", xerrors.Errorf("generate random string: %w", err) + } + fname := fmt.Sprintf("scaletest-dashboard-%s-%s-%s.png", name, time.Now().Format("20060102-150405"), randExt) pwd := os.Getenv("PWD") fpath := filepath.Join(pwd, fname) f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, 0o644) From 5d5db72a3140d09b415bd7786c8f3582fb7750bf Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 11 Oct 2023 10:28:09 +0100 Subject: [PATCH 09/10] use os.GetWd instead --- scaletest/dashboard/chromedp.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scaletest/dashboard/chromedp.go b/scaletest/dashboard/chromedp.go index 1c6ba570b5d0b..d4d944a845071 100644 --- a/scaletest/dashboard/chromedp.go +++ b/scaletest/dashboard/chromedp.go @@ -243,7 +243,10 @@ func Screenshot(ctx context.Context, name string) (string, error) { return "", xerrors.Errorf("generate random string: %w", err) } fname := fmt.Sprintf("scaletest-dashboard-%s-%s-%s.png", name, time.Now().Format("20060102-150405"), randExt) - pwd := os.Getenv("PWD") + pwd, err := os.Getwd() + if err != nil { + return "", xerrors.Errorf("get working directory: %w", err) + } fpath := filepath.Join(pwd, fname) f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { From b044c4e7e1cd4a315dd15cbb6165b702491566e5 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 11 Oct 2023 10:29:44 +0100 Subject: [PATCH 10/10] rename func --- scaletest/dashboard/run.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scaletest/dashboard/run.go b/scaletest/dashboard/run.go index 6fde88d350aa6..fd7ce95b63adf 100644 --- a/scaletest/dashboard/run.go +++ b/scaletest/dashboard/run.go @@ -43,7 +43,7 @@ func NewRunner(client *codersdk.Client, metrics Metrics, cfg Config) *Runner { } func (r *Runner) Run(ctx context.Context, _ string, _ io.Writer) error { - err := r._run(ctx) + err := r.runUntilDeadlineExceeded(ctx) // If the context deadline exceeded, don't return an error. // This just means the test finished. if err == nil || errors.Is(err, context.DeadlineExceeded) { @@ -52,7 +52,7 @@ func (r *Runner) Run(ctx context.Context, _ string, _ io.Writer) error { return err } -func (r *Runner) _run(ctx context.Context) error { +func (r *Runner) runUntilDeadlineExceeded(ctx context.Context) error { if r.client == nil { return xerrors.Errorf("client is nil") }