{"id":1430,"date":"2018-08-21T07:58:09","date_gmt":"2018-08-21T14:58:09","guid":{"rendered":"https:\/\/angryweasel.com\/blog\/?p=1430"},"modified":"2018-08-21T07:58:17","modified_gmt":"2018-08-21T14:58:17","slug":"coding-with-testproject","status":"publish","type":"post","link":"https:\/\/angryweasel.com\/blog\/coding-with-testproject\/","title":{"rendered":"Coding with TestProject"},"content":{"rendered":"<p>This is my third post on <a href=\"https:\/\/testproject.io\">TestProject<\/a>. The previous posts are&nbsp;<a href=\"https:\/\/angryweasel.com\/blog\/getting-started-with-testproject\/\">Getting Started with Test Project<\/a>&nbsp;and&nbsp;<a href=\"https:\/\/angryweasel.com\/blog\/experimenting-with-mobile-automation\/\">Experimenting with Mobile Automation<\/a>.<\/p>\n<p>The last thing I wanted to explore a bit with TestProject is directly writing some code for some (potentially) more advanced testing. Currently, the TestProject SDK supports Java, but SDKs for JavaScript, Python, C#, and Groovy are under construction. Fortunately, Java is a language I\u2019m at least partially competent in, so I feel comfortable giving it a shot.<\/p>\n<h2>The Application Under Test<\/h2>\n<p>To keep things simple, I just used the TestProject Example page at https:\/\/example.testproject.io\/web\/.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" title=\"\" src=\"https:\/\/angryweasel.com\/blog\/wp-content\/uploads\/2018\/08\/null-18.png\" alt=\"\" width=\"433\" height=\"254\"\/><\/p>\n<p>To start, I cloned the <a href=\"https:\/\/github.com\/testproject-io\">latest samples from GitHub<\/a>, and grabbed the <a href=\"https:\/\/app.testproject.io\/#\/developers\">TestProject Developer SDK<\/a>. Note that there\u2019s a link on the bottom of the SDK page link above that allows you to copy your developer key. You\u2019ll need that to get started as well.<\/p>\n<p>The next steps are documented well in the readme.md for the web project (<a href=\"https:\/\/github.com\/testproject-io\/java-sdk-examples\/tree\/master\/Web\">TestProject Java SDK &#8211; Quick Start for Web<\/a>). Short version is that to get the samples to run, you need to make sure the build.gradle file knows where the SDK jar file lives, and add your developer key to the test runner java file.<\/p>\n<p>I could write a series of blog posts with details on gradle and java build command lines, but I probably wouldn\u2019t answer questions here as fast as a google search. Once you can build the test and test runner you can upload the test to TestProject using the New Test Wizard to upload your .jar file.<\/p>\n<p>For this blog post, I\u2019ll be playing with the <a href=\"https:\/\/github.com\/testproject-io\/java-sdk-examples\/blob\/master\/Web\/Test\/src\/main\/java\/io\/testproject\/examples\/sdk\/java\/tests\/BasicTest.java\">BasicTest sample<\/a>. I cloned the sample repo, and built it as is.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" title=\"\" src=\"https:\/\/angryweasel.com\/blog\/wp-content\/uploads\/2018\/08\/null-19.png\" alt=\"\" width=\"433\" height=\"324\"\/><\/p>\n<p>My Test Package now shows up on the Web page (note that I temporarily changed the test name to match the full namespace name of the test &#8211; and that the package includes the Extended Test from the sample project as well. From here, I can add tests and run them the same as we did with the Recorded \/ Designed tests discussed in the previous posts.<img loading=\"lazy\" decoding=\"async\" title=\"\" src=\"https:\/\/angryweasel.com\/blog\/wp-content\/uploads\/2018\/08\/image.png\" alt=\"\" width=\"550\" height=\"109\"\/><\/p>\n<h2>Coded Automation<\/h2>\n<p>The coding constructs in TestProject should feel pretty familiar to anyone who\u2019s written Selenium or a derived language before. You instantiate a web driver, tell it to do things with elements, and write some verification code.<\/p>\n<p>The entire basic test is this:<\/p>\n<pre class=\"theme:twilight striped:false nums:false tab-convert:true lang:java decode:true\" title=\"Basic Test\">public class BasicTest implements WebTest {\n\n    public String name = \"John Smith\";\n    public String password = \"12345\";\n    public String country = \"United States\";\n    public String address = \"Street number and name\";\n    public String email = \"john.smith@somewhere.tld\";\n    public String phone = \"+1 555 555 55\";\n\n    public ExecutionResult execute(WebTestHelper helper) throws FailureException {\n\n        \/\/ Get driver initialized by TestProject Agent\n        \/\/ No need to specify browser type, it can be done later via UI\n        WebDriver driver = helper.getDriver();\n\n        \/\/ Navigate to TestProject Demo website\n        driver.navigate().to(\"https:\/\/example.testproject.io\/web\/\");\n\n        \/\/ Login using provided credentials\n        LoginPage loginPage = PageFactory.initElements(driver, LoginPage.class);\n        loginPage.login(name, password);\n\n        \/\/ Complete profile forms and save it\n        ProfilePage profilePage = PageFactory.initElements(driver, ProfilePage.class);\n        profilePage.updateProfile(country, address, email, phone);\n\n        return profilePage.isSaved() ? ExecutionResult.PASSED : ExecutionResult.FAILED;\n    }\n}\n<\/pre>\n<p>&nbsp;<\/p>\n<p>I think it\u2019s pretty straightforward to read &#8211; even if you\u2019re not a &#8220;coder&#8221;.<\/p>\n<p>These two lines:<\/p>\n<pre class=\"theme:twilight striped:false nums:false tab-convert:true lang:java decode:true\" title=\"Snippet\">WebDriver driver = helper.getDriver();\ndriver.navigate().to(\"https:\/\/example.testproject.io\/web\/\");\n\n<\/pre>\n<p>&#8230;start WebDriver and navigate to our page-under test.<\/p>\n<p>The next two lines initialize the Page Objects on the profile page and fill out the fields there. (please &#8211; if you write web automation, use Page Objects. TestProject gets serious points from me for including Page Objects in their sample) &#8211; and then login to the page.<\/p>\n<pre class=\"theme:twilight striped:false nums:false tab-convert:true lang:java decode:true\" title=\"Snippet\">ProfilePage profilePage = PageFactory.initElements(driver, ProfilePage.class);\nprofilePage.updateProfile(country, address, email, phone);\n\n<\/pre>\n<p>And finally, there\u2019s some verification to ensure the page is saved.<\/p>\n<pre class=\"theme:twilight striped:false nums:false tab-convert:true lang:java decode:true\" title=\"Snippet\">return profilePage.isSaved() ? ExecutionResult.PASSED : ExecutionResult.FAILED;\n\n<\/pre>\n<p>This is a sample, but it\u2019s worth mentioning that the oracle for this test isn\u2019t great.<\/p>\n<pre class=\"theme:twilight striped:false nums:false tab-convert:true lang:java decode:true\" title=\"Snippet\">public boolean isSaved() {\n    return savedElement.isDisplayed();\n}\n\n<\/pre>\n<p>As testers, we\u2019ve all seen pages that display the proper text, but don\u2019t do _everything_ we expect. On one hand, I\u2019d lean toward a more robust verification &#8211; OTOH, writing a massively complex verification function is part of why I get scared of a lot of UI automation.<\/p>\n<h2>Play Time<\/h2>\n<p>Cool so far, but let\u2019s muck with this a bit and think about how we can use the power of the computer to do a bit more testing.<\/p>\n<p>In a much longer blog post, I would create an array of structures containing names, addresses, etc. and loop through them ensuring that all could be entered correctly. I\u2019d pick canonical examples from different locales to ensure (or help with) globalization, and probably add a handful of contrived examples as well (sql injection, cross site scripting, etc.). Note that I did try logging in with a user name of &lt;script&gt;alert(\u2018ruh-roh\u2019);&lt;\/script&gt; with no problems.<\/p>\n<h3>Loops<\/h3>\n<p>First thing to do is wrap the whole function in a loop. Other than adding a loop construct, I\u2019ll track a count of failures (so I can just return pass \/ fail once).<\/p>\n<h3>Names<\/h3>\n<p>I wrote a tiny helper function to create a random string from characters in the ISO Latin Alphabet. I could just as easily used UTF-16.<\/p>\n<pre class=\"theme:twilight striped:false nums:false tab-convert:true lang:java decode:true\" title=\"Snippet\">public String randomString(int length) {\n    byte[] array = new byte[length];\n    new Random().nextBytes(array);\n    String generatedString = new String(array, Charset.forName(\"ISO_8859_1\"));\n    return generatedString;\n}\n\n<\/pre>\n<p>I\u2019ll use this to create a random first name and last name of (arbitrary) lengths 10 and 20.<\/p>\n<pre class=\"theme:twilight striped:false nums:false tab-convert:true lang:java decode:true\" title=\"Snippet\">\/\/ Make a name of random ISO_8859_1 characters\nname = randomString(10) + \" \" + randomString(20);\n\n<\/pre>\n<p>At this stage, I have a test that loops, trying different names&#8230;but there\u2019s one thing interesting on the following page I want to investigate.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" title=\"\" src=\"https:\/\/angryweasel.com\/blog\/wp-content\/uploads\/2018\/08\/null-20.png\" alt=\"\" width=\"570\" height=\"113\"\/><\/p>\n<p>The web page grabs the name I entered on the previous screen and displays it. I want to write a test to verify that the name is correct.<\/p>\n<p><i>Probably<\/i> the easiest thing to do at this step is right click on the text, choose Inspect (assuming Chrome), and then either grab the id and find the text element by id, or choose Copy and Copy XPath (I find XPath to often be ugly, but in this case it\u2019s <b>very<\/b> clean).<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" title=\"\" src=\"https:\/\/angryweasel.com\/blog\/wp-content\/uploads\/2018\/08\/null-21.png\" alt=\"\" width=\"624\" height=\"420\"\/><\/p>\n<p>Based on that bit of investigation, I added the following bit of code to ProfilePage.java<\/p>\n<pre class=\"theme:twilight striped:false nums:false tab-convert:true lang:java decode:true\" title=\"Snippet\">@FindBy(id = \"greetings\")\nprivate WebElement greetingElement;public boolean isGreetingCorrect(String name) {\n    String greetingsText = greetingElement.getText();\n    return greetingsText.contains(name);\n}\n\n<\/pre>\n<p>It grabs the full text of the greeting element, and returns true if the string contains the name. Pretty darn straightforward.<\/p>\n<p>My slightly more interesting basic test now looks like this.<\/p>\n<pre class=\"theme:twilight tab-convert:true lang:java decode:true \" title=\"Snippet\">public class BasicTest implements WebTest {\n\n    \/\/public String name = \"John Smith\";\n    public String password = \"12345\";\n    public String country = \"United States\";\n    public String address = \"Street number and name\";\n    public String email = \"john.smith@somewhere.tld\";\n    public String phone = \"+1 555 555 55\";\n\n    public ExecutionResult execute(WebTestHelper helper) throws FailureException {\n\n        \/\/ Get driver initialized by TestProject Agent\n        \/\/ No need to specify browser type, it can be done later via UI\n        WebDriver driver = helper.getDriver();\n        \n        String name;\n        int failCount = 0;\n        int limit = 10;\n\n        for (int i = 0; i &lt; limit; i++)\n        {\n            \/\/ Make a name of random ISO_8859_1 characters\n            name = randomString(10) + \" \" + randomString(20);\n\n            \/\/ Navigate to TestProject Demo website\n            driver.navigate().to(\"https:\/\/example.testproject.io\/web\/\");\n\n            \/\/ Login using provided credentials\n            LoginPage loginPage = PageFactory.initElements(driver, LoginPage.class);\n            \n            loginPage.login(name, password);\n\n            \/\/ Complete profile forms and save it\n            ProfilePage profilePage = PageFactory.initElements(driver, ProfilePage.class);\n\n            \/\/ NOTE: I'm a fan of the one test, one verification school, I'm making\n            \/\/ an exception FOR SAMPLE PURPOSES.\n            if (!profilePage.isGreetingCorrect(name)){\n                failCount += 1;\n            }\n\n            profilePage.updateProfile(country, address, email, phone);\n            if (!profilePage.isSaved())\n            {\n                failCount += 1;\n            }\n        }\n        return (failCount == 0) ? ExecutionResult.PASSED : ExecutionResult.FAILED;\n    }\n    public String randomString(int length) {\n        byte[] array = new byte[length]; \n        new Random().nextBytes(array);\n        String generatedString = new String(array, Charset.forName(\"ISO_8859_1\"));\n     \n        return generatedString;\n    }\n}\n\n<\/pre>\n<p>Of course, there are thousands of other test and verification options open at this stage, but to me, the TestProject stuff has been extremely easy to use and highly intuitive.<\/p>\n<h2>But Wait\u2026 Addons<\/h2>\n<p>I\u2019m not going to be able to do the idea of Addons justice, but to me, this is a big differentiator with TestProject.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-1442 aligncenter\" src=\"https:\/\/angryweasel.com\/blog\/wp-content\/uploads\/2018\/08\/Addons.png\" alt=\"\" width=\"728\" height=\"374\" srcset=\"https:\/\/angryweasel.com\/blog\/wp-content\/uploads\/2018\/08\/Addons.png 2252w, https:\/\/angryweasel.com\/blog\/wp-content\/uploads\/2018\/08\/Addons-300x154.png 300w, https:\/\/angryweasel.com\/blog\/wp-content\/uploads\/2018\/08\/Addons-768x395.png 768w, https:\/\/angryweasel.com\/blog\/wp-content\/uploads\/2018\/08\/Addons-1024x527.png 1024w\" sizes=\"auto, (max-width: 728px) 100vw, 728px\" \/><\/p>\n<p>Addons are sharable components that perform a specific task. My dumb name generator could be spruced up to actually draw from a database of real names and be uploaded as a shareable component that anyone can use. I expect, in fact, that as TestProject grows, that the number of Addons &#8211; and their value will increase immensely.<\/p>\n<p>Also note that the <b>jRand<\/b> addon from the TestProject Community addons is a nice shortcut for a lot of this particular test scenario. It generates a random (and usable) value for just about any sort of entry field you could think of, including sentences, paragraphs, birthdays, addresses, altitude, credit card details and more.<\/p>\n<p>There\u2019s definitely a lot of potential for good code reuse with the addons concept.<\/p>\n<h2>And Finally\u2026<\/h2>\n<p>I covered a lot, but not nearly as much as I should have. If you play with the coded tests in TestProject, I suggest going through the docs on github, and <b>not<\/b> my walkthrough above, because I probably forgot just enough steps (<i>some<\/i> on purpose) to throw you into a pit of despair.<\/p>\n<p>But I do suggest giving this framework a shot. As always, remember that my suggestion is to automate at the Web UI level <i>only to verify things that can only be found at the UI level<\/i>. Used correctly, I think this automation framework and supported web tools can give a lot of value to a lot of software teams.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is my third post on TestProject. The previous posts are&nbsp;Getting Started with Test Project&nbsp;and&nbsp;Experimenting with Mobile Automation. The last thing I wanted to explore a bit with TestProject is directly writing some code for some (potentially) more advanced testing. Currently, the TestProject SDK supports Java, but SDKs for JavaScript, Python, C#, and Groovy are&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[1],"tags":[],"class_list":["post-1430","post","type-post","status-publish","format-standard","hentry","category-allposts"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/angryweasel.com\/blog\/wp-json\/wp\/v2\/posts\/1430","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/angryweasel.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/angryweasel.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/angryweasel.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/angryweasel.com\/blog\/wp-json\/wp\/v2\/comments?post=1430"}],"version-history":[{"count":5,"href":"https:\/\/angryweasel.com\/blog\/wp-json\/wp\/v2\/posts\/1430\/revisions"}],"predecessor-version":[{"id":1449,"href":"https:\/\/angryweasel.com\/blog\/wp-json\/wp\/v2\/posts\/1430\/revisions\/1449"}],"wp:attachment":[{"href":"https:\/\/angryweasel.com\/blog\/wp-json\/wp\/v2\/media?parent=1430"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/angryweasel.com\/blog\/wp-json\/wp\/v2\/categories?post=1430"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/angryweasel.com\/blog\/wp-json\/wp\/v2\/tags?post=1430"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}