In our previous blog post we introduced a Continuous Integration server called Hudson to run automated tests against a PeopleSoft system for us. We didn’t really get into any test content itself though; we just ran some dummy Application Engine code to show how Hudson can work with PeopleSoft.
Now that the framework piece is in place though, we can begin creating tests. For this blog post we’re going to focus on testing functional aspects of PeopleSoft, not performance testing.
Functional Testing Webinar
Before getting into the details, I wanted to point out that we have an upcoming webinar on this exact topic on May 20th, so if you’re interested be sure to register for that.
Functional Testing Options
There’s a variety of different ways for doing functional testing. Dave Bain from Oracle recently announced the availability of a PeopleSoft specific unit testing tool called PS/Unit. We’ll look at PS/Unit in an upcoming post, but if you’re not familiar with unit testing you can think of it as testing the lowest level “units” of your code.
Another approach is doing browser-based testing. Browser based testing means having automated tests that drive an actual web browser instance and simulate an actual user’s behavior. The primary benefit of browser based testing is that you can be reasonably sure that you are testing PeopleSoft as your users will be seeing and working with it.
In fact, browser based testing can be thought of as automating the way that most people test their PeopleSoft implementations today; which, sadly enough, is by having some of their functional users run through a series of manual tests in their browsers. Expensive, error-prone, incomplete are some of the adjectives that spring to mind when describing manual testing. Not to mention just the hassle of trying to get the functional users to participate in testing. They may be willing to participate once or twice, but if you need to incorporate any additional testing beyond your original project plan, it is a major hassle.
Browser based testing
One tool that we like for functional testing via the browser is called Selenium. Selenium can run tests across multiple different browsers, and there is a plugin for running Selenium tests via Hudson so we can automate the execution and management of our automated tests.
Getting Started with Selenium
Before we get into running our Selenium tests in Hudson, we need to create some tests. The easiest way to do this is with the Selenium IDE, which is a Firefox plugin and can record, edit and debug tests. The tests themselves can be executed in other browsers, so we’re only dependent on Firefox for our initial test development.
So if you’re not already in Firefox right now, switch over to Firefox and launch the Selenium IDE plugin installer. Here is the current link, which is for version 1.0 beta 2, but you can check their download page as well to see if there are updated versions available.
Your First Test
After installing Selenium IDE, you can launch it from the Tools -> Selenium IDE menu in Firefo
When Selenium IDE launches it automatically starts in recording mode, so you can switch back to your main Firefox window and begin working. For this first example, I
- Entered the URL for the PeopleSoft signon page
- Typed in the password (the user ID was already defaulted for me)
- Clicked “Sign In”
- Waited for the portal home page to load
- Clicked the “Logout” link
Then I switched back to the Selenium IDE window and stopped recording by clicking the Red button in the recording toolbar (unfortunately there are not keyboard equivalents for the Selenium recording toolbar). Let’s see what Selenium captured for us.
The first thing to notice is that Selenium has captured http://www.gsdemo.com as the base URL. All URL commands will be relative to this (there are some extra things to know if you plan to write tests that go across more than one base URL).
The first actual command that Selenium has captured is the
open command. All Selenium commands take the form of Command, Target, Value. Here the target is the URL
/psp/ps/?cmd=login, which gets combined with the base URL. This opens the login page.
Next is the
type command, where we entered the password. In this case Selenium realized that the pwd DOM ID for the password field was the simplest choice that would work. You’ll notice that the Target editbox has become a dropdown box which shows some alternate methods of referencing the password field.
The next command is the
clickAndWait command. Selenium recognized the Sign In button via it’s DOM ID of Submit and used that.
The last command is also the
clickAndWait command, but when we logged out, there was no unique DOM ID that was associated with the Logout link, so Selenium recognized the click by noting that the link text was Logout (eagle-eyed readers will note that the logout text in this environment has actually been customized from an earlier blog entry).
At this point
we can repeatedly click the play button in the Selenium recording toolbar and watch the browser login and logout. There are actually two play buttons, one is for the current test case and one is for playing back an entire test suite (which is many test cases grouped together). In this instance it doesn’t matter, but once you actually create test suites, then you’ll want keep them straight in your head.
If you have any problems at this point playing back the test, you can try adjusting slider control in the Selenium IDE toolbar towards the “Slow” end. This causes Selenium IDE to wait a bit longer for commands to show some results.
Modifying the Test
Each of the commands that Selenium captured for us is modifiable, including being able to delete it. So to make our test a bit more exciting, we highlight the last command (the clickAndWait command that logged out) and delete it, since we don’t want to logout just yet.
Then play the script again. This time when the script finishes you will be left on the portal home page (since we’re not logging out). Pressing the Record button will start recording again at the end of the script.
This time we will navigate via the portal menu navigation to PeopleTools -> Security -> User Profiles -> User Profiles. Once the User Profiles component search page is up, we type in
PTDMO and then press the Search button.
Before playing back this script, I made one manual edit. I changed the very top command from navigating to cmd=login to cmd=logout. PeopleTools will send back the signon page in both cases, but by starting with the logout command you clear server resources from the previous script run. Your system administrators will thank you for this small act of kindness 🙂
When playing back the script, I have a small problem; the script does not playback successfully. It tells me that it can’t find a frame named “NAV”.
The source of the problem is that Selenium does not quite understand the way that PeopleTools is doing it’s navigation. When we click on the first navigation link from the Portal home page Selenium records the following two commands.
- Command: clickAndWait, Target: link=(the name of the link)
- Command: waitForPopup, Target: _parent, Value: 30000
After we leave the portal home page though, we are getting into PeopleTools generated HTML frames. The next command that Selenium IDE records is Command: selectFrame, Target: name=NAV. The NAV is the name that PeopleTools gives to the menu frame once we leave the portal home page. What we want at this point is for Selenium to wait for the NAV frame to load before we select it, but the commands that Selenium is generating are only waiting for our top level page to load.
Rather than run the entire test at a slower rate so that we’re sure that the navigation frame is loaded, we’ll insert an extra command before the
selectFrame command that will tell Selenium to wait for that frame to load. To do that we
- Right click on the
- Select “Insert New Command” from the popup menu
waitForFrameToLoadin the Command edit box
TargetContentin the Target edit box
30000in the Value edit box
One cool thing about Selenium IDE is that as you enter commands, you get type-ahead support for what commands are available. Once you select a command, then you get the help text for it automatically displayed at the bottom of the IDE window. Very nice! Here’s a full list of all of the different commands that Selenium supports.
The other nice thing about the IDE is that we can copy and paste our
waitForFrameToLoad command instead of typing it in everywhere. There are a few more places in our navigation script where the
selectFrame command is used, so we’ll paste it before each one. That allows us to run our script at full speed, but have it wait for the navigation to finish loading if the webserver is slow for some reason. Note that after we finish navigating we are then waiting for the frame called “TargetContent”, so we use that name instead.
In some cases you may want to include the navigation portion in your tests, but in many cases you’ll want to just test a certain functional area. A good trick for doing this is just have Selenium go straight to the page that you want instead of navigating to it.
open command in Selenium can be used in the middle of your test scripts, not just at the beginning, so we can replace all of the navigation commands with the
open command and the value “/psc/ps/EMPLOYEE/PT_LOCAL/c/MAINTAIN_SECURITY.USERMAINT.GBL”. Note that we used the psc servlet instead of the default psp. That will completely bypass the portal and just load the underlying component itself.
Part of testing is making assertions about the behavior that is seen. Selenium supports making assertions about things as simple as text being on the page to whether specific cookies are present (a good way of checking that you are logged in is asserting that you have a PS_TOKEN cookie) and even as detailed as making assertions about the HTML source code that has been generated for a page.
There are somewhere in the neighborhood of around 100 different things that Selenium can make assertions about, so you should be able to come up with something that helps you validate the desired functional behavior that you are testing. For the example that we have been going through I added a the
assertTextPresent command with the Target set to “Confirm Password” since that is the label for one of the fields on the User Profile page. This assertion will return true if “Confirm Password” is present anywhere on the entire page.
We can make some tighter assertions by narrowing the focus from the entire page to specific elements. The above assertion could also be implemented as the
assertText, which takes an element locator in addition to the text that is being checked. We saw the use of element locators when working with the login form above; an element locator is just some way of identifying a single element on the page.
PeopleTools will typically generate unique IDs for each form field on page. These are normally the underlying record name and field name for the underlying value in the database (with row numbers if working with multiple rows). In the case of checking a label, PeopleTools will generate a “label” element, with the “for” attribute referencing which form field the label is for.
In this case, we end up with our element locator as
//label[@for='PSUSRPRFL_WRK_OPERPSWDCONF'] because we want the label for the OPERPSWD_CONF field from the PSUSRPRFL_WRK record.
In the above screenshot we can see that our
assertTextPresent assertion passed, but our
assertText assertion failed. Selenium IDE provides the reason for the failure in the log though. The
assertText assertion is making an exact check of the element’s text, while the
assertTextPresent assertion merely checks that the text exists at all across the entire page. Since PeopleTools is generating a colon at the end of the label, we would need to incorporate that in our assertion.
Saving Our Tests
Up to this point we haven’t saved our test logic at all. In the Selenium IDE window, select File -> Save Test Case (or press Ctrl-S for a keyboard equivalent). You’ll be prompted for a file name. I selected TestUserProfile.html. By default Selenium stores it’s list of commands in a special HTML format, which is why we used that extension.
You can also have Selenium export a test case to various other programming languages (Java, C#, Perl, PHP, Python, and Ruby are all supported currently). The benefit to using a programming language for controlling Selenium is that you can then do things like have conditional logic, common subroutines, etc. The drawback is that you lose the ability to use Selenium IDE to work with them at that point. We’ll stick with the plain HTML format for now while we’re still getting familiar with Selenium, but future blog posts will get into using the programming language integration.
In the above screenshot you’ll notice that I expanded out one section of the Selenium IDE window. That shows us the current test case being worked (TestUserProfile). If we click File -> New Test Case, we’ll get an “Untitled” entry added in the list of test cases and an empty list of commands. We would then build up our test case just like we did above and save it.
Once you have a few test cases, then you can select File -> Save Test Suite to group the test cases together. You can copy and paste commands between different test cases, so if you find one test case is starting to cover too much functionality, you can break it into multiple test cases this way. Selenium lets you run either single test cases or run an entire test suite together, so you can break things in manageable chunks without losing the ability to group things appropriately.
I did notice a bug in the test case re-ordering within the Selenium IDE window though, so after you save your test suite, you’ll probably want to open to the open the test file and re-order the tests listed there so they match the order that you want to run things in.
Now that we can start coming up with some meaningful tests, the next steps from here are to incorporate the execution of our tests into our Hudson environment so that they can be run automatically for us.
We’ll also want to start integrating all of this with version control. We not only want to start keeping track of the different versions of our tests, but we also want to tie the execution of our tests to changes that are happening with the underlying PeopleSoft objects.
That way if a working test starts throwing assertion errors at some point, we’ll be able to see exactly what changes were implemented in PeopleSoft that caused the problem, along with who did it and the underlying reason that the changes were implemented. And if necessary, revert those changes to put things back the way they should be.
Labels: 2009, Browser, Sysadmin, Testing, VersionControl