Commits

Miki Tebeka committed d2271eb Merge

merge from default

  • Participants
  • Parent commits 9ed70ff, 0084861
  • Branches dev

Comments (0)

Files changed (15)

 7735844d76be789e804869bdcbc375ba6088cd1d 0.1.3
 a6c58f300ea52fbff3b15129cfda4673e62b5d20 0.2.1
 0b09b7f2c54e3a876288f73aa94b2ed116827076 0.2.2
+9a2c757a1b231836bee84da4262fa13a54e73295 r.60
+6c03b057bbb3fc2719d4915a3c4649b6789dbbc7 0.3.0
+3952038b147f56ccb7b67b4e82b4ca18c7298810 0.4.0
+fcae16a3dcd3ca13ffd681d3a5bb6eebaf09cef5 0.5.0
+1eacd12b95785a74cf9c00c01817a666ea3901c6 0.6.0
+647b5c75be050a0bc770fb1e2130a7ea756bbb98 0.6.1
+c5f954c92a3a61a46ce8a19d296d268137e7f263 0.7.0
+2014-01-28 version 0.8.0
+    * GetHTTPClient for AppEngine support (issue #8)
+
+2013-11-02 version 0.7.0
+    * Added SessionId to Selenium interface
+    
+2013-10-09 version 0.6.1
+    * Fix panic when stringCommand return value is nil (issue #6)
+
+2012-04-10 version 0.6.0
+    * ExecuteScriptRaw, ExecuteScriptAsyncRaw (Naitik Shah)
+    * DecodeElement in API (Naitik Shah)
+
+2012-04-07 version 0.5.0
+    * IsDiaplayed -> IsDisplayed (thanks Naitik Shah)
+    * VERSION is a const now
+
+2012-03-29 version 0.4.0
+    * Fix bug in /status
+    * timeouts are in time.Duration
+
+2012-02-13 version 0.3.0
+    * Go 1
+    * Compatible with `go get`
+    * README teaks
+
 2011-09-27 version 0.2.2
-	* All keys from http://bit.ly/p8SIrD
+    * All keys from http://bit.ly/p8SIrD
 
 2011-09-27 version 0.2.1
-	* Capabilities, SetAsyncScriptTimeout, SetImplicitWaitTimeout, CloseWindow
-	* IME commands
-	* Use selenium 2.7.0 for testing
+    * Capabilities, SetAsyncScriptTimeout, SetImplicitWaitTimeout, CloseWindow
+    * IME commands
+    * Use selenium 2.7.0 for testing
 
 2011-09-21 version 0.2.0
     * Moved profileDir out of NewRemote
-Copyright (c) 2011 Miki Tebeka <miki.tebeka@gmail.com>.
+Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
-include $(GOROOT)/src/Make.inc
+export GOPATH := $(shell dirname $(shell dirname $(PWD)))
+PACKAGE := selenium
 
-TARG=selenium
+all:
+	go build $(PACKAGE)
 
-GOFILES= \
-	selenium.go \
-	remote.go \
-	common.go 
+test:
+	@echo WARNING: You probably want to use run-tests.sh
+	go test -v $(PACKAGE)
 
-include $(GOROOT)/src/Make.pkg
+fix:
+	go fix $(PACKAGE)
+
+doc:
+	go doc $(PACKAGE)
+
+install:
+	go install $(PACKAGE)
+
+README.html: README.md
+	markdown $< > $@
+
+.PHONY: all test install fix doc
+`selenium` - Selenium Client For Go
+
+# About
+This is a [Selenium][selenium] client for [Go][go].
+Currently it supports only the remote WebDriver client, so you'll need a
+[selenium server][server] running.
+
+[selenium]: http://seleniumhq.org/
+[go]: http://golang.org/
+[server]: http://seleniumhq.org/download/
+
+# Installing
+Run
+
+    go get bitbucket.org/tebeka/selenium
+
+# Docs
+Docs are at [godoc.org][godoc]. 
+
+[godoc]: http://godoc.org/bitbucket.org/tebeka/selenium
+
+## AppEngine
+
+`GetHTTPClient` exposes the HTTP client used by the driver. You can access it to
+add the request context.
+
+    func myRequestHandler(w http.ResponseWriter, r *http.Request) {
+        selenium.GetHTTPClient().Transport = &urlfetch.Transport{
+            Context:  appengine.NewContext(r),
+            Deadline: 30 * time.Second,
+        }
+        ...
+    }
+
+Thanks to [bthomson](https://bitbucket.org/tebeka/selenium/issue/8) for this
+one.
+
+# Changes
+See [here][changelog].
+
+[changelog]: ChangeLog
+
+# TODO
+* Support Firefox profiles
+* Finish full [Selenium API][api].
+* More testing
+* [Selenium 1][sel1] client
+* Our own server for testing (getting out takes too much time)
+* [SauceLabs][sauce] integration
+
+[api]: http://code.google.com/p/selenium/wiki/JsonWireProtocol
+[sel1]: http://wiki.openqa.org/display/SRC/Specifications+for+Selenium+Remote+Control+Client+Driver+Protocol
+[sauce]: http://saucelabs.com/docs/quickstart
+
+# Hacking
+
+* You'll need a Selenium server to run the tests, run `selenium.sh download` to
+  get it and `selenium.sh start` to run it.
+* Test with `./run-tests.sh`.
+* I (Miki) work on `dev` branch since `go get` pull from default.
+
+# License
+[MIT][mit]
+
+[mit]: https://bitbucket.org/tebeka/selenium/src/tip/LICENSE.txt

README.rst

-=================================
-selenium - Selenium Client For Go
-=================================
-
-About
-=====
-This is a `Selenium`_ client for `Go`_.
-Currently it supports only the remote WebDriver client, so you'll need a
-`selenium server`_ running.
-
-.. _`Selenium`: http://seleniumhq.org/
-.. _`Go`: http://golang.org/
-.. _`selenium server`: http://seleniumhq.org/download/
-
-
-Changes
-=======
-See here_.
-
-.. _here: ChangeLog
-
-TODO
-====
-* Support Firefox profiles
-* Finish full `Selenium API`_.
-* More testing
-* `Selenium 1`_ client
-* Our own server for testing (getting out takes too much time)
-* `SauceLabs`_ integration
-
-.. _`Selenium API`: http://code.google.com/p/selenium/wiki/JsonWireProtocol
-.. _`SauceLabs`: http://saucelabs.com/docs/quickstart
-.. _`Selenium 1`: http://wiki.openqa.org/display/SRC/Specifications+for+Selenium+Remote+Control+Client+Driver+Protocol
-
-Hacking
-=======
-You'll need a Selenium server to run the tests, run `selenium.sh download` to
-get it and `selenium.sh start` to run it.
-
-Test with `gotest -v`.
-
-I (Miki) work on `dev` branch since `goinstall` pull from default.
-
-Authors
-=======
-
-* Miki Tebeka <miki.tebeka@gmail.com>
-
-
-License
-=======
-Copyright (C) 2010 Miki Tebeka <miki.tebeka@gmail.com>
-
-Distributed under the Eclipse Public License, the same as Clojure.
 	}
 	log.Printf(format+"\n", args...)
 }
-
+/*
+Selenium/Webdriver client.
+
+Currently provides only WebDriver remote client.
+This means you'll need to run the Selenium server by yourself (or use a service
+like SauceLabs). The easiest way to do that is to grab the Selenium server jar
+from http://selenium.googlecode.com/files and run it
+	java -jar selenium-server-standalone-2.24.1.jar
+
+Example usage:
+
+	// Run some code on play.golang.org and display the result
+	package main
+
+	import (
+		"fmt"
+		"time"
+
+		"bitbucket.org/tebeka/selenium"
+	)
+
+	var code string = `
+	package main
+	import "fmt"
+
+	func main() {
+		fmt.Println("Hello WebDriver!\n")
+	}
+	`
+
+	// Errors are ignored for brevity.
+
+	func main() {
+		// FireFox driver without specific version
+		caps := selenium.Capabilities{"browserName": "firefox"}
+		wd, _ := selenium.NewRemote(caps, "")
+		defer wd.Quit()
+
+		// Get simple playground interface
+		wd.Get("http://play.golang.org/?simple=1")
+
+		// Enter code in textarea
+		elem, _ := wd.FindElement(selenium.ByCSSSelector, "#code")
+		elem.Clear()
+		elem.SendKeys(code)
+
+		// Click the run button
+		btn, _ := wd.FindElement(selenium.ByCSSSelector, "#run")
+		btn.Click()
+
+		// Get the result
+		div, _ := wd.FindElement(selenium.ByCSSSelector, "#output")
+
+		output := ""
+		// Wait for run to finish
+		for {
+			output, _ = div.Text()
+			if output != "Waiting for remote server..." {
+				break
+			}
+			time.Sleep(time.Millisecond * 100)
+		}
+
+		fmt.Printf("Got: %s\n", output)
+	}
+*/
+package selenium
 package selenium
 
-var defaultProfile = map[string][string] {
-	"app.update.auto": "false",
-	"app.update.enabled": "false",
-	"browser.startup.page" : "0",
+var defaultProfile = map[string]string{
+	"app.update.auto":                           "false",
+	"app.update.enabled":                        "false",
+	"browser.startup.page":                      "0",
 	"browser.download.manager.showWhenStarting": "false",
-	"browser.EULA.override": "true",
-	"browser.EULA.3.accepted": "true",
-	"browser.link.open_external": "2",
-	"browser.link.open_newwindow": "2",
-	"browser.offline": "false",
-	"browser.safebrowsing.enabled": "false",
-	"browser.search.update": "false",
-	"browser.sessionstore.resume_from_crash": "false",
-	"browser.shell.checkDefaultBrowser": "false",
-	"browser.tabs.warnOnClose": "false",
-	"browser.tabs.warnOnOpen": "false",
-	"browser.startup.page": "0",
-	"startup.homepage_welcome_url": "\"about:blank\"",
-	"devtools.errorconsole.enabled": "true",
-	"dom.disable_open_during_load": "false",
-	"dom.max_script_run_time": "30",
-	"extensions.logging.enabled": "true",
-	"extensions.update.enabled": "false",
-	"extensions.update.notifyUser": "false",
-	"network.manage-offline-status": "false",
-	"network.http.max-connections-per-server": "10",
-	"network.http.phishy-userpass-length": "255",
-	"prompts.tab_modal.enabled": "false",
-	"security.fileuri.origin_policy": "3",
-	"security.fileuri.strict_origin_policy": "false",
-	"security.warn_entering_secure": "false",
-	"security.warn_submit_insecure": "false",
-	"security.warn_entering_secure.show_once": "false",
-	"security.warn_entering_weak": "false",
-	"security.warn_entering_weak.show_once": "false",
-	"security.warn_leaving_secure": "false",
-	"security.warn_leaving_secure.show_once": "false",
-	"security.warn_submit_insecure": "false",
-	"security.warn_viewing_mixed": "false",
-	"security.warn_viewing_mixed.show_once": "false",
-	"signon.rememberSignons": "false",
-	"toolkit.networkmanager.disable": "true",
-	"javascript.options.showInConsole": "true",
-	"browser.dom.window.dump.enabled": "true",
-	"webdriver_accept_untrusted_certs": "true",
-	"webdriver_enable_native_events": "true",
-	"dom.max_script_run_time": "30",
+	"browser.EULA.override":                     "true",
+	"browser.EULA.3.accepted":                   "true",
+	"browser.link.open_external":                "2",
+	"browser.link.open_newwindow":               "2",
+	"browser.offline":                           "false",
+	"browser.safebrowsing.enabled":              "false",
+	"browser.search.update":                     "false",
+	"browser.sessionstore.resume_from_crash":    "false",
+	"browser.shell.checkDefaultBrowser":         "false",
+	"browser.tabs.warnOnClose":                  "false",
+	"browser.tabs.warnOnOpen":                   "false",
+	"startup.homepage_welcome_url":              "\"about:blank\"",
+	"devtools.errorconsole.enabled":             "true",
+	"dom.disable_open_during_load":              "false",
+	"dom.max_script_run_time":                   "30",
+	"extensions.logging.enabled":                "true",
+	"extensions.update.enabled":                 "false",
+	"extensions.update.notifyUser":              "false",
+	"network.manage-offline-status":             "false",
+	"network.http.max-connections-per-server":   "10",
+	"network.http.phishy-userpass-length":       "255",
+	"prompts.tab_modal.enabled":                 "false",
+	"security.fileuri.origin_policy":            "3",
+	"security.fileuri.strict_origin_policy":     "false",
+	"security.warn_entering_secure":             "false",
+	"security.warn_submit_insecure":             "false",
+	"security.warn_entering_secure.show_once":   "false",
+	"security.warn_entering_weak":               "false",
+	"security.warn_entering_weak.show_once":     "false",
+	"security.warn_leaving_secure":              "false",
+	"security.warn_leaving_secure.show_once":    "false",
+	"security.warn_viewing_mixed":               "false",
+	"security.warn_viewing_mixed.show_once":     "false",
+	"signon.rememberSignons":                    "false",
+	"toolkit.networkmanager.disable":            "true",
+	"javascript.options.showInConsole":          "true",
+	"browser.dom.window.dump.enabled":           "true",
+	"webdriver_accept_untrusted_certs":          "true",
+	"webdriver_enable_native_events":            "true",
 }
 
-type FirefoxProfile {
+type FirefoxProfile struct {
 	Root string
 }
-

push-to-github.sh

+#!/bin/bash
+
+hg bookmark -r default master
+hg push git+ssh://git@github.com/tebeka/selenium.git
 import (
 	"bytes"
 	"encoding/base64"
+	"encoding/json"
+	"errors"
 	"fmt"
-	"http"
 	"io/ioutil"
-	"json"
-	"os"
+	"net/http"
+	"net/url"
 	"strings"
+	"time"
 )
 
 /* Errors returned by Selenium server. */
-var errors = map[int]string{
+var errors_ = map[int]string{
 	7:  "no such element",
 	8:  "no such frame",
 	9:  "unknown command",
 	SUCCESS          = 0
 	DEFAULT_EXECUTOR = "http://127.0.0.1:4444/wd/hub"
 	JSON_TYPE        = "application/json"
+	MAX_REDIRECTS    = 10
 )
 
 type remoteWD struct {
 	Value Capabilities
 }
 
+var httpClient *http.Client
+
+func GetHTTPClient() *http.Client {
+	return httpClient
+}
+
 func isMimeType(response *http.Response, mtype string) bool {
 	if ctype, ok := response.Header["Content-Type"]; ok {
 		return strings.HasPrefix(ctype[0], mtype)
 	return false
 }
 
-func newRequest(method string, url string, data []byte) (*http.Request, os.Error) {
+func newRequest(method string, url string, data []byte) (*http.Request, error) {
 	request, err := http.NewRequest(method, url, bytes.NewBuffer(data))
 	if err != nil {
 		return nil, err
 	return false
 }
 
+func normalizeURL(n string, base string) (string, error) {
+	baseURL, err := url.Parse(base)
+	if err != nil {
+		return "", fmt.Errorf(
+			"Failed to parse base URL %s with error %s", base, err)
+	}
+	nURL, err := baseURL.Parse(n)
+	if err != nil {
+		return "", fmt.Errorf("Failed to parse new URL %s with error %s", n, err)
+	}
+	return nURL.String(), nil
+}
+
 func (wd *remoteWD) requestURL(template string, args ...interface{}) string {
 	path := fmt.Sprintf(template, args...)
 	return wd.executor + path
 }
 
-func (wd *remoteWD) execute(method, url string, data []byte) ([]byte, os.Error) {
+func (wd *remoteWD) execute(method, url string, data []byte) ([]byte, error) {
 	debugLog("-> %s %s\n%s", method, url, data)
 	request, err := newRequest(method, url, data)
 	if err != nil {
 		return nil, err
 	}
 
-	response, err := http.DefaultClient.Do(request)
+	response, err := httpClient.Do(request)
 	if err != nil {
 		return nil, err
 	}
 
-	// http.Client don't follow POST redirects ....
-	if (method == "POST") && isRedirect(response) {
-		url := response.Header["Location"][0]
-		request, _ = newRequest("GET", url, nil)
-		response, err = http.DefaultClient.Do(request)
-		if err != nil {
-			return nil, err
-		}
-	}
-
 	buf, err := ioutil.ReadAll(response.Body)
 	debugLog("<- %s [%s]\n%s",
 		response.Status, response.Header["Content-Type"], buf)
 	}
 
 	if err != nil {
-		return nil, os.NewError(string(buf))
+		return nil, errors.New(string(buf))
 	}
 
 	cleanNils(buf)
 		reply := new(serverReply)
 		err := json.Unmarshal(buf, reply)
 		if err != nil {
-			return nil, os.NewError(fmt.Sprintf("Bad server reply status: %s", response.Status))
+			return nil, errors.New(fmt.Sprintf("Bad server reply status: %s", response.Status))
 		}
-		message, ok := errors[reply.Status]
+		message, ok := errors_[reply.Status]
 		if !ok {
 			message = fmt.Sprintf("unknown error - %d", reply.Status)
 		}
 
-		return nil, os.NewError(message)
+		return nil, errors.New(message)
 	}
 
 	/* Some bug(?) in Selenium gets us nil values in output, json.Unmarshal is
-	* not happy about that. 
+	* not happy about that.
 	 */
 	if isMimeType(response, JSON_TYPE) {
 		reply := new(serverReply)
 		}
 
 		if reply.Status != SUCCESS {
-			message, ok := errors[reply.Status]
+			message, ok := errors_[reply.Status]
 			if !ok {
 				message = fmt.Sprintf("unknown error - %d", reply.Status)
 			}
 
-			return nil, os.NewError(message)
+			return nil, errors.New(message)
 		}
 		return buf, err
 	}
 
 /* Create new remote client, this will also start a new session.
    capabilities - the desired capabilities, see http://goo.gl/SNlAk
-   executor - the URL to the Selenim server
+   executor - the URL to the Selenim server, *must* be prefixed with protocol (http,https...).
+              Empty string means DEFAULT_EXECUTOR
 */
-func NewRemote(capabilities Capabilities, executor string) (WebDriver, os.Error) {
+func NewRemote(capabilities Capabilities, executor string) (WebDriver, error) {
 
 	if len(executor) == 0 {
 		executor = DEFAULT_EXECUTOR
 	return wd, nil
 }
 
-func (wd *remoteWD) stringCommand(urlTemplate string) (string, os.Error) {
+func (wd *remoteWD) stringCommand(urlTemplate string) (string, error) {
 	url := wd.requestURL(urlTemplate, wd.id)
 	response, err := wd.execute("GET", url, nil)
 	if err != nil {
 		return "", err
 	}
 
+	if reply.Value == nil {
+		return "", fmt.Errorf("nil return value")
+	}
+
 	return *reply.Value, nil
 }
 
-func (wd *remoteWD) voidCommand(urlTemplate string, data []byte) os.Error {
+func (wd *remoteWD) voidCommand(urlTemplate string, data []byte) error {
 	url := wd.requestURL(urlTemplate, wd.id)
 	_, err := wd.execute("POST", url, data)
 	return err
 
 }
 
-func (wd remoteWD) stringsCommand(urlTemplate string) ([]string, os.Error) {
+func (wd remoteWD) stringsCommand(urlTemplate string) ([]string, error) {
 	url := wd.requestURL(urlTemplate, wd.id)
 	response, err := wd.execute("GET", url, nil)
 	if err != nil {
 	return reply.Value, nil
 }
 
-func (wd *remoteWD) boolCommand(urlTemplate string) (bool, os.Error) {
+func (wd *remoteWD) boolCommand(urlTemplate string) (bool, error) {
 	url := wd.requestURL(urlTemplate, wd.id)
 	response, err := wd.execute("GET", url, nil)
 	if err != nil {
 
 // WebDriver interface implementation
 
-func (wd *remoteWD) Status() (*Status, os.Error) {
+func (wd *remoteWD) Status() (*Status, error) {
 	url := wd.requestURL("/status")
 	reply, err := wd.execute("GET", url, nil)
 	if err != nil {
 	return &status.Value, nil
 }
 
-func (wd *remoteWD) NewSession() (string, os.Error) {
+func (wd *remoteWD) NewSession() (string, error) {
 	message := map[string]interface{}{
 		"sessionId":           nil,
 		"desiredCapabilities": wd.capabilities,
 	return wd.id, nil
 }
 
-func (wd *remoteWD) Capabilities() (Capabilities, os.Error) {
+func (wd *remoteWD) SessionId() string {
+	return wd.id
+}
+
+func (wd *remoteWD) Capabilities() (Capabilities, error) {
 	url := wd.requestURL("/session/%s", wd.id)
 	response, err := wd.execute("GET", url, nil)
 	if err != nil {
 	return c.Value, nil
 }
 
-func (wd *remoteWD) SetAsyncScriptTimeout(ms uint) os.Error {
+func (wd *remoteWD) SetAsyncScriptTimeout(timeout time.Duration) error {
 	params := map[string]uint{
-		"ms": ms,
+		"ms": uint(timeout / time.Millisecond),
 	}
 
 	data, err := json.Marshal(params)
 	return wd.voidCommand("/session/%s/timeouts/async_script", data)
 }
 
-func (wd *remoteWD) SetImplicitWaitTimeout(ms uint) os.Error {
+func (wd *remoteWD) SetImplicitWaitTimeout(timeout time.Duration) error {
 	params := map[string]uint{
-		"ms": ms,
+		"ms": uint(timeout / time.Millisecond),
 	}
 
 	data, err := json.Marshal(params)
 	return wd.voidCommand("/session/%s/timeouts/implicit_wait", data)
 }
 
-func (wd *remoteWD) AvailableEngines() ([]string, os.Error) {
+func (wd *remoteWD) AvailableEngines() ([]string, error) {
 	return wd.stringsCommand("/session/%s/ime/available_engines")
 }
 
-func (wd *remoteWD) ActiveEngine() (string, os.Error) {
+func (wd *remoteWD) ActiveEngine() (string, error) {
 	return wd.stringCommand("/session/%s/ime/active_engine")
 }
 
-func (wd *remoteWD) IsEngineActivated() (bool, os.Error) {
+func (wd *remoteWD) IsEngineActivated() (bool, error) {
 	return wd.boolCommand("/session/%s/ime/activated")
 }
 
-func (wd *remoteWD) DeactivateEngine() os.Error {
+func (wd *remoteWD) DeactivateEngine() error {
 	return wd.voidCommand("session/%s/ime/deactivate", nil)
 }
 
-func (wd *remoteWD) ActivateEngine(engine string) os.Error {
+func (wd *remoteWD) ActivateEngine(engine string) error {
 	params := map[string]string{
 		"engine": engine,
 	}
 	return wd.voidCommand("/session/%s/ime/activate", data)
 }
 
-func (wd *remoteWD) Quit() os.Error {
+func (wd *remoteWD) Quit() error {
 	url := wd.requestURL("/session/%s", wd.id)
 	_, err := wd.execute("DELETE", url, nil)
 	if err == nil {
 	return err
 }
 
-func (wd *remoteWD) CurrentWindowHandle() (string, os.Error) {
+func (wd *remoteWD) CurrentWindowHandle() (string, error) {
 	return wd.stringCommand("/session/%s/window_handle")
 }
 
-func (wd *remoteWD) WindowHandles() ([]string, os.Error) {
+func (wd *remoteWD) WindowHandles() ([]string, error) {
 	return wd.stringsCommand("/session/%s/window_handles")
 }
 
-func (wd *remoteWD) CurrentURL() (string, os.Error) {
+func (wd *remoteWD) CurrentURL() (string, error) {
 	url := wd.requestURL("/session/%s/url", wd.id)
 	response, err := wd.execute("GET", url, nil)
 	if err != nil {
 
 }
 
-func (wd *remoteWD) Get(url string) os.Error {
+func (wd *remoteWD) Get(url string) error {
 	requestURL := wd.requestURL("/session/%s/url", wd.id)
 	params := map[string]string{
 		"url": url,
 	return err
 }
 
-func (wd *remoteWD) Forward() os.Error {
+func (wd *remoteWD) Forward() error {
 	return wd.voidCommand("/session/%s/forward", nil)
 }
 
-func (wd *remoteWD) Back() os.Error {
+func (wd *remoteWD) Back() error {
 	return wd.voidCommand("/session/%s/back", nil)
 }
 
-func (wd *remoteWD) Refresh() os.Error {
+func (wd *remoteWD) Refresh() error {
 	return wd.voidCommand("/session/%s/refresh", nil)
 }
 
-func (wd *remoteWD) Title() (string, os.Error) {
+func (wd *remoteWD) Title() (string, error) {
 	return wd.stringCommand("/session/%s/title")
 }
 
-func (wd *remoteWD) PageSource() (string, os.Error) {
+func (wd *remoteWD) PageSource() (string, error) {
 	return wd.stringCommand("/session/%s/source")
 }
 
-func (wd *remoteWD) find(by, value, suffix, url string) ([]byte, os.Error) {
+func (wd *remoteWD) find(by, value, suffix, url string) ([]byte, error) {
 	params := map[string]string{
 		"using": by,
 		"value": value,
 	return wd.execute("POST", url, data)
 }
 
-func decodeElement(wd *remoteWD, data []byte) (WebElement, os.Error) {
+func (wd *remoteWD) DecodeElement(data []byte) (WebElement, error) {
 	reply := new(elementReply)
 	err := json.Unmarshal(data, reply)
 	if err != nil {
 	return elem, nil
 }
 
-func (wd *remoteWD) FindElement(by, value string) (WebElement, os.Error) {
+func (wd *remoteWD) FindElement(by, value string) (WebElement, error) {
 	response, err := wd.find(by, value, "", "")
 	if err != nil {
 		return nil, err
 	}
 
-	return decodeElement(wd, response)
+	return wd.DecodeElement(response)
 }
 
-func decodeElements(wd *remoteWD, data []byte) ([]WebElement, os.Error) {
+func (wd *remoteWD) DecodeElements(data []byte) ([]WebElement, error) {
 	reply := new(elementsReply)
 	err := json.Unmarshal(data, reply)
 	if err != nil {
 	return elems, nil
 }
 
-func (wd *remoteWD) FindElements(by, value string) ([]WebElement, os.Error) {
+func (wd *remoteWD) FindElements(by, value string) ([]WebElement, error) {
 	response, err := wd.find(by, value, "s", "")
 	if err != nil {
 		return nil, err
 	}
 
-	return decodeElements(wd, response)
+	return wd.DecodeElements(response)
 }
 
-func (wd *remoteWD) Close() os.Error {
+func (wd *remoteWD) Close() error {
 	url := wd.requestURL("/session/%s/window", wd.id)
 	_, err := wd.execute("DELETE", url, nil)
 	return err
 }
 
-func (wd *remoteWD) SwitchWindow(name string) os.Error {
+func (wd *remoteWD) SwitchWindow(name string) error {
 	params := map[string]string{
 		"name": name,
 	}
 	return wd.voidCommand("/session/%s/window", data)
 }
 
-func (wd *remoteWD) CloseWindow(name string) os.Error {
+func (wd *remoteWD) CloseWindow(name string) error {
 	_, err := wd.execute("DELETE", "/session/%s/window", nil)
 	return err
 }
 
-func (wd *remoteWD) SwitchFrame(frame string) os.Error {
+func (wd *remoteWD) SwitchFrame(frame string) error {
 	params := map[string]string{
 		"id": frame,
 	}
 	return wd.voidCommand("/session/%s/frame", data)
 }
 
-func (wd *remoteWD) ActiveElement() (WebElement, os.Error) {
+func (wd *remoteWD) ActiveElement() (WebElement, error) {
 	url := wd.requestURL("/session/%s/element/active", wd.id)
 	response, err := wd.execute("GET", url, nil)
 	if err != nil {
 		return nil, err
 	}
 
-	return decodeElement(wd, response)
+	return wd.DecodeElement(response)
 }
 
-func (wd *remoteWD) GetCookies() ([]Cookie, os.Error) {
+func (wd *remoteWD) GetCookies() ([]Cookie, error) {
 	url := wd.requestURL("/session/%s/cookie", wd.id)
 	data, err := wd.execute("GET", url, nil)
 	if err != nil {
 	return reply.Value, nil
 }
 
-func (wd *remoteWD) AddCookie(cookie *Cookie) os.Error {
+func (wd *remoteWD) AddCookie(cookie *Cookie) error {
 	params := map[string]*Cookie{
 		"cookie": cookie,
 	}
 	return wd.voidCommand("/session/%s/cookie", data)
 }
 
-func (wd *remoteWD) DeleteAllCookies() os.Error {
+func (wd *remoteWD) DeleteAllCookies() error {
 	url := wd.requestURL("/session/%s/cookie", wd.id)
 	_, err := wd.execute("DELETE", url, nil)
 	return err
 }
 
-func (wd *remoteWD) DeleteCookie(name string) os.Error {
+func (wd *remoteWD) DeleteCookie(name string) error {
 	url := wd.requestURL("/session/%s/cookie/%s", wd.id, name)
 	_, err := wd.execute("DELETE", url, nil)
 	return err
 }
 
-func (wd *remoteWD) Click(button int) os.Error {
+func (wd *remoteWD) Click(button int) error {
 	params := map[string]int{
 		"button": button,
 	}
 	return wd.voidCommand("/session/%s/click", data)
 }
 
-func (wd *remoteWD) DoubleClick() os.Error {
+func (wd *remoteWD) DoubleClick() error {
 	return wd.voidCommand("/session/%s/doubleclick", nil)
 }
 
-func (wd *remoteWD) ButtonDown() os.Error {
+func (wd *remoteWD) ButtonDown() error {
 	return wd.voidCommand("/session/%s/buttondown", nil)
 }
 
-func (wd *remoteWD) ButtonUp() os.Error {
+func (wd *remoteWD) ButtonUp() error {
 	return wd.voidCommand("/session/%s/buttonup", nil)
 }
 
-func (wd *remoteWD) SendModifier(modifier string, isDown bool) os.Error {
+func (wd *remoteWD) SendModifier(modifier string, isDown bool) error {
 	params := map[string]interface{}{
 		"value":  modifier,
 		"isdown": isDown,
 	return wd.voidCommand("/session/%s/modifier", data)
 }
 
-func (wd *remoteWD) DismissAlert() os.Error {
+func (wd *remoteWD) DismissAlert() error {
 	return wd.voidCommand("/session/%s/dismiss_alert", nil)
 }
 
-func (wd *remoteWD) AcceptAlert() os.Error {
+func (wd *remoteWD) AcceptAlert() error {
 	return wd.voidCommand("/session/%s/accept_alert", nil)
 }
 
-func (wd *remoteWD) AlertText() (string, os.Error) {
+func (wd *remoteWD) AlertText() (string, error) {
 	return wd.stringCommand("/session/%s/alert_text")
 }
 
-func (wd *remoteWD) SetAlertText(text string) os.Error {
+func (wd *remoteWD) SetAlertText(text string) error {
 	params := map[string]string{
 		"text": text,
 	}
 	return wd.voidCommand("/session/%s/alert_text", data)
 }
 
-func (wd *remoteWD) execScript(script string, args []interface{}, suffix string) (interface{}, os.Error) {
+func (wd *remoteWD) execScriptRaw(script string, args []interface{}, suffix string) ([]byte, error) {
 	params := map[string]interface{}{
 		"script": script,
 		"args":   args,
 
 	template := "/session/%s/execute" + suffix
 	url := wd.requestURL(template, wd.id)
-	response, err := wd.execute("POST", url, data)
+	return wd.execute("POST", url, data)
+}
+
+func (wd *remoteWD) execScript(script string, args []interface{}, suffix string) (interface{}, error) {
+	response, err := wd.execScriptRaw(script, args, suffix)
 	if err != nil {
 		return nil, err
 	}
 	return reply.Value, nil
 }
 
-func (wd *remoteWD) ExecuteScript(script string, args []interface{}) (interface{}, os.Error) {
+func (wd *remoteWD) ExecuteScript(script string, args []interface{}) (interface{}, error) {
 	return wd.execScript(script, args, "")
 }
 
-func (wd *remoteWD) ExecuteScriptAsync(script string, args []interface{}) (interface{}, os.Error) {
+func (wd *remoteWD) ExecuteScriptAsync(script string, args []interface{}) (interface{}, error) {
 	return wd.execScript(script, args, "_async")
 }
 
-func (wd *remoteWD) Screenshot() ([]byte, os.Error) {
+func (wd *remoteWD) ExecuteScriptRaw(script string, args []interface{}) ([]byte, error) {
+	return wd.execScriptRaw(script, args, "")
+}
+
+func (wd *remoteWD) ExecuteScriptAsyncRaw(script string, args []interface{}) ([]byte, error) {
+	return wd.execScriptRaw(script, args, "_async")
+}
+
+func (wd *remoteWD) Screenshot() ([]byte, error) {
 	data, err := wd.stringCommand("/session/%s/screenshot")
 	if err != nil {
 		return nil, err
 	id     string
 }
 
-func (elem *remoteWE) Click() os.Error {
+func (elem *remoteWE) Click() error {
 	urlTemplate := fmt.Sprintf("/session/%%s/element/%s/click", elem.id)
 	return elem.parent.voidCommand(urlTemplate, nil)
 }
 
-func (elem *remoteWE) SendKeys(keys string) os.Error {
+func (elem *remoteWE) SendKeys(keys string) error {
 	chars := make([]string, len(keys))
 	for i, c := range keys {
 		chars[i] = string(c)
 	return elem.parent.voidCommand(urlTemplate, data)
 }
 
-func (elem *remoteWE) TagName() (string, os.Error) {
+func (elem *remoteWE) TagName() (string, error) {
 	urlTemplate := fmt.Sprintf("/session/%%s/element/%s/name", elem.id)
 	return elem.parent.stringCommand(urlTemplate)
 }
 
-func (elem *remoteWE) Text() (string, os.Error) {
+func (elem *remoteWE) Text() (string, error) {
 	urlTemplate := fmt.Sprintf("/session/%%s/element/%s/text", elem.id)
 	return elem.parent.stringCommand(urlTemplate)
 }
 
-func (elem *remoteWE) Submit() os.Error {
+func (elem *remoteWE) Submit() error {
 	urlTemplate := fmt.Sprintf("/session/%%s/element/%s/submit", elem.id)
 	return elem.parent.voidCommand(urlTemplate, nil)
 }
 
-func (elem *remoteWE) Clear() os.Error {
+func (elem *remoteWE) Clear() error {
 	urlTemplate := fmt.Sprintf("/session/%%s/element/%s/clear", elem.id)
 	return elem.parent.voidCommand(urlTemplate, nil)
 }
 
-func (elem *remoteWE) MoveTo(xOffset, yOffset int) os.Error {
+func (elem *remoteWE) MoveTo(xOffset, yOffset int) error {
 	params := map[string]interface{}{
 		"element": elem.id,
 		"xoffset": xOffset,
 	return elem.parent.voidCommand("/session/%s/moveto", data)
 }
 
-func (elem *remoteWE) FindElement(by, value string) (WebElement, os.Error) {
+func (elem *remoteWE) FindElement(by, value string) (WebElement, error) {
 	url := fmt.Sprintf("/session/%%s/element/%s/element", elem.id)
 	response, err := elem.parent.find(by, value, "", url)
 	if err != nil {
 		return nil, err
 	}
 
-	return decodeElement(elem.parent, response)
+	return elem.parent.DecodeElement(response)
 }
 
-func (elem *remoteWE) FindElements(by, value string) ([]WebElement, os.Error) {
+func (elem *remoteWE) FindElements(by, value string) ([]WebElement, error) {
 	url := fmt.Sprintf("/session/%%s/element/%s/element", elem.id)
 	response, err := elem.parent.find(by, value, "s", url)
 	if err != nil {
 		return nil, err
 	}
 
-	return decodeElements(elem.parent, response)
+	return elem.parent.DecodeElements(response)
 }
 
-func (elem *remoteWE) boolQuery(urlTemplate string) (bool, os.Error) {
+func (elem *remoteWE) boolQuery(urlTemplate string) (bool, error) {
 	url := fmt.Sprintf(urlTemplate, elem.id)
 	return elem.parent.boolCommand(url)
 }
 
 // Porperties
-func (elem *remoteWE) IsSelected() (bool, os.Error) {
+func (elem *remoteWE) IsSelected() (bool, error) {
 	return elem.boolQuery("/session/%%s/element/%s/selected")
 }
 
-func (elem *remoteWE) IsEnabled() (bool, os.Error) {
+func (elem *remoteWE) IsEnabled() (bool, error) {
 	return elem.boolQuery("/session/%%s/element/%s/enabled")
 }
 
-func (elem *remoteWE) IsDiaplayed() (bool, os.Error) {
+func (elem *remoteWE) IsDisplayed() (bool, error) {
 	return elem.boolQuery("/session/%%s/element/%s/displayed")
 }
 
-func (elem *remoteWE) GetAttribute(name string) (string, os.Error) {
+func (elem *remoteWE) GetAttribute(name string) (string, error) {
 	template := "/session/%%s/element/%s/attribute/%s"
 	urlTemplate := fmt.Sprintf(template, elem.id, name)
 
 	return elem.parent.stringCommand(urlTemplate)
 }
 
-func (elem *remoteWE) location(suffix string) (*Point, os.Error) {
+func (elem *remoteWE) location(suffix string) (*Point, error) {
 	wd := elem.parent
 	path := "/session/%s/element/%s/location" + suffix
 	url := wd.requestURL(path, wd.id, elem.id)
 	return &reply.Value, nil
 }
 
-func (elem *remoteWE) Location() (*Point, os.Error) {
+func (elem *remoteWE) Location() (*Point, error) {
 	return elem.location("")
 }
 
-func (elem *remoteWE) LocationInView() (*Point, os.Error) {
+func (elem *remoteWE) LocationInView() (*Point, error) {
 	return elem.location("_in_view")
 }
 
-func (elem *remoteWE) Size() (*Size, os.Error) {
+func (elem *remoteWE) Size() (*Size, error) {
 	wd := elem.parent
 	url := wd.requestURL("/session/%s/element/%s/size", wd.id, elem.id)
 	response, err := wd.execute("GET", url, nil)
 	return &reply.Value, nil
 }
 
-func (elem *remoteWE) CSSProperty(name string) (string, os.Error) {
+func (elem *remoteWE) CSSProperty(name string) (string, error) {
 	wd := elem.parent
 	urlTemplate := fmt.Sprintf("/session/%s/element/%s/css/%s", wd.id, elem.id, name)
 	return elem.parent.stringCommand(urlTemplate)
 }
+
+func init() {
+	// http.Client doesn't copy request headers, and selenium requires that
+	httpClient = &http.Client{
+		CheckRedirect: func(req *http.Request, via []*http.Request) error {
+			if len(via) > MAX_REDIRECTS {
+				return fmt.Errorf("too many redirects (%d)", len(via))
+			}
+
+			req.Header.Add("Accept", JSON_TYPE)
+			return nil
+		},
+	}
+}
 package selenium
 
 import (
-	"flag"
 	"fmt"
-	"http"
-	"io/ioutil"
-	"json"
+	"net/http"
 	"os"
 	"strings"
 	"testing"
+	"time"
 )
 
-var caps = Capabilities{
-	"browserName": "firefox",
-}
-
-type sauceCfg struct {
-	User string
-	Key  string
-}
-
 var serverPort = ":4793"
 var serverURL = "http://localhost" + serverPort + "/"
 
-var runOnSauce *bool = flag.Bool("saucelabs", false, "run on sauce")
+func browser() string {
+	browser := os.Getenv("TEST_BROWSER")
+	if len(browser) > 0 {
+		return browser
+	}
+	return "firefox"
+}
 
-func readSauce() (*sauceCfg, os.Error) {
-	data, err := ioutil.ReadFile("sauce.json")
-	if err != nil {
-		message := fmt.Sprintf("can't open sauce.json - %s\n", err)
-		return nil, os.NewError(message)
+func isHtmlUnit() bool {
+	return browser() == "htmlunit"
+}
+
+func getCaps() Capabilities {
+	return Capabilities{
+		"browserName": browser(),
 	}
-	cfg := &sauceCfg{}
-	if err = json.Unmarshal(data, cfg); err != nil {
-		return nil, os.NewError(fmt.Sprintf("bad JSON- %s\n", err))
-	}
-
-	return cfg, nil
 }
 
 func newRemote(testName string, t *testing.T) WebDriver {
 	executor := ""
-	// FIXME: Since we use internal http server, we can use SauceLabs ...
-	//if *runOnSauce {
-	if false {
-		cfg, err := readSauce()
-		if err != nil {
-			t.Fatalf("can't read sauce config - %s", err)
-		}
-		caps["name"] = testName // SauceLabs
-		urlTemplate := "http://%s:%s@ondemand.saucelabs.com:80/wd/hub"
-		executor = fmt.Sprintf(urlTemplate, cfg.User, cfg.Key)
-	}
-	wd, err := NewRemote(caps, executor)
+	wd, err := NewRemote(getCaps(), executor)
 	if err != nil {
 		t.Fatalf("can't start session - %s", err)
 	}
 }
 
 func TestNewSession(t *testing.T) {
-	if *runOnSauce {
-		return
-	}
-	wd := &remoteWD{capabilities: caps, executor: DEFAULT_EXECUTOR}
+	wd := &remoteWD{capabilities: getCaps(), executor: DEFAULT_EXECUTOR}
 	sid, err := wd.NewSession()
 	defer wd.Quit()
 
 		t.Fatal(err)
 	}
 
-	if c["browserName"] != caps["browserName"] {
-		t.Fatalf("bad browser name - %s", c["browserName"])
+	browser := getCaps()["browserName"]
+
+	if c["browserName"] != browser {
+		t.Fatalf("bad browser name - %s (should be %s)", c["browserName"], browser)
 	}
 }
 
 		t.Fatal(err)
 	}
 
+	time.Sleep(500 * time.Millisecond)
+
 	source, err := wd.PageSource()
 	if err != nil {
 		t.Fatal(err)
 }
 
 func TestAddCookie(t *testing.T) {
+	if isHtmlUnit() {
+		t.Log("Skipping on htmlunit")
+		return
+	}
 	wd := newRemote("TestAddCookie", t)
 	defer wd.Quit()
 
 }
 
 func TestExecuteScript(t *testing.T) {
+	if isHtmlUnit() {
+		t.Log("Skipping on htmlunit")
+		return
+	}
 	wd := newRemote("TestExecuteScript", t)
 	defer wd.Quit()
 
 }
 
 func TestScreenshot(t *testing.T) {
+	if isHtmlUnit() {
+		t.Log("Skipping on htmlunit")
+		return
+	}
 	wd := newRemote("TestScreenshot", t)
 	defer wd.Quit()
 
 	}
 }
 
+func TestIsDisplayed(t *testing.T) {
+	wd := newRemote("TestIsDisplayed", t)
+	defer wd.Quit()
+
+	wd.Get(serverURL)
+	elem, err := wd.FindElement(ById, "chuk")
+	if err != nil {
+		t.Fatal("Can't find element")
+	}
+	displayed, err := elem.IsDisplayed()
+	if err != nil {
+		t.Fatal("Can't check for displayed")
+	}
+
+	if !displayed {
+		t.Fatal("Not displayed")
+	}
+}
+
+func TestGetAttributeNotFound(t *testing.T) {
+	wd := newRemote("TestGetAttributeNotFound", t)
+	defer wd.Quit()
+
+	wd.Get(serverURL)
+	elem, err := wd.FindElement(ById, "chuk")
+	if err != nil {
+		t.Fatal("Can't find element")
+	}
+
+	_, err = elem.GetAttribute("no-such-attribute")
+	if err == nil {
+		t.Fatal("Got non existing attribute")
+	}
+}
+
+func TestSessionId(t *testing.T) {
+	wd := newRemote("TestGetAttributeNotFound", t)
+	wd.Quit()
+
+	sid, err := wd.NewSession()
+	if err != nil {
+		t.Fatalf("error in new session: %s", err)
+	}
+	defer wd.Quit()
+
+	if wd.SessionId() != sid {
+		t.Fatalf("Got session id mismatch %s != %s", sid, wd.SessionId())
+	}
+}
+
 // Test server
 
 var homePage = `
 }
 
 func init() {
+	fmt.Printf("Using Browser: %s\n", browser())
+
 	go func() {
 		http.HandleFunc("/", handler)
 		http.ListenAndServe(serverPort, nil)
 #!/bin/bash
 # Run the test suite
 
-jar=selenium-server-standalone-2.5.0.jar
+jar=$(./selenium.sh jar)
 if [ ! -f $jar ]; then
 	echo "error: can't find ${jar} (use './selenium.sh download' to get it)"
 	exit 1
 # Wait for selenium to start
 max_wait=20
 start_time=$(date +%s)
-while true; do
+while true;
+do
 	now=$(date +%s)
 	wait_time=$((now - start_time))
 	curl -s http://127.0.0.1:4444/wd/hub > /dev/null
 	fi
 done
 
-gotest -v
+go test -v $@
+value=$!
 ./selenium.sh stop
+exit $value
-/* Selenium client.
-
-Currently provides on WebDriver remote client.
-
-Version: 0.2.2
-*/
 package selenium
 
 import (
-	"os"
+	"time"
+)
+
+const (
+	VERSION = "0.8.0" // Driver version
 )
 
 /* Element finding options */
 type Build struct {
 	Version, Revision, Time string
 }
+
 /* OS object, part of Status return. */
 type OS struct {
 	Arch, Name, Version string
 }
 
+type Java struct {
+	Version string
+}
+
 /* Information retured by Status method. */
 type Status struct {
-	Build
-	OS
+	Java  Java
+	Build Build
+	OS    OS
 }
 
 /* Point */
 
 type WebDriver interface {
 	/* Status (info) on server */
-	Status() (*Status, os.Error)
+	Status() (*Status, error)
 
 	/* Start a new session, return session id */
-	NewSession() (string, os.Error)
+	NewSession() (string, error)
+
+	/* Current session id (empty string on none) */
+	SessionId() string
 
 	/* Current session capabilities */
-	Capabilities() (Capabilities, os.Error)
-	/* Set the amount of time, in milliseconds, that asynchronous scripts are permitted to run before they are aborted. */
-	SetAsyncScriptTimeout(ms uint) os.Error
-	/* Set the amount of time, in milliseconds, the driver should wait when searching for elements. */
-	SetImplicitWaitTimeout(ms uint) os.Error
+	Capabilities() (Capabilities, error)
+	/* Set the amount of time, in microseconds, that asynchronous scripts are permitted to run before they are aborted.
+
+	Note that Selenium/WebDriver timeouts are in milliseconds, timeout will be rounded to nearest millisecond.
+	*/
+	SetAsyncScriptTimeout(timeout time.Duration) error
+	/* Set the amount of time, in milliseconds, the driver should wait when searching for elements.
+
+	Note that Selenium/WebDriver timeouts are in milliseconds, timeout will be rounded to nearest millisecond.
+	*/
+	SetImplicitWaitTimeout(timeout time.Duration) error
 
 	// IME
 	/* List all available engines on the machine. */
-	AvailableEngines() ([]string, os.Error)
+	AvailableEngines() ([]string, error)
 	/* Get the name of the active IME engine. */
-	ActiveEngine() (string, os.Error)
+	ActiveEngine() (string, error)
 	/* Indicates whether IME input is active at the moment. */
-	IsEngineActivated() (bool, os.Error)
+	IsEngineActivated() (bool, error)
 	/* De-activates the currently-active IME engine. */
-	DeactivateEngine() os.Error
+	DeactivateEngine() error
 	/* Make an engines active */
-	ActivateEngine(engine string) os.Error
+	ActivateEngine(engine string) error
 
 	/* Quit (end) current session */
-	Quit() os.Error
+	Quit() error
 
 	// Page information and manipulation
 	/* Return id of current window handle. */
-	CurrentWindowHandle() (string, os.Error)
+	CurrentWindowHandle() (string, error)
 	/* Return ids of current open windows. */
-	WindowHandles() ([]string, os.Error)
+	WindowHandles() ([]string, error)
 	/* Current url. */
-	CurrentURL() (string, os.Error)
+	CurrentURL() (string, error)
 	/* Page title. */
-	Title() (string, os.Error)
+	Title() (string, error)
 	/* Get page source. */
-	PageSource() (string, os.Error)
+	PageSource() (string, error)
 	/* Close current window. */
-	Close() os.Error
+	Close() error
 	/* Switch to frame, frame parameter can be name or id. */
-	SwitchFrame(frame string) os.Error
+	SwitchFrame(frame string) error
 	/* Swtich to window. */
-	SwitchWindow(name string) os.Error
+	SwitchWindow(name string) error
 	/* Close window. */
-	CloseWindow(name string) os.Error
+	CloseWindow(name string) error
 
 	// Navigation
 	/* Open url. */
-	Get(url string) os.Error
+	Get(url string) error
 	/* Move forward in history. */
-	Forward() os.Error
+	Forward() error
 	/* Move backward in history. */
-	Back() os.Error
+	Back() error
 	/* Refresh page. */
-	Refresh() os.Error
+	Refresh() error
 
 	// Finding element(s)
 	/* Find, return one element. */
-	FindElement(by, value string) (WebElement, os.Error)
+	FindElement(by, value string) (WebElement, error)
 	/* Find, return list of elements. */
-	FindElements(by, value string) ([]WebElement, os.Error)
+	FindElements(by, value string) ([]WebElement, error)
 	/* Current active element. */
-	ActiveElement() (WebElement, os.Error)
+	ActiveElement() (WebElement, error)
+
+	// Decoding element(s)
+	/* Decode a single element response. */
+	DecodeElement([]byte) (WebElement, error)
+	/* Decode a multi element response. */
+	DecodeElements([]byte) ([]WebElement, error)
 
 	// Cookies
 	/* Get all cookies */
-	GetCookies() ([]Cookie, os.Error)
+	GetCookies() ([]Cookie, error)
 	/* Add a cookies */
-	AddCookie(cookie *Cookie) os.Error
+	AddCookie(cookie *Cookie) error
 	/* Delete all cookies */
-	DeleteAllCookies() os.Error
+	DeleteAllCookies() error
 	/* Delete a cookie */
-	DeleteCookie(name string) os.Error
+	DeleteCookie(name string) error
 
 	// Mouse
 	/* Click mouse button, button should be on of RightButton, MiddleButton or
 	LeftButton.
 	*/
-	Click(button int) os.Error
+	Click(button int) error
 	/* Dobule click */
-	DoubleClick() os.Error
+	DoubleClick() error
 	/* Mouse button down */
-	ButtonDown() os.Error
+	ButtonDown() error
 	/* Mouse button up */
-	ButtonUp() os.Error
+	ButtonUp() error
 
 	// Misc
 	/* Send modifier key to active element.
 	modifier can be one of ShiftKey, ControlKey, AltKey, MetaKey.
 	*/
-	SendModifier(modifier string, isDown bool) os.Error
-	Screenshot() ([]byte, os.Error)
+	SendModifier(modifier string, isDown bool) error
+	Screenshot() ([]byte, error)
 
 	// Alerts
 	/* Dismiss current alert. */
-	DismissAlert() os.Error
+	DismissAlert() error
 	/* Accept current alert. */
-	AcceptAlert() os.Error
+	AcceptAlert() error
 	/* Current alert text. */
-	AlertText() (string, os.Error)
+	AlertText() (string, error)
 	/* Set current alert text. */
-	SetAlertText(text string) os.Error
+	SetAlertText(text string) error
 
 	// Scripts
 	/* Execute a script. */
-	ExecuteScript(script string, args []interface{}) (interface{}, os.Error)
+	ExecuteScript(script string, args []interface{}) (interface{}, error)
 	/* Execute a script async. */
-	ExecuteScriptAsync(script string, args []interface{}) (interface{}, os.Error)
+	ExecuteScriptAsync(script string, args []interface{}) (interface{}, error)
+
+	/* Execute a script but don't JSON decode. */
+	ExecuteScriptRaw(script string, args []interface{}) ([]byte, error)
+	/* Execute a script async but don't JSON decode. */
+	ExecuteScriptAsyncRaw(script string, args []interface{}) ([]byte, error)
 }
 
 type WebElement interface {
 	// Manipulation
 
 	/* Click on element */
-	Click() os.Error
+	Click() error
 	/* Send keys (type) into element */
-	SendKeys(keys string) os.Error
+	SendKeys(keys string) error
 	/* Submit */
-	Submit() os.Error
+	Submit() error
 	/* Clear */
-	Clear() os.Error
+	Clear() error
 	/* Move mouse to relative coordinates */
-	MoveTo(xOffset, yOffset int) os.Error
+	MoveTo(xOffset, yOffset int) error
 
 	// Finding
 
 	/* Find children, return one element. */
-	FindElement(by, value string) (WebElement, os.Error)
+	FindElement(by, value string) (WebElement, error)
 	/* Find children, return list of elements. */
-	FindElements(by, value string) ([]WebElement, os.Error)
+	FindElements(by, value string) ([]WebElement, error)
 
 	// Porperties
 
 	/* Element name */
-	TagName() (string, os.Error)
+	TagName() (string, error)
 	/* Text of element */
-	Text() (string, os.Error)
+	Text() (string, error)
 	/* Check if element is selected. */
-	IsSelected() (bool, os.Error)
+	IsSelected() (bool, error)
 	/* Check if element is enabled. */
-	IsEnabled() (bool, os.Error)
+	IsEnabled() (bool, error)
 	/* Check if element is displayed. */
-	IsDiaplayed() (bool, os.Error)
+	IsDisplayed() (bool, error)
 	/* Get element attribute. */
-	GetAttribute(name string) (string, os.Error)
+	GetAttribute(name string) (string, error)
 	/* Element location. */
-	Location() (*Point, os.Error)
+	Location() (*Point, error)
 	/* Element location once it has been scrolled into view. */
-	LocationInView() (*Point, os.Error)
+	LocationInView() (*Point, error)
 	/* Element size */
-	Size() (*Size, os.Error)
+	Size() (*Size, error)
 	/* Get element CSS property value. */
-	CSSProperty(name string) (string, os.Error)
+	CSSProperty(name string) (string, error)
 }
 
 pidfile=/tmp/selenium.pid
 log=/tmp/selenium.log
-jar=selenium-server-standalone-2.7.0.jar
+jar=selenium-server-standalone-2.39.0.jar
 url=http://selenium.googlecode.com/files/$jar
 
 start() {
     start ) start;;
     stop ) stop;;
     download ) download;;
+    jar ) echo $jar;;
     * ) echo "error: unknown command - $1"; exit 1;;
 esac