Skip to content

Commit

Permalink
Add API for explicitly waiting for a condition to become true (#91)
Browse files Browse the repository at this point in the history
Without this change, test developers are required to roll their own loops to poll when a condition becomes true. This API addition provides some syntactic sugar to cut down on boilerplate.
  • Loading branch information
serge1peshcoff authored and minusnine committed Aug 21, 2017
1 parent 4adf11e commit 988bde7
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ selenium-server-standalone-*.jar
chromedriver*
vendor/*
!vendor/*.go
coverage.txt
coverage.out
39 changes: 39 additions & 0 deletions remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,45 @@ func (wd *remoteWD) Screenshot() ([]byte, error) {
return ioutil.ReadAll(decoder)
}

// Condition is an alias for a type that is passed as an argument
// for selenium.Wait(cond Condition) (error) function.
type Condition func(wd WebDriver) (bool, error)

const (
// Default polling interval for selenium.Wait function.
DefaultWaitInterval = 100 * time.Millisecond

// Default timeout for selenium.Wait function.
DefaultWaitTimeout = 60 * time.Second
)

func (wd *remoteWD) WaitWithTimeoutAndInterval(condition Condition, timeout, interval time.Duration) error {
startTime := time.Now()

for {
done, err := condition(wd)
if err != nil {
return err
}
if done {
return nil
}

if elapsed := time.Since(startTime); elapsed > timeout {
return fmt.Errorf("timeout after %v", elapsed)
}
time.Sleep(interval)
}
}

func (wd *remoteWD) WaitWithTimeout(condition Condition, timeout time.Duration) error {
return wd.WaitWithTimeoutAndInterval(condition, timeout, DefaultWaitInterval)
}

func (wd *remoteWD) Wait(condition Condition) error {
return wd.WaitWithTimeoutAndInterval(condition, DefaultWaitTimeout, DefaultWaitInterval)
}

func (wd *remoteWD) Log(typ log.Type) ([]log.Message, error) {
url := wd.requestURL("/session/%s/log", wd.id)
params := map[string]log.Type{
Expand Down
53 changes: 53 additions & 0 deletions remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ func runTests(t *testing.T, c config) {
t.Run("CSSProperty", runTest(testCSSProperty, c))
t.Run("Proxy", runTest(testProxy, c))
t.Run("SwitchFrame", runTest(testSwitchFrame, c))
t.Run("Wait", runTest(testWait, c))
t.Run("ActiveElement", runTest(testActiveElement, c))
}

Expand Down Expand Up @@ -1633,6 +1634,42 @@ func testSwitchFrame(t *testing.T, c config) {
}
}

func testWait(t *testing.T, c config) {
const newTitle = "Title changed."
titleChangeCondition := func(wd WebDriver) (bool, error) {
title, err := wd.Title()
if err != nil {
return false, err
}

return title == newTitle, nil
}

wd := newRemote(t, c)
defer quitRemote(t, wd)

titleURL := serverURL + "/title"

if err := wd.Get(titleURL); err != nil {
t.Fatalf("wd.Get(%q) returned error: %v", titleURL, err)
}

// Testing when the title should change.
if err := wd.Wait(titleChangeCondition); err != nil {
t.Fatalf("wd.Wait(titleChangeCondition) returned error: %v", err)
}

// Reloading the page.
if err := wd.Get(titleURL); err != nil {
t.Fatalf("wd.Get(%q) returned error: %v", titleURL, err)
}

// Testing when the Wait() should error the timeout..
if err := wd.WaitWithTimeout(titleChangeCondition, 500*time.Millisecond); err == nil {
t.Fatalf("wd.Wait(titleChangeCondition) should returned error, but it didn't.")
}
}

var homePage = `
<html>
<head>
Expand Down Expand Up @@ -1708,6 +1745,21 @@ var framePage = `
</html>
`

var titleChangePage = `
<html>
<head>
<title>Go Selenium Test Suite - Title Change Page</title>
</head>
<body>
This page will change a title after 1 second.
<script>
setTimeout(function() { document.title = 'Title changed.' }, 1000);
</script>
</body>
</html>
`

func handler(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
page, ok := map[string]string{
Expand All @@ -1716,6 +1768,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
"/search": searchPage,
"/log": logPage,
"/frame": framePage,
"/title": titleChangePage,
}[path]
if !ok {
http.NotFound(w, r)
Expand Down
9 changes: 9 additions & 0 deletions selenium.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,15 @@ type WebDriver interface {
// ExecuteScriptAsyncRaw asynchronously executes a script but does not
// perform JSON decoding.
ExecuteScriptAsyncRaw(script string, args []interface{}) ([]byte, error)

// WaitWithTimeoutAndInterval waits for the condition to evaluate to true.
WaitWithTimeoutAndInterval(condition Condition, timeout, interval time.Duration) error

// WaitWithTimeout works like WaitWithTimeoutAndInterval, but with default polling interval.
WaitWithTimeout(condition Condition, timeout time.Duration) error

//Wait works like WaitWithTimeoutAndInterval, but using the default timeout and polling interval.
Wait(condition Condition) error
}

// WebElement defines method supported by web elements.
Expand Down

0 comments on commit 988bde7

Please sign in to comment.