ADF and Selenium Component Based Unit Testing About Us Richard Olrichs Wilfred van der Deijl MN The Future Group www.olrichs.nl www.redheap.com @richardolrichs @wilfreddeijl Agenda Demo: Selenium Plain Selenium Examples Page Objects Demo: ADF Selenium ADF Selenium Tools Demo: Testing Your Bounded Taskflows Selenium 101 Demo Selenium 101 public void simpleTest() { WebDriver driver = new FirefoxDriver(); driver.get("http://google.com/?hl=en"); WebElement searchBox = driver.findElement(name("q")); searchBox.sendKeys("adf selenium"); searchBox.submit(); } Selenium History Selenium v1 ● Uses JavaScript injection to emulate user interaction Very flaky with modern apps ● Used in OTN Article (don’t do that) Selenium v2 (aka WebDriver) ● Native events to drive browser Page Objects Page Objects Martin Fowler: “It should provide an interface that's easy to program to and hides the underlying widgetry in the window” Source: martinfowler.com/bliki/PageObject.html Also advocated by Selenium team ADF Selenium Tools ADF Selenium Tools github.com/wvanderdeijl/adf-selenium Two JDev 12c Projects: ● ● SeleniumTools - Library JAR RichClientDemoTest - Sample JUnit tests against ADF 12c component demo ADF Selenium Demo Basic JUnit Example @Test public void testHover() { CalendarDemoPage page = pages.goHome(); AdfCalendar calendar = page.findCalendar(); calendar.hoverActivityInView(0); assertEquals("NOTE: This popup is for demo purposes only;...", page.findPopupNote().getValue()); } Basic JUnit Example Page Object @Test public void testHover() { CalendarDemoPage page = pages.goHome(); AdfCalendar calendar = page.findCalendar(); calendar.hoverActivityInView(0); assertEquals("NOTE: This popup is for demo purposes only;...", page.findPopupNote().getValue()); } Basic JUnit Example ADF Component Object @Test public void testHover() { CalendarDemoPage page = pages.goHome(); AdfCalendar calendar = page.findCalendar(); calendar.hoverActivityInView(0); assertEquals("NOTE: This popup is for demo purposes only;...", page.findPopupNote().getValue()); } Basic JUnit Example @Test public void testHover() { CalendarDemoPage page = pages.goHome(); AdfCalendar calendar = page.findCalendar(); calendar.hoverActivityInView(0); assertEquals("NOTE: This popup is for demo purposes only;...", page.findPopupNote().getValue()); } Interact with ADF Component Basic JUnit Example @Test public void testHover() { CalendarDemoPage page = pages.goHome(); AdfCalendar calendar = page.findCalendar(); calendar.hoverActivityInView(0); assertEquals("NOTE: This popup is for demo purposes only;...", page.findPopupNote().getValue()); } Test Assertion Acquiring ADF Page Object @Test public void testHover() { CalendarDemoPage page = pages.goHome(); AdfCalendar calendar = page.findCalendar(); calendar.hoverActivityInView(0); assertEquals("NOTE: This popup is for demo purposes only;...", page.findPopupNote().getValue()); } ● ● How to start browser? How to navigate to this page? Acquiring ADF Page Object SeleniumTools Selenium CustomCode public class CalendarTest { @ClassRule public static WebDriverResource driver = new FirefoxDriverResource(); WebDriverResource starts (and stops) a web browser to run the tests @ClassRule: invoke JUnit rule once per test class @Rule: invoke JUnit rule for each test method Acquiring ADF Page Object SeleniumTools Selenium CustomCode public class CalendarTest { @ClassRule public static WebDriverResource driver = new FirefoxDriverResource(); @Rule public PageProvider<CalendarDemoPage> pages = new PageProvider(CalendarDemoPage.class, PAGE_URL, driver.getDriver()); PageProvider takes a WebDriver (browser) and knows how to navigate to a URL and instantiate a Page Object @Rule triggers provider for each test so we start fresh Acquiring ADF Component @Test public void testHover() { CalendarDemoPage page = pages.goHome(); AdfCalendar calendar = page.findCalendar(); calendar.hoverActivityInView(0); assertEquals("NOTE: This popup is for demo purposes only;...", page.findPopupNote().getValue()); } How does a Page Object locate components? Acquiring ADF Component SeleniumTools Selenium CustomCode import com.redheap.selenium.component.AdfCalendar; import com.redheap.selenium.page.Page; public class CalendarDemoPage extends Page { public AdfCalendar findCalendar() { return findAdfComponent("dmoTpl:cal"); } com.redheap.selenium.page.Page base class offers protected utility methods findAdfComponent uses (relative) JSF selectors Interacting with ADF Components How did we implement @Test component methods? public void testHover() { CalendarDemoPage page = pages.goHome(); AdfCalendar calendar = page.findCalendar(); calendar.hoverActivityInView(0); assertEquals("NOTE: This popup is for demo purposes only;...", page.findPopupNote().getValue()); } Interacting with ADF Components import com.redheap.selenium.component.AdfComponent; import org.openqa.selenium.WebElement; import org.openqa.selenium.interactions.Actions; SeleniumTools Selenium CustomCode public class AdfCalendar extends AdfComponent { public void hoverActivityInView(int index) { WebElement element = findAllActivitiesInView().get(index); // move mouse to element and wait for ADF to detect hover new Actions(getDriver()).moveToElement(element).pause(1000).perform(); waitForPpr(); } } Component class encapsulates interaction with HTML elements Selenium Actions can interact with browser and mouse AdfComponent.waitForPpr waits for any PPR and complete javascript processing Interacting with ADF Components @Test public void testHover() { CalendarDemoPage page = pages.goHome(); AdfCalendar calendar = page.findCalendar(); calendar.hoverActivityInView(0); assertEquals("NOTE: This popup is for demo purposes only;...", page.findPopupNote().getValue()); } How did we implement component methods? Interacting with ADF Components SeleniumTools Selenium CustomCode import com.redheap.selenium.component.AdfComponent; public class AdfOutputText extends AdfComponent { public Object getValue() { return executeScript("var cmp=AdfPage.PAGE.findComponentByAbsoluteId(arguments[0]);" + "return cmp.getValue()", getClientId()); } } CalendarDemoPage.findPopupNote returns AdfOutputText component Component classes frequently use javascript to interact with components AdfComponent base class offers protected executeScript method Every component becomes a client component with oracle.adf.view.rich.automation.ENABLED=true in web.xml Selenium Tools Component Classes ● ● ● Re-use a lot of logic that would otherwise live in Page Objects Rely heavily on ADF JavaScript API All extend from AdfComponent ○ ○ ○ ○ ○ ○ click(), contextClick(), doubleClick() dragAndDropTo(AdfComponent target) findAdfComponent(String childId) isDisabled(), isDisplayed() scrollIntoView() and a few more Component Class Example: AdfTable long getRowCount() findAdfComponent(String child, int row) scrollToRowIndex(int row) discloseRowDetail(int row) List<Integer> getDisclosedRows() selectRow(int row) selectToRow(int row) selectAdditionalRow(int row) ... and all AdfComponent methods Testing Your Bounded Taskflows Bounded Taskflow Recap JUnit Test your taskflows Use TaskFlow Tester for isolated tests - java.net/projects/adf-task-flow-tester PerceptualDiffMatcher - bit.ly/pdiff or look at more powerful Depicted at github.com/bslatkin/dpxdt JaCoCo Code Coverage JUnit Rule to dump execution data - bit.ly/jacoco-rule Optional reporter to write html report - bit.ly/jacoco-report Browser of choice: Firefox, PhantomJS or any other... Resources github.com/wvanderdeijl/adf-selenium www.seleniumhq.org - mostly v1 docs seleniumhq.github.io/docs/ - new v2 docs Demo shots (reference material) Example Test Start test in Taskflow Tester Basic assertions Compare screenshot Taskflow running in Taskflow Tester Validation Error triggered by test JUnit Test Runner in JDeveloper JaCoCo Code Coverage Report Not all paths tested JaCoCo Code Coverage shows this method wasn’t covered in test Screenshot Diff Assertion Violation Tested application looks different during test compared to “known good reference” “Known good reference” Actual screenshot during test Diffs indicated