Testing Your Website with Selenium (and Perl)

Preview:

Citation preview

Testing Your Website With Selenium (and Perl)

Tim Vroom - vroom@blockstackers.com YAPC NA - June 2015 Salt Lake City, Utah

Photo by waynerd on flickr

About Me

Tim Vroom Once upon a time was creator

of perlmonks.org

Currently work at slashdot.org

@vroomtim on twitter

GitHub Repo

• https://github.com/tvroom/selenium-testing-with-perl

Short url of ( http://tinyurl.com/sel-perl )

(slides / example code / web pages / scripts) — final updates may come in next few days

What is Selenium?

• A tool for web-browser automation & testing

• Allows for functional testing that also runs JavaScript loaded on the page

• Can run tests against multiple web browsers

Why might you want to use it?

Test close to where users experience your code (in the browser)

Test things your JS, and CSS are responsible for, layout in browser, visibility of messages etc…

Speed up involved debugging / testing

while developing long flows

Don’t spend your time typing and clicking to test code as you develop, in complicated code, and flows.

Automate the tedium of testing… so you can write more code, and test your code changes more quickly.

What we’ll cover

• Running your first Selenium test with Perl

• SeleniumRC vs Selenium Webdriver

• Key Perl libraries to consider using for your tests

• Locating Elements & Handling Waits

• Variety of Example Tests

What we’ll finish up with… Time in the Selenium Playground working through listed test tasks

Steps to running your first Selenium Test with Perl

1. Download Selenium Server - selenium-server-standalone-2.45.0.jar (latest currently) at http://www.seleniumhq.org/download/

2. Run the server:

3. Install Selenium::Remote::Driver from CPAN

java -jar selenium-server-standalone-2.45.0.jar

Sample YAPC Testuse Selenium::Remote::Driver; use Selenium::Remote::WDKeys; use Test::More;

my $driver = Selenium::Remote::Driver->new(default_finder => 'css');

$driver->get('http://www.google.com');

$driver->find_element('q','name')->send_keys('YAPC Salt Lake City',KEYS->{'enter'});

$driver->set_implicit_wait_timeout(5000);

$driver->find_element('#rso li h3 a')->click(); my $title = $driver->get_title(); like($title, qr/YAPC/, "Title for YAPC page is what is expected");

$driver->find_element('Talks and Schedule', 'link_text')->click(); $driver->find_element('Schedule','link_text')->click(); $driver->find_element('Wednesday','link_text')->click(); $driver->find_element('Selenium','partial_link_text')->click();

$title = $driver->get_title(); like($title, qr/Selenium/, "Title seems to be about testing with Selenium"); sleep 5; # For demo purposes wait a bit before closing so we can see the final page $driver->quit();

done_testing();

Versions of Selenium

Remote Control - Vs - WebDriver

Selenium RC (Selenium 1)

• Selenium Remote Control serves as middle layer between code and browser

• When you run a test it pops one window for the Remote Control, and then that window operates your test window via javascript

• Operates browser actions by running Javascript to mimic those actions

• Due to translation in middle layer sometimes did things differently than the browser actually would

Your Code -> Selenium RC —> Browser test is running in

Selenium 2 (WebDriver API)

• Talks directly to browser via WebDriver API calls

• Browser makers have built WebDriver support right into the browser

• Typically faster running tests due to more direct nature

• Behaves more consistently with actual browser behavior, because you’re executing native code with-in the browser, which matches up with WebDriver API Calls

Additional Reading on JsonWireProtocol (or WebDriver wire protocol)

• JsonWireProtocol - learn about the protocol (and capabilities provided) in the Protocol used to power WebDriver

• https://code.google.com/p/selenium/wiki/JsonWireProtocol

• Particularly - the command reference section: https://code.google.com/p/selenium/wiki/JsonWireProtocol#Command_Reference

SELENIUM VERSION REMOTE CONTROL (RC) WEBDRIVER

BASE PERL MODULE WWW:::Selenium Selenium::Remote::Driver

TEST MODULE Test::WWW::Selenium Test::Selenium::Remote::Driver

OTHER STUFF WWW::Selenium::NetworkCaptureWWW::Selenium::Util

Selenium::FirefoxSelenium::ChromeSelenium::PhantomJS

Perl Modules By Selenium Version

• Selenium::Firefox • Selenium::Chrome • Selenium::PhantomJS

New modules in the space - run WebDriver tests w/o Selenium Standalone Server

(from Selenium::Remote::Driver docs)

Sample code for this presentation

• Will primarily show code interacting with Selenium Webdriver (Selenium::Remote::Driver Perl Module)

• Repo will contain examples with SeleniumRC in most cases for comparison

Typical Test FlowStep 0. Find a Thing in the DOM

Step 1. Do something to that thing, or look at its attributes

Step 2. Wait appropriate amount of time if necessary before doing next thing

Repeat…

Key Concepts While Writing Tests

Use Dev Tools in Chrome or similar to inspect the element you’re interested in

and find a selector for it

Methods to Find Your Elements

find_elementfind_element($search_string, $locator_type);

# find using css locator — type$driver->find_element(‘h3.articlehead’,’css’);

# use default of path for locator$driver->find_element(‘//*[@id=“logo”]/a’);

If your element is not found — will CROAK, killing your script. (unless you check for and trap in try/catch or eval block etc)

Returns Selenium::Remote::WebElement object

How to override default_finder on driver initialization

my $driver = Selenium::Remote::Driver->new(default_finder => ‘css');

# default to css now instead of path $driver->find_element(‘h3.article’);

Convenience methodsfind_element_by_class

find_element_by_class_name

find_element_by_css

find_element_by_id

find_element_by_link

find_element_by_link_text

find_element_by_name

find_element_by_partial_link_text

find_element_by_tag_name

find_element_by_xpath

find_elementsmy $ar = $driver->find_elements($search,$opt_locator);

Same as find_element, but returns an array, or array ref

Warning — latest Selenium::Remote::Driver does not croak now on find_elements with size of 0 (none found)

— but may in the future

Strategies for testing for elements that aren’t there

• find_element (trap croak / eval / try/catch) • find_elements?? (0 size array(ref), or croak pay

attention to what your version does)

• Test::Selenium::RemoteDriver method $driver-

>find_no_element_ok($search_target [,$finder,

$desc ]); • use JavaScript $driver->execute_script(‘return

your_test_for_no_element()’);

css # <div class=“header”></div> $driver->find_element(‘.header’,’css);

Locate Via…

name # <input type=“text” name=“user” value=“” /> $driver->find_element(‘user’,’name’);

link_text # <a href=“/schedule.html”>Schedule</a> $driver->find_element(‘Schedule’,’link_text’);

xpath # <div id=“logo”><a href=“/”></div> $driver->find_element(‘//*[@id=“logo"]/a’,’xpath’);and more… class, class_name, id, link, partial_link_text, tag_name

find_element methods return (on success)

Selenium::Remote::WebElement objects

Selenium::Remote::WebElement defines methods to take action on the elements or test attributes of them, look at the documentation to see what’s available

This allows you to chain calls like: $driver->find_element(‘.foo’,’css’)->click(); $driver-find_element(‘username’,’name’)->send_keys(‘bob’);

Selenium::Remote::WebElement

click submit send_keys is_selected set_selected toggle is_enabled get_element_location get_element_location_in_view get_tag_name

clear get_attribute get_value is_displayed is_hidden drag get_size get_text get_css_attribute describe

Selenium::Remote::WebElement Methods

Managing Waits For Your

Tests

WaitsTests need to properly wait for completion of

actions, before looking for an element provided as a result of previous action

Load a new page

Change current page via Ajax or JS

When you….

Too short/ Non-Existent = failing tests

Too long = (long sleeps), slow test suites

Just right - wait only as long as you need to = passing tests + fast test suites

Range of Waits

Selenium::Remote::Driver waits for new page loads

Default Behavior:polls and waits for up to ‘page load’ timeout (default 180 secs)

You can change this timeout via:

$driver->set_timeout('page load', 10000); # 10 second timeout in ms

Controls actions that take you to a new page. ie: navigate to a new page, full page form submit

Selenium::Remote::Driver waits for in page element

changes

poll and wait up to implicit_wait_timeout for find_element - default is 0 ms

You can change this timeout via:

$driver->set_implicit_wait_timeout(5000) # change to 5 secs in ms

Selenium::Remote::Driver (wait for condition)

Selenium RC has a method, which runs a piece of JS checking for a condition for up to a timeout threshold

# wait until a new article is inserted $sel->wait_for_condition(‘window.$(".article").length >’.$orig_count, 10000);

You could write your own method to extend Selenium::Remote::Driver to provide the same functionality (sample of this in repo)

Example Tests

These will cover some tests that go beyond navigating pages / links / and

filling out forms

Taking a Screenshot

use Selenium::Remote::Driver;

my $driver = Selenium::Remote::Driver->new(); $driver->get("http://blogs.perl.org"); $driver->capture_screenshot("screenshot.png"); $driver->quit();

Running JS in your tests$driver->execute_script() method to run JS and return

resultsuse Selenium::Remote::Driver; use Test::More; my $driver = Selenium::Remote::Driver->new(); $driver->navigate("http://timvroom.com/selenium/js_testing.html"); my $shopping_count = $driver->execute_script( 'return $(".shopping-list li").length' ); ok($shopping_count >= 3, "Shopping list has at least 3 items");

done_testing();

Verify JS library inclusionuse Selenium::Remote::Driver;use Test::More;

my $driver = Selenium::Remote::Driver->new();

$driver->navigate("http://timvroom.com/selenium/js_testing.html");

ok(js_defined("window.jQuery"), "jQuery installed on $_[0]");ok(js_defined("window.jQuery.cookie"), "jQuery cookie plugin installed");ok(js_defined("window.Handlebars"), "has Handlebars installed");

done_testing();

sub js_defined { my $val = shift; my $ret = $driver->execute_script("return !(typeof $val === 'undefined')"); return $ret;}

Capturing Network Data

WWW::Selenium::NetworkCapture - gives you a way of seeing all the HTTP requests, and response information generated by your Selenium

session

Webdriver doesn’t offer this capability, but you could run your tests through a webproxy, and then analyze results

Ex) Test for missing failed resources loading (Source HTML)

<html> <head> <title>Broken resources test page</title> </head> <body> <h1>Broken resources test page</h1> <style type="text/css"> #logo { background:transparent url(/missing_logo_call_from_css.png) no-repeat top left; float:left; height: 20px; width: 120px; } </style> <div id="logo"></div> <img src="broken_image.png" alt="broken_image"> <script src="broken_script.js" type="text/javascript"> </body> </html>

use WWW::Selenium;use WWW::Selenium::NetworkCapture;use Data::Dumper;use Test::More;

my $sel=WWW::Selenium->new( browser_url => "http://timvroom.com/selenium/ga_test.html");$sel->start();$sel->{session_id} = $sel->get_string( "getNewBrowserSession", $sel->{browser_start_command}, $sel->{browser_url}, undef, 'captureNetworkTraffic=true');

$sel->open("http://timvroom.com/selenium/broken_resources.html");$sel->wait_for_page_to_load(10000);

my $traffic_xml = $sel->get_string('captureNetworkTraffic', 'xml');

my $netcap = WWW::Selenium::NetworkCapture->new($traffic_xml);my @stat_code_urls = map { [$_->{statusCode}, $_->{url}]} grep { $_->{url} !~ /favicon.ico/ } grep { $_->{statusCode} =~/^(4|5)/ } @{$netcap->{dom}{entry}};

ok(!@stat_code_urls, "Resources loaded w/o 40x or 50x errors for $key $url\n") or diag Dumper(@stat_code_urls);$sel->stop();done_testing();

Test Script

not ok 1 - Resources loaded w/o 40x or 50x errors for## Failed test 'Resources loaded w/o 40x or 50x errors for# '# at missing_resources.t line 27.# $VAR1 = [# [# '404',# 'http://timvroom.com/selenium/broken_image.png'# ],# [# '404',# 'http://timvroom.com/selenium/broken_script.js'# ],# [# '404',# 'http://timvroom.com/missing_logo_call_from_css.png'# ]# ];1..1# Looks like you failed 1 test of 1.

Results

use WWW::Selenium;use WWW::Selenium::NetworkCapture;use Data::Dumper;use Test::More;

my $sel=WWW::Selenium->new( browser_url => "http://timvroom.com");$sel->start();$sel->{session_id} = $sel->get_string( "getNewBrowserSession", $sel->{browser_start_command}, $sel->{browser_url}, undef, 'captureNetworkTraffic=true');

$sel->open("http://timvroom.com/selenium/ga_test.html");$sel->wait_for_page_to_load(10000);

my $traffic_xml = $sel->get_string('captureNetworkTraffic', 'xml');

my $netcap = WWW::Selenium::NetworkCapture->new($traffic_xml);

my @requests = @{$netcap->{dom}{entry}};

my @ga_track_pv = grep { $_->{url} =~ m|www.google-analytics.com/r/collect\?.*t=pageview| } @requests;my @non_200_ga_track = grep { !$_->{statusCode} == 200 } @ga_track_pv; ok(@ga_track_pv == 1, "Got exactly 1 GA track pageview request");ok(!@non_200_ga_track, "All GA track pageview requests got a 200 statusCode");

Check for Google Analytics Tracking

Results:

ok 1 - Got exactly 1 GA track pageview request ok 2 - All GA track pageview requests got a 200 statusCode 1..2

Now let’s hit the

Selenium Playground!!!

Remind us again what this fun thing is? This is a static page with automation tasks you can run your

automated script against and then check the results. Hopefully a good way to get your feet wet with web

automation

Start with run_playground_empty.pl from the repo which will load the page and check results (which gives us lots

of test fails)

( http://tinyurl.com/selplay )

Navigate to:

http://tinyurl.com/selplayon your phone or computer if you want to give input on the tasks from the list we demo in the

presentation

(I’ll ask for suggestions — raise your hand and call out a number you’d like to see demo-ed once I call on you)

If you want to see the script that solves in action run… run_playground_answers.pl (or

check answers if you’re stuck on a task)

Questions? Talk repo at:

https://github.com/tvroom/selenium-testing-with-perl

Short url of ( http://tinyurl.com/sel-perl )

Thanks for your time!

and now a short Public Service Announcement….

Take a few minutes and provide your feedback - so we can learn what worked, what didn’t and how to improve

and probably all other speakers would like your HONEST feedback on the online speaker evaluations.

(Check your email for instructions)

This friendly guy…..

Thanks for listening

Talk repo at: https://github.com/tvroom/selenium-testing-with-perl

Short url of ( http://tinyurl.com/sel-perl )

Recommended