Skip to content

Commit a43b490

Browse files
committed
fix(scaletest/dashboard): handle hanging when elem not visible
1 parent dc11705 commit a43b490

File tree

5 files changed

+41
-19
lines changed

5 files changed

+41
-19
lines changed

cli/exp_scaletest.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,9 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd {
10991099
}
11001100
ctx := inv.Context()
11011101
logger := slog.Make(sloghuman.Sink(inv.Stdout)).Leveled(slog.LevelInfo)
1102+
if r.verbose {
1103+
logger = logger.Leveled(slog.LevelDebug)
1104+
}
11021105
tracerProvider, closeTracing, tracingEnabled, err := tracingFlags.provider(ctx)
11031106
if err != nil {
11041107
return xerrors.Errorf("create tracer provider: %w", err)
@@ -1200,14 +1203,14 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd {
12001203
{
12011204
Flag: "interval",
12021205
Env: "CODER_SCALETEST_DASHBOARD_INTERVAL",
1203-
Default: "3s",
1206+
Default: "10s",
12041207
Description: "Interval between actions.",
12051208
Value: clibase.DurationOf(&interval),
12061209
},
12071210
{
12081211
Flag: "jitter",
12091212
Env: "CODER_SCALETEST_DASHBOARD_JITTER",
1210-
Default: "2s",
1213+
Default: "5s",
12111214
Description: "Jitter between actions.",
12121215
Value: clibase.DurationOf(&jitter),
12131216
},

scaletest/dashboard/chromedp.go

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,13 @@ var defaultTargets = []Target{
9090
// If no elements are found, an error is returned.
9191
// If more than one element is found, one is chosen at random.
9292
// The label of the clicked element is returned.
93-
func ClickRandomElement(ctx context.Context, randIntn func(int) int) (Label, Action, error) {
93+
func ClickRandomElement(ctx context.Context, log slog.Logger, randIntn func(int) int, deadline time.Time) (Label, Action, error) {
9494
var xpath Selector
9595
var found bool
9696
var err error
9797
matches := make([]Target, 0)
9898
for _, tgt := range defaultTargets {
99-
xpath, found, err = randMatch(ctx, tgt.ClickOn, randIntn)
99+
xpath, found, err = randMatch(ctx, log, tgt.ClickOn, randIntn, deadline)
100100
if err != nil {
101101
return "", nil, xerrors.Errorf("find matches for %q: %w", tgt.ClickOn, err)
102102
}
@@ -111,14 +111,20 @@ func ClickRandomElement(ctx context.Context, randIntn func(int) int) (Label, Act
111111
}
112112

113113
if len(matches) == 0 {
114+
log.Debug(ctx, "no matches found this time")
114115
return "", nil, xerrors.Errorf("no matches found")
115116
}
116117
match := pick(matches, randIntn)
117-
// rely on map iteration order being random
118118
act := func(actx context.Context) error {
119-
if err := clickAndWait(actx, match.ClickOn, match.WaitFor); err != nil {
119+
log.Debug(ctx, "clicking", slog.F("label", match.Label), slog.F("xpath", match.ClickOn))
120+
if err := runWithDeadline(ctx, deadline, chromedp.Click(match.ClickOn, chromedp.NodeReady)); err != nil {
121+
log.Error(ctx, "click failed", slog.F("label", match.Label), slog.F("xpath", match.ClickOn), slog.Error(err))
120122
return xerrors.Errorf("click %q: %w", match.ClickOn, err)
121123
}
124+
if err := runWithDeadline(ctx, deadline, chromedp.WaitReady(match.WaitFor)); err != nil {
125+
log.Error(ctx, "wait failed", slog.F("label", match.Label), slog.F("xpath", match.WaitFor), slog.Error(err))
126+
return xerrors.Errorf("wait for %q: %w", match.WaitFor, err)
127+
}
122128
return nil
123129
}
124130
return match.Label, act, nil
@@ -128,26 +134,32 @@ func ClickRandomElement(ctx context.Context, randIntn func(int) int) (Label, Act
128134
// The returned selector is the full XPath of the matched node.
129135
// If no matches are found, an error is returned.
130136
// If multiple matches are found, one is chosen at random.
131-
func randMatch(ctx context.Context, s Selector, randIntn func(int) int) (Selector, bool, error) {
137+
func randMatch(ctx context.Context, log slog.Logger, s Selector, randIntn func(int) int, deadline time.Time) (Selector, bool, error) {
132138
var nodes []*cdp.Node
133-
err := chromedp.Run(ctx, chromedp.Nodes(s, &nodes, chromedp.NodeVisible, chromedp.AtLeast(0)))
134-
if err != nil {
139+
log.Debug(ctx, "getting nodes for selector", slog.F("selector", s))
140+
if err := runWithDeadline(ctx, deadline, chromedp.Nodes(s, &nodes, chromedp.NodeReady, chromedp.AtLeast(0))); err != nil {
141+
log.Debug(ctx, "failed to get nodes for selector", slog.F("selector", s), slog.Error(err))
135142
return "", false, xerrors.Errorf("get nodes for selector %q: %w", s, err)
136143
}
137144
if len(nodes) == 0 {
145+
log.Debug(ctx, "no nodes found for selector", slog.F("selector", s))
138146
return "", false, nil
139147
}
140148
n := pick(nodes, randIntn)
149+
log.Debug(ctx, "found node", slog.F("node", n.FullXPath()))
141150
return Selector(n.FullXPath()), true, nil
142151
}
143152

144-
// clickAndWait clicks the given selector and waits for the page to finish loading.
145-
// The page is considered loaded when the network event "LoadingFinished" is received.
146-
func clickAndWait(ctx context.Context, clickOn, waitFor Selector) error {
147-
return chromedp.Run(ctx, chromedp.Tasks{
148-
chromedp.Click(clickOn, chromedp.NodeVisible),
149-
chromedp.WaitVisible(waitFor, chromedp.NodeVisible),
150-
})
153+
func waitForWorkspacesPageLoaded(ctx context.Context, deadline time.Time) error {
154+
return runWithDeadline(ctx, deadline, chromedp.WaitReady(`tbody.MuiTableBody-root`))
155+
}
156+
157+
func runWithDeadline(ctx context.Context, deadline time.Time, acts ...chromedp.Action) error {
158+
deadlineCtx, deadlineCancel := context.WithDeadline(ctx, deadline)
159+
defer deadlineCancel()
160+
c := chromedp.FromContext(ctx)
161+
tasks := chromedp.Tasks(acts)
162+
return tasks.Do(cdp.WithExecutor(deadlineCtx, c.Target))
151163
}
152164

153165
// initChromeDPCtx initializes a chromedp context with the given session token cookie

scaletest/dashboard/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type Config struct {
2121
// Headless controls headless mode for chromedp.
2222
Headless bool `json:"headless"`
2323
// ActionFunc is a function that returns an action to run.
24-
ActionFunc func(ctx context.Context, randIntn func(int) int) (Label, Action, error) `json:"-"`
24+
ActionFunc func(ctx context.Context, log slog.Logger, randIntn func(int) int, deadline time.Time) (Label, Action, error) `json:"-"`
2525
// RandIntn is a function that returns a random number between 0 and n-1.
2626
RandIntn func(int) int `json:"-"`
2727
}

scaletest/dashboard/run.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ func (r *Runner) Run(ctx context.Context, _ string, _ io.Writer) error {
5353
defer cdpCancel()
5454
t := time.NewTicker(1) // First one should be immediate
5555
defer t.Stop()
56+
r.cfg.Logger.Info(ctx, "waiting for workspaces page to load")
57+
loadWorkspacePageDeadline := time.Now().Add(r.cfg.Interval)
58+
if err := waitForWorkspacesPageLoaded(cdpCtx, loadWorkspacePageDeadline); err != nil {
59+
return xerrors.Errorf("wait for workspaces page to load: %w", err)
60+
}
5661
for {
5762
select {
5863
case <-cdpCtx.Done():
@@ -63,8 +68,9 @@ func (r *Runner) Run(ctx context.Context, _ string, _ io.Writer) error {
6368
offset = time.Duration(r.cfg.RandIntn(int(2*r.cfg.Jitter)) - int(r.cfg.Jitter))
6469
}
6570
wait := r.cfg.Interval + offset
71+
actionCompleteByDeadline := time.Now().Add(wait)
6672
t.Reset(wait)
67-
l, act, err := r.cfg.ActionFunc(cdpCtx, r.cfg.RandIntn)
73+
l, act, err := r.cfg.ActionFunc(cdpCtx, r.cfg.Logger, r.cfg.RandIntn, actionCompleteByDeadline)
6874
if err != nil {
6975
r.cfg.Logger.Error(ctx, "calling ActionFunc", slog.Error(err))
7076
continue

scaletest/dashboard/run_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/stretchr/testify/assert"
1212
"github.com/stretchr/testify/require"
1313

14+
"cdr.dev/slog"
1415
"cdr.dev/slog/sloggers/slogtest"
1516
"github.com/coder/coder/v2/coderd/coderdtest"
1617
"github.com/coder/coder/v2/scaletest/dashboard"
@@ -51,7 +52,7 @@ func Test_Run(t *testing.T) {
5152
Jitter: 100 * time.Millisecond,
5253
Logger: log,
5354
Headless: true,
54-
ActionFunc: func(_ context.Context, rnd func(int) int) (dashboard.Label, dashboard.Action, error) {
55+
ActionFunc: func(_ context.Context, _ slog.Logger, rnd func(int) int, _ time.Time) (dashboard.Label, dashboard.Action, error) {
5556
if rnd(2) == 0 {
5657
return "fails", failAction, nil
5758
}

0 commit comments

Comments
 (0)