Skip to content

Commit

Permalink
add RetryInterval option to query actions (#1159)
Browse files Browse the repository at this point in the history
A query action will retry every 5ms if it fails to get the expected nodes.
The family of Wait* functions depend on this behavior. The problem is,
when it waits too long, there will be many retries. This new option allows
the caller to change the default interval to reduce retries.

That said, the poll action is still preferable if the caller needs to wait.
  • Loading branch information
taylorchu committed Sep 17, 2022
1 parent d52f142 commit 90ef638
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 9 deletions.
30 changes: 21 additions & 9 deletions query.go
Expand Up @@ -26,12 +26,13 @@ type QueryAction Action
// See Query for information on building an element selector and relevant
// options.
type Selector struct {
sel interface{}
fromNode *cdp.Node
exp int
by func(context.Context, *cdp.Node) ([]cdp.NodeID, error)
wait func(context.Context, *cdp.Frame, runtime.ExecutionContextID, ...cdp.NodeID) ([]*cdp.Node, error)
after func(context.Context, runtime.ExecutionContextID, ...*cdp.Node) error
sel interface{}
fromNode *cdp.Node
retryInterval time.Duration
exp int
by func(context.Context, *cdp.Node) ([]cdp.NodeID, error)
wait func(context.Context, *cdp.Frame, runtime.ExecutionContextID, ...cdp.NodeID) ([]*cdp.Node, error)
after func(context.Context, runtime.ExecutionContextID, ...*cdp.Node) error
}

// Query is a query action that queries the browser for specific element
Expand Down Expand Up @@ -128,8 +129,9 @@ type Selector struct {
// element nodes matching the selector.
func Query(sel interface{}, opts ...QueryOption) QueryAction {
s := &Selector{
sel: sel,
exp: 1,
sel: sel,
exp: 1,
retryInterval: 5 * time.Millisecond,
}

// apply options
Expand All @@ -155,7 +157,7 @@ func (s *Selector) Do(ctx context.Context) error {
if t == nil {
return ErrInvalidTarget
}
return retryWithSleep(ctx, 5*time.Millisecond, func(ctx context.Context) (bool, error) {
return retryWithSleep(ctx, s.retryInterval, func(ctx context.Context) (bool, error) {
frame, root, execCtx, ok := t.ensureFrame()
if !ok {
return false, nil
Expand Down Expand Up @@ -553,6 +555,16 @@ func AtLeast(n int) QueryOption {
}
}

// RetryInterval is an element query action option to set the retry interval to specify
// how often it should retry when it failed to select the target element(s).
//
// The default value is 5ms.
func RetryInterval(interval time.Duration) QueryOption {
return func(s *Selector) {
s.retryInterval = interval
}
}

// After is an element query option that sets a func to execute after the
// matched nodes have been returned by the browser, and after the node
// condition is true.
Expand Down
62 changes: 62 additions & 0 deletions query_test.go
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/cdproto/css"
"github.com/chromedp/cdproto/dom"
cdpruntime "github.com/chromedp/cdproto/runtime"
"github.com/chromedp/chromedp/kb"
)

Expand Down Expand Up @@ -188,6 +189,67 @@ func TestAtLeast(t *testing.T) {
}
}

func TestRetryInterval(t *testing.T) {
t.Parallel()

tests := []struct {
name string
opts []QueryOption
wantCountMin int
wantCountMax int
}{
{
name: "default",
opts: []QueryOption{},
// in 100ms
wantCountMin: 10,
wantCountMax: 20,
},
{
name: "large interval",
opts: []QueryOption{RetryInterval(60 * time.Millisecond)},
// in 100ms
wantCountMin: 2,
wantCountMax: 2,
},
}

ctx, cancel := testAllocate(t, "js.html")
defer cancel()

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
retryCount := 0

// count is a wait function that makes the query always fail and
// counts the number of retries. Note that the wait func is called
// only after the number of result nodes >= s.exp .
count := WaitFunc(
func(ctx context.Context, f *cdp.Frame, eci cdpruntime.ExecutionContextID, ni ...cdp.NodeID) ([]*cdp.Node, error) {
retryCount += 1
return nil, ErrInvalidTarget
},
)

ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer cancel()

opts := append(tc.opts, count)
err := Run(ctx, Query("//input", opts...))

if err == nil || !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("want error context.DeadlineExceeded, got: %v", err)
}
if retryCount < tc.wantCountMin {
t.Fatalf("want retry count > %d, got: %d", tc.wantCountMin, retryCount)
}
if retryCount > tc.wantCountMax {
t.Fatalf("want retry count < %d, got: %d", tc.wantCountMax, retryCount)
}
})
}
}

func TestByJSPath(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit 90ef638

Please sign in to comment.