sclevine / agouti Goto Github PK
View Code? Open in Web Editor NEWA WebDriver client and acceptance testing library for Go
License: MIT License
A WebDriver client and acceptance testing library for Go
License: MIT License
@sclevine - do we start actually downloading things like PhantomJS, FirefoxDriver, and ChromeDriver for people as they want to use those dependencies?
I will need Appium extensions support.. I'm willing to start contributing. Is there a way to layout the code that you'd prefer ?
Hi guys, I'm using Agouti for testing my application, its work great for almost my need.
Can I wait for the JS excution to finish after trigger an even before checking something else?
For example, in my web page:
$("#js-btn").click(function(){
// take a long time to be done, some ajax load etc
window.location.replace("http://example.com");
})
And in my test, currently I do:
Expect(page.Find("#js-btn").Click()).To(Succeed())
time.Sleep(time.Minute)
Expect(page.URL()).To(Equal("http://example.com"))
What can I do for avoiding the Sleep line? Its not really work because who know the network speed can be slower!!
Hey Steven, I'm trying to find a way to load chrome extensions into the chromedriver so I can run tests with an ad-blocker extension but unfortunately I can't figure out how to.
According to this SO entry: http://stackoverflow.com/questions/16800689/running-webdriver-with-extensions-crx-files I should be able to do something like:
chop = webdriver.ChromeOptions()
chop.add_extension('Adblock-Plus_v1.4.1.crx')
driver = webdriver.Chrome(chrome_options = chop)
It looks like I might have to use api.NewWebDriver
and pass the command by hand, but I'm not quite sure.
We'd need MoveTo to scroll things into view.. it isn't exposed.. could we add it to selectable ?
Example
s to GodocThis looks like
page.Find(".my-div").To(HaveCSS("text-weight", "bold"))
It would be nice to offer support for a custom http.Client to be used behind the scenes.
For example, TLS connections fail with the following error:
2015/08/30 15:32:25 http: TLS handshake error from 127.0.0.1:38010: tls: client offered an unsupported, maximum protocol version of 300
But there is no way to change the Client that is being used currently (and thus no way to fix the error other than making a local edit to agouti).
This would be an awesome one to include,
https://code.google.com/p/selenium/wiki/FirefoxDriver
You won't actually have to run headlessly and can instead view what is happening in the browser.
Relevant portion of the protocol is here:
https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/selected
Allow setting per session cookies:
https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/cookie
agouti.Navigate
opens a new session with new cookiesPage#Navigate
persists the session and cookiesCan easily be implemented, I say we go for it:
Portion of the Protocol is here:
https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/clear
Eventually(page.Find("#username").Fill("demo")).Should(Succeed())
(This looks almost the same as the demo code at http://agouti.org/#adding-acceptance-tests )
Full Stack Trace
/home/etiennebruines/workspaces/go-all/src/github.com/onsi/gomega/internal/asyncassertion/async_assertion.go:30 (0x58f6ce)
New: if actualType.Kind() == reflect.Func {
/home/etiennebruines/workspaces/go-all/src/github.com/onsi/gomega/gomega_dsl.go:199 (0x508ac2)
EventuallyWithOffset: return asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, actual, globalFailHandler, timeoutInterval, pollingInterval, offset)
/home/etiennebruines/workspaces/go-all/src/github.com/onsi/gomega/gomega_dsl.go:181 (0x50892f)
Eventually: return EventuallyWithOffset(0, actual, intervals...)
/home/etiennebruines/workspaces/go-all/src/git.huperwebs.nl/nu-1-website/client-server/controllers/login_test.go:37 (0x48dfa9)
func.007: Eventually(page.Find("#username").Fill("demo")).Should(Succeed())
It's a nil-pointer exception, because actualType
(async_assertion.go line 30) is nil
.
actualType
is nil
because reflect.TypeOf(actualInput)
returns nil
when actualInput
is nil.
actualInput
is the second argument (type interface{}
) which has the variable name actual
at the caller at line 199 at gomega_dsl.go.
Nothing is being done to that variable in EventuallyWithOffset
and Eventually
, it's just being passed along.
At the end, that actualInput
(or actual
) variable is the result of
page.Find("#username").Fill("demo")
However, .Fill
returns an error
object. This is nil
...
I don't really understand any of it (I don't know the gomega/agouti framework). It's identical to the demo code, but results in an error.
Go version 1.3.3
Ginkgo version 1.1.0
ChromeDriver 2.14.313457
Hi,
I am trying to match a multi selection via
...
Expect( page.All("a").At(1)).To(HaveText("Text 1"))
Expect( page.All("a").At(2)).To(HaveText("Text 2"))
...
Only the first test matches, the second reports an empty string ?
This manifests as:
failed to determine whether selection 'CSS: not_there [0]' is visible: request unsuccessful: error parsing response: invalid character 'u' looking for beginning of value
The actual value it was trying to parse is:
unknown command: session/[garble]/element/displayed
Which appears to be caused by the use of path.Join()
in api/element.go
. Specifically, when an indexed selector matches nothing, you get an Element.ID == ""
. This generates a Selenium request of the form "[...]/element/displayed"
rather than "[...]/element//displayed"
. The latter won't work either, but at least Selenium doesn't choke on it, generating a non-JSON response.
The second part, of which I'm a bit less sure, is why it's possible to get an Element.ID == ""
in the first place. This doesn't seem to happen with non-indexed selectors (e.g., All("not_there").Visible()
) -- this generates the more sane response failed to select elements from selection 'CSS: not_there': no elements found
. I'm still trying to make sense of precisely how this should work, but I suspect it shouldn't be generating element instances without their IDs set.
Endpoint is here:
https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/screenshot
page.Screenshot()
Straight from @sclevine - "so we just un-base64 encode the png and save it to a file ftw"
To do this:
You'd have to first get the window handle
https://code.google.com/p/selenium/wiki/JsonWireProtocol#GET_/session/:sessionId/window_handle
and then set its size
page.Size(640, 480)
It would be nice to have some matchers and actions that work on multiple elements.
Possible implementation:
selection.All()
method that returns a MultiSelection
struct, which is type-equivalent to a Selection
struct.MultiSelection
only has methods defined on it that make sense for multiple elements. It implements some, but not all, of Selection
. For instance, Visible() bool
would have the same signature, and only return true if all elements in the selection are visible. However, Text() string
would return a slice of strings.BeVisible
matcher would not need to change, but HaveText
would need to switch over interface{ Text() string }
and interface{ Text() []string }
It would be nice if Agouti could report any javascript errors that occur on a page so that I could include a check for this in my application's acceptance tests. Is there any way to do this currently?
func (s *selectable) FindByName(text string) *Selection {}
Not a huge deal, but really convenient helper function.
The "selenium-server" script that must be in the path seems to be installed by the brew
recipe.. however I didn't find the equivalent on Linux. I was thinking of adding SeleniumJar()
that would launch the .jar
file through java -jar whatever.jar.is.in.the.current.directory.jar
.
What do you think ?
We'd be looking to run a command like this
page.Find("#my-specific-element").To(BeEnabled())
Let people swap PhantomJS for ChromeDriver instead.
I'm trying to load a PhantomJS page without loading images to speed thing up.
I've tried the following but it still loads images:
capabilities := agouti.NewCapabilities().Without("loadImages")
driver := agouti.PhantomJS(agouti.Desired(capabilities))
Can this setting be used?
Page to test would look like this
<div class="something-else">
<div id="something" class="something"></div>
</div>
Matcher should look something along the lines of
page.Find(".something .something#something").To(Exist())
Trying to get Agouti to work on Ubuntu 12.04
Installed agouti and followed the instructions from the examples.
Tried different combinations of browsers (firefox, google chrome) with Selenium standalone server running on port 4444 and with phantomjs. Not sure which versions I should use for either
Selenium or phantomjs.
No browser is started though, the test just waits. Please advise.
$ go test
Random Seed: 1422079854
Will run 1 of 1 specs
2015/01/24 07:10:55 server started
[negroni] listening on :3000
Received interrupt. Running AfterSuite...
^C again to terminate immediately
Ran 1 of 1 Specs in 5.425 seconds
FAIL! -- 0 Passed | 0 Failed | 0 Pending | 0 Skipped
This would be based on this portion of the protocol
https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/active
Background: I'm trying to make sure a javascript-populated list on my page has the correct contents. I tried to check the HTML of the page by itself, but that doesn't update with the javascript changes...
So instead, I'm trying to pull information out of the javascript-heavy page and ensure that it contains what I expect, so I wrote code like this (replace 100 with something like window.App.getCount() for example):
var number int
Expect(page.RunScript("return 100;", nil, &number)).To(Succeed())
Expect(number).To(Equal(100))
That didn't work however, and the RunScript fails with this error:
"failed to run script: request unsuccessful: error unreadable"
Am I using Agouti wrong (is it not meant to probe the running web driver instance)? I would have at least expecte dit to return the 100 as an integer.
What is the axiomatic agouti way to ensure an element does not exist on a page?
Currently I use
Expect(page.Find("selector")).To(BeFound())
for things I expect to be found.
As the find is the thing that fails, I wonder what the right way to ensure a thing doesn't exist is.
I would have thought this would work (but it didn't):
Expect(page.Find("#topnav-statistics-link")).ToNot(BeFound())
Users have the ability to click around on the page by doing the following:
https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/click
We need to decide on where we'd like to handle the three types of errors that might come back from this method:
NoSuchWindow - If the currently selected window has been closed.
StaleElementReference - If the element referenced by :id is no longer attached to the page's DOM.
ElementNotVisible - If the referenced element is not visible on the page (either is hidden by CSS, has 0-width, or has 0-height)
I looked for the source code of the page but didn't find it, so no PR from me :(
func TestUserLoginPrompt(t *testing.T) {
driver := agouti.Selenium()
// ...
should probably read:
func TestUserLoginPrompt(t *testing.T) {
driver := agouti.Selenium()
if err := driver.Start(); err != nil {
t.Fatal(err)
}
// ...
Now that you can add cookies, you should be able to delete cookies (by name) from your session.
https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/cookie/:name
Seems that you should be able to say:
page.DeleteCookie("myNamedCookie")
Feedback @sclevine
I'm trying to run the generated suite_test but I'm using Selenium Hub in a Docker container. I think I need to use NewWebDriver
but I can't figure out why I have to pass a command? I already have it running.
Name
- Methods that retrieve data or perform some action should not start with "Is" or "Set"
SetName
- Methods that set data and have a corresponding Name
method should start with "Set". Implementing these methods is low priority, because they aren't as useful for integration testing
I was looking to see if the API has the ability to send keystrokes, but I was unable to locate it. Is this possible?
Sorry, I couldn't find any documentation and I've been reading through the code and API trying to locate this. It seems like I would have to implement this via the agouti.Session and the JSON protocol.
All webdriver method names should be as close to their endpoint name as possible.
GetName
for all GET
requests returning a string
IsName
for all GET
requests returning a boolean
SetName
for POST
requests that have matching GET
requests
Name
for POST
requests that perform some action, or retrieve data
Note: methods that POST
in order to return an element (and have no matching GET
requests) are in the form Get<X>Element
Hi, Thanks for building this great tool, my integration test run on chrome like a charm in local.
However, when I'm trying to get agouti run on semaphore -- a CI provider, I'm blocked.
Keep getting panic: runtime error: invalid memory address or nil pointer dereference
error. This is my configuration.
func TestMain(m *testing.M) {
var t *testing.T
var err error
driver = agouti.Selenium()
driver.Start()
go Start(PORT)
page, err = driver.NewPage(agouti.Browser("chrome"))
if err != nil {
t.Error("Failed to open page.")
}
RegisterTestingT(t)
test := m.Run()
driver.Stop()
os.Exit(test)
}
the error stack shows it raised from page, err = driver.NewPage(agouti.Browser("chrome"))
. I have selenium-server-standalone-2.44.0.jar
and chrome(also tried firefox) installed. Can't figure out where is the problem. How can I get more detailed error information for debug ?
Thanks for reply.
btw. this is the full error stack
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x0 pc=0x466286]
goroutine 1 [running]:
_/home/runner/qor/test/integration.TestMain(0xc20802e410)
/home/runner/qor/test/integration/setup_integration_test.go:35 +0x326
main.main()
_/home/runner/qor/test/integration/_test/_testmain.go:66 +0x1d1
goroutine 9 [runnable]:
_/home/runner/qor/test/integration.Start(0x2328)
/home/runner/qor/test/integration/main.go:49
created by _/home/runner/qor/test/integration.TestMain
/home/runner/qor/test/integration/setup_integration_test.go:31 +0xa1
goroutine 8 [runnable]:
text/template/parse.lexText(0xc20807e180, 0xa6d140)
/usr/local/go/src/text/template/parse/lex.go:228 +0x37b
text/template/parse.(*lexer).run(0xc20807e180)
/usr/local/go/src/text/template/parse/lex.go:198 +0x5d
created by text/template/parse.lex
/usr/local/go/src/text/template/parse/lex.go:191 +0x1ac
exit status 2
FAIL _/home/runner/qor/test/integration 0.008s
If we follow the tutorial angularjs in this step
https://docs.angularjs.org/tutorial/step_03
In this step check if the phone list have a certain amount of results
describe('PhoneCat App', function() {
describe('Phone list view', function() {
beforeEach(function() {
browser.get('app/index.html');
});
it('should filter the phone list as a user types into the search box', function() {
var phoneList = element.all(by.repeater('phone in phones'));
var query = element(by.model('query'));
expect(phoneList.count()).toBe(3);
query.sendKeys('nexus');
expect(phoneList.count()).toBe(1);
query.clear();
query.sendKeys('motorola');
expect(phoneList.count()).toBe(2);
});
});
});
Is possible to create similar test? How should I write the test?
I had a failure running phantomjs.. I'm not sure about the required versions and such. Is there any doc that shows how to setup a testing environment... mostly for integration tests?
Thanks!
You can delete all cookies based on your session from this endpoint:
https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/cookie
Feedback on the interface to do this?
page.DeleteAllCookies()
Define a form syntax, iterate through the various fields, enter the information and optionally submit the form. We should define the syntax here for filling and optionally submitting the form.
Need to come up with the right syntax for this
For instance, adding style="color: blue;"
to an element does not match HaveCSS("color","blue")
, because PhantomJS returns RGBA.
I'm writing some tests that call several functions, sometimes they are deeply nested, sometimes they aren't.. so calls to ExpectWithOffset
or EventuallyWithOffset
can never be certain of the nesting level of the caller.
I was wondering, if we could change the logic of file:line
reporting to match the file where the Describe
call is run, and use the line number of the closest function call in that same file. This way, there are better chances we get the right line number shown.. Or maybe we could have some sort of global switch to change the behavior, on BeforeSuite
or something ?
What do you think?
The page.RunScript
is fine to run inline javascript but the WebDriver's Execute is a must if I want to run heavier javascript like injecting jQuery and other libs.
I already forked agouti to expose page.Execute
(niclupien/agouti) but I feel there's a reason you didn't exposed it before so I'm opening this issue to start the discussion.
Another alternative would be a new method InjectScript
that could take the name of file to inject in the page (ex: page.injectScript("jquery.js")
)
I am writing a screen scraper that needs to run on Windows. Agouti works very well - thank you! I noticed that the webdriver process was not shutting down properly on Windows after my go program exited (though it did on my mac.) I believe this is the relevant code:
func (s *Service) Stop() {
if s.process == nil {
return
}
s.process.Signal(syscall.SIGINT)
s.process.Wait()
s.process = nil
}
The docs for os.Process note that [s]ending Interrupt on Windows is not implemented. This is in conflict with the docs for os.Signal, which state that [t]he only signal values guaranteed to be present on all systems are Interrupt (send the process an interrupt) and Kill (force the process to exit). Nonetheless, SIGINT doesn't seem to work on Windows.
This hacky solution seems to do the trick:
func (s *Service) Stop() {
if s.process == nil {
return
}
if runtime.GOOS == "windows" {
s.process.Signal(syscall.SIGKILL)
} else {
s.process.Signal(syscall.SIGINT)
}
s.process.Wait()
s.process = nil
}
Of course, this requires adding the runtime package to the imports section...
We should support modifiers that account for multiple elements (or no elements at all), like so:
page.Within("#selector").Nothing().ShouldContainText("foo")
page.Within("#selector").Each().ShouldContainText("foo")
page.Within("#selector").Each(func(sel FinalSelection) {
sel.ShouldContainText("foo")
})
These should return a new Selection
the allowed plurality of the selection.
If no modifier is present and multiple elements are selected, the matcher should fail.
If the Nothing()
modifier is added, the match should be inverted.
The modifiers should return interfaces (wrapping Selection
s) that cannot have Within
or other modifiers called on them again.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.