Created
September 11, 2009 00:09
-
-
Save jubishop/184942 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Automate Functional Tests of Your Connect Implementation | |
Here on the Facebook Platform Engineering team we strive to keep Connect fast and stable and to help our developers ensure their implementations are working, bug free. | |
A while back we began working on a suite of Automated tests for some of our top partners that would confirm Connect was always functioning. Then we decided it'd be great if we could create a library that would help any connect developer easily create automated tests of their own site. | |
We've written a library on top of Watir (http://wtr.rubyforge.org/), that helps you deal with all the generic issues needed to stress test a Connect site's implementation. In this post we'll be demonstrating how to use our new library to create automated tests for StreamDiff (http://www.streamdiff.com/) a cool Connect site built by a coworker of mine: Naitik Shah. | |
First, we'll need to install Watir, which is described here: http://wtr.rubyforge.org/install.html . Be sure to get the plugin for Firefox...The library and this demo assume you'll be using Firefox for your testing. | |
Now, download FBWatir and the StreamdiffTest example, here: https://github.com/jubishop/FBWatir/tree | |
Before diving into the code, let's just try running the tests on the command line to see them pass and confirm everything is set up correctly. Crack open the terminal and cd into the directory where you downloaded fbwatir.rb, streamdiff.rb, etc. | |
Run: ruby fbtestrunner.rb streamdiff | |
fbtestrunner.rb utilizes a Ruby Unit TestRunner, which is documented here: http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html | |
Every parameter handed to fbtestrunner.rb should be the name of a .rb file in the directory, which represents a site for testing. So in this case we have streamdiff.rb. Each of these site-test files should require 'fbwatir.rb' and 'test/unit' at the top, as we see in streamdiff.rb: | |
require 'fbwatir.rb' | |
require 'test/unit' | |
The site-test file (streamdiff.rb in our example...) should then define a class that extends Test::Unit::TestCase, which should have the same name as the file, only capitalized, and appended with "Test", as we see inside streamdiff.rb: | |
class StreamdiffTest < Test::Unit::TestCase | |
The next few lines are for initialization, identifying a specific Connect test user and his/her credentials which we can utilize for our tests: | |
include FBWatir | |
Name = "John Doe" | |
User = "[email protected]" | |
Pass = "johndoe" | |
def setup | |
init(User, Pass) | |
end | |
The method setup always gets called by the UnitTestRunner at the beginning of each test run. So this would be the place to add any additional initialization code needed for your test. init() is a method in the FBWatir module which allows us to initialize the connect credentials of the account we'll be using in our tests. | |
After calling setup(), the ruby UnitTestRunner will run every method in the defined class which begins with "test". Then, in those test methods, we'll add our assertions.. | |
Once init() has been called, the FBWatir module provides 3 methods for easily interacting with Facebook/Connect: | |
disconnectFromFacebook(appId) : This deauthorizes our application, removes all extended permissions, and logs us out of Facebook. | |
loginAndGoToFacebook : This logs us into facebook and then returns only when fully logged in and loaded a logged in Facebook.com/home.php | |
loginToConnect(urlRegex) : This fills out the credentials in the Facebook connect iframe/popup, and also clicks all the necessary buttons to allow all extended permissions requested. | |
You would call this once you had navigated to your website and clicked the Facebook Connect button...which we'll see in Streamdiff. | |
The URLRegex should be a simple /regex/ that matches the url of your site, so that Watir can reattach to it once it's done interacting with the connect login popup... | |
goFB : Go to Facebook and don't return until the page is fully loaded, either home.php or login.php... | |
Before looking at the complex "test" methods in our StreamdiffTest class, let's go to the bottom of the file and look at some of the utility methods we've defined. The structure utilized around these generic methods should be reusable for most Connect implementations... | |
You're going to want an easy to way to simply visit your website, and confirm that the page is fully loaded before proceeding. Usually when your site loads it will either render some indicator that you need to log-in (A "Log In" link perhaps...), or it will render some alternative indicator showing that you're logged-in already (Perhaps it will welcome you by your full name in a <div> near the top... We want to define a function for each of these two dom-objects, and then we'll have a go method which can wait for the existence of one of these two dom-objects in order to be sure the page has loaded...Here's how this looks in streamdiff: | |
# This function just takes us to streamdiff.com, and then ensures we're fully loaded | |
def go | |
Browser.goto("http://www.streamdiff.com") | |
wait_until { connect_button.exists? or logged_in_indicator.exists? } # Here's how we ensure we're fully loaded | |
end | |
# Here's the connect button that appears if we're not connected yet | |
def connect_button | |
Browser.button(:class, "connect") | |
end | |
# This is the dom element that appears if we're logged into streamdiff already | |
def logged_in_indicator | |
Browser.text_field(:id, "message") | |
end | |
Ok, now we're going to want some utility functions to connect/disconnect our test user from our connect site. So let's implement a connect() and disconnect() function. | |
def disconnect | |
go # Go to our website | |
Browser.maybe.link(:class, "logout").click # Click the logout link if it exists (.maybe means "if it exists...") | |
disconnectFromFacebook(96228874643) # Deauthorize our app in Facebook entirely | |
go # Go back to our website so that when all done, we're on our website, logged out... | |
end | |
def connect | |
go # Go to our website | |
if (connect_button.exists?) # Remember we defined connect_button ourselves... | |
connect_button.click # So if it exists, click it | |
loginToConnect(/streamdiff/) # Now login to connect, and bind to our url when done... | |
connect # Double check that it worked by re-calling ourselves... | |
end | |
end | |
There are 3 basic actions we can take on Streamdiff...We can post to the stream, we can comment on a post, and we can like a post. So let's create a utility method for these three activities...We can call them postToStream(), likePost(message), commentOnPost(message) | |
def likePost(message) # Message is the text of the message in the stream we want to "like" | |
connect # Ensure we're connected | |
# Wait for our message to appear in the stream... (.await means "wait till it exists...") | |
Browser.await.div(:text, /#{message}/) | |
# Click the "Like" link on this post... | |
Browser.li(:class => /post/, | |
:text => /#{message}/).link( | |
:class, /action-like/).click | |
end | |
def commentOnPost(message) # Message is the text of the message in the stream we want to comment on | |
connect # Ensure we're connected | |
# Create a unique comment by mashing it with the unix time stamp | |
comment = "Commenting #{Time.now.tv_sec}" | |
# Set the text field to our new comment text | |
Browser.await.li(:class => /post/, | |
:text => /#{message}/).form( | |
:class, /post-comment/).text_field( | |
:name, "text").fb_set(comment) | |
# Click the "comment" link | |
Browser.li(:class => /post/, :text => /#{message}/).form( | |
:class, /post-comment/).button( | |
:class, /button/).click | |
comment # Return our new comment in case the caller cares | |
end | |
def postToStream # Posts a new random message to the stream | |
connect # Ensure we're connected | |
# Create a unique message by mashing it with the unix time stamp | |
message = "Posting #{Time.now.tv_sec}" | |
# Set the text of our composer to our new post message | |
Browser.await.text_field(:id, "message").fb_set(message) | |
# Click the "Share" link | |
Browser.button(:value, "share").click | |
message # Return our new stream message in case the caller cares | |
end | |
Now, let's make some tests! First let's do simple tests that assert our user can connect/disconnect successfully: | |
def test_connect | |
connect | |
# If we're connected, then there'll definitely be a Logout Link | |
assert_exists(Browser.link(:class, "logout"), | |
"Logout Link") | |
# StreamDiff welcomes me by name if I'm logged in, so that should be in the text | |
assert(Browser.text.include?(Name), | |
"Knows My Name") | |
end | |
def test_disconnect | |
disconnect | |
# If I'm disconnected, then there should be a connect button | |
assert_exists(connect_button, | |
"Connect Button") | |
# The site should not my name, if I'm logged out... | |
assert((not Browser.text.include?(Name)), | |
"Doesn't Know My Name") | |
end | |
Finally, to test our interaction with the Stream, let's post a new message to the stream, then let's comment on and like that message we just created. At each step along the way, we'll visit Facebook.com to make sure the post gets updated in the stream, and then we'll re-visit our own website to make sure it still shows up there, too. | |
First, let's define a function that will create the post and assert that it's successful... | |
def _test_posting | |
message = postToStream | |
# Go to facebook and assert our new posted message exists there | |
loginAndGoToFacebook | |
assert_exists(Browser.h3(:text, /#{message}/), | |
"Stream Post In Facebook") | |
# Come back to streamdiff and assert our message still exists there | |
connect | |
Browser.await.div(:text, /#{message}/) | |
assert_exists(Browser.div(:text, /#{message}/), | |
"Stream Post In StreamDiff") | |
message # Return our message for the caller | |
end | |
Now let's define a function that will "Like" a given post in the stream. Note that we're naming these with "_test" instead of "test" because we don't want our UnitTestRunner to run them directly. Rather we'll call them from within our "Comment" test...Here's the "Like" test: | |
def _test_liking(message) | |
likePost(message) | |
# Go to facebook and assert our "Like" exists in the stream | |
loginAndGoToFacebook | |
assert_exists(Browser.div(:id => /div_story_/, | |
:text => /#{message}/).div( | |
:text, /You like this/), | |
"'You Like This' for Liked Story on Facebook") | |
# Come back to streamdiff and assert our "Like" still exists | |
connect | |
assert_nonexists(Browser.li(:class => /post/, | |
:text => /#{message}/).link( | |
:class, /action-like/), | |
"Like Button for Liked Story on StreamDiff") | |
end | |
Finally, our Comment "test" method, which incorporates the Like and Post tests: | |
def test_commenting | |
message = _test_posting # Test creating a new post | |
comment = commentOnPost(message) # Comment on the new post | |
# Go to facebook and assert our Comment exists in the stream | |
loginAndGoToFacebook | |
assert_exists(Browser.div(:text => /#{comment}/), | |
"Comment In Facebook") | |
# Come back to streamdiff and assert our Comment still exists | |
connect | |
Browser.await.div(:text, /#{comment}/) | |
assert_exists(Browser.div(:text, /#{comment}/), | |
"Comment In StreamDiff") | |
# Test liking our new post | |
_test_liking(message) | |
end | |
There you have it! Let us know what you think. We hope this helps! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment