{"id":6720,"date":"2016-07-28T14:42:57","date_gmt":"2016-07-28T05:42:57","guid":{"rendered":"http:\/\/www.skyarch.net\/blog\/?p=6720"},"modified":"2017-11-07T11:17:20","modified_gmt":"2017-11-07T02:17:20","slug":"customizing-selenium-pageobject","status":"publish","type":"post","link":"https:\/\/www.skyarch.net\/blog\/en\/customizing-selenium-pageobject\/","title":{"rendered":"Customizing Selenium PageObject"},"content":{"rendered":"<p><span style=\"font-size: 11px\">In this blog, we\u00a0will be using <a href=\"http:\/\/www.seleniumhq.org\/projects\/webdriver\/\">Selenium WebDriver <\/a>(Java) for my example codes.<br \/>\nAnd please note that the codes here are not tested. These are only guides and are not intended for any other purpose.<\/span><\/p>\n<p>In simple words, page objects in selenium mimics the page's UI controls\/objects so we can use it to interact with the page easily.<br \/>\nThink of the page as an object-oriented class, that is exactly what page object pattern is. The page object class serves as interface to a page of your application. It hides away the page-specific code implementations like the locators.<\/p>\n<p style=\"text-align: center\"><a href=\"http:\/\/www.skyarch.net\/blog\/wp-content\/uploads\/2016\/06\/page-object-design-pattern.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-6784 aligncenter\" src=\"http:\/\/www.skyarch.net\/blog\/wp-content\/uploads\/2016\/06\/page-object-design-pattern-300x66.jpg\" alt=\"page object design pattern\" width=\"300\" height=\"66\" srcset=\"https:\/\/www.skyarch.net\/blog\/wp-content\/uploads\/2016\/06\/page-object-design-pattern-300x66.jpg 300w, https:\/\/www.skyarch.net\/blog\/wp-content\/uploads\/2016\/06\/page-object-design-pattern.jpg 695w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><br \/>\n<span style=\"font-size: 10px\">Image taken from\u00a0<a href=\"https:\/\/solutionscafe.wordpress.com\/category\/selenium-2\/\">Page Object Pattern &amp; Page Factory<\/a><\/span><\/p>\n<p>We will use this class along with the selenium webdriver to interact with its UI.<\/p>\n<p>Before we\u00a0start building page objects for our\u00a0tests, here are some pros and cons to know.<br \/>\nPros:<\/p>\n<ol>\n<li>Your AUT's UI becomes loosely coupled from the actual test. Hence;<\/li>\n<li>Minimizing test code complexity.<\/li>\n<li>Removes redundancy.<\/li>\n<\/ol>\n<p>Con:<\/p>\n<ol>\n<li>Your code will be a little hard to read as your objects will be behind an abstraction.<\/li>\n<\/ol>\n<h3>Creating a simple Page Object Class<\/h3>\n<p>Say we have a Login Page to test and the page has 3 UI objects we need to interact with.<\/p>\n<a href=\"http:\/\/www.skyarch.net\/blog\/wp-content\/uploads\/2016\/06\/login.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-6782 aligncenter\" src=\"http:\/\/www.skyarch.net\/blog\/wp-content\/uploads\/2016\/06\/login-300x141.png\" alt=\"login\" width=\"300\" height=\"141\" srcset=\"https:\/\/www.skyarch.net\/blog\/wp-content\/uploads\/2016\/06\/login-300x141.png 300w, https:\/\/www.skyarch.net\/blog\/wp-content\/uploads\/2016\/06\/login.png 599w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a>\n<p>We would want to create a simple page object class containing each of the UI object as field\/property.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\npackage com.skyarch.PageObjects;\r\n\r\nimport org.openqa.selenium.WebDriver;\r\nimport org.openqa.selenium.WebElement;\r\nimport org.openqa.selenium.support.FindBy;\r\nimport org.openqa.selenium.support.How;\r\n\r\npublic class LoginPage {\r\n\r\n    @FindBy(how = How.NAME, using = &quot;username&quot;)\r\n    private WebElement username;\r\n\r\n    @FindBy(how = How.NAME, using = &quot;password&quot;)\r\n    private WebElement password;\r\n\r\n    @FindBy(how = How.ID, using = &quot;login&quot;)\r\n    private WebElement login;\r\n}\r\n<\/pre>\n<p>When writing the actual test, you can simply initilize the page object using the <code>initElements<\/code> method of <code>PageFactory<\/code>.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nimport org.openqa.selenium.support.PageFactory;\r\n\r\nLoginPage page = PageFactory.intElements(driver,LoginPage.class)\r\n<\/pre>\n<p>Using the objects is fairly the same as you would have when you were not using the page object pattern.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\npage.username.sendKeys(&quot;zuckerberd&quot;);\r\npage.username.sendKeys(&quot;dadada&quot;);\r\npage.login.click();\r\n<\/pre>\n<p>Now that is plainly better than doing this.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\ndriver.findElement(By.name(&quot;username&quot;)).sendKeys(&quot;zuckerberd&quot;);\r\ndriver.findElement(By.name(&quot;password&quot;)).sendKeys(&quot;dadada&quot;);\r\ndriver.findElement(By.id(&quot;login&quot;)).click();\r\n<\/pre>\n<p>Now our lives are better with the page object pattern in our tests, isn't it? \ud83d\ude42<\/p>\n<h5>But wait...<\/h5>\n<p>What if your page object class is getting bigger and your AUT's pages are having framework generated names and other stuffs that make us suffer?<br \/>\nHere is where we want to create our own locators, alternate to the ones that Selenium gives us.<\/p>\n<p>For our team, we wanted to locate elements on the page by using the best readable identifiers like labels or placeholders, button texts, etc.\u00a0We could not do this because we cannot assign a dynamic value for the\u00a0annotation (in java).<\/p>\n<p>Now you're thinking, why not just use xpath instead of creating a custom <code>ElementLocator<\/code> for the PageFactory?<\/p>\n<p>Yes, we will be using XPath, but that doesn't solve the problem of complicating our page object classes when they get bigger.<br \/>\nCreating a custom ElementLocator will abstract those long and hard to read, redundant XPaths, leaving us with only labels on the class, or maybe a little count of Xpaths, which is way better than reading the whole XPath when you only want to know where that object points to on the page.<\/p>\n<p>And not only we can create a custom <code>ElementLocator<\/code>, we can also create a custom <code>FieldDecorator<\/code>, giving us the power to fully customize our page object classes.<br \/>\nFor instance we have a page that contains <code>select<\/code> or the <a href=\"https:\/\/select2.github.io\/\"><code>select2<\/code><\/a> plugin boxes, <code>datetime<\/code> pickers, and other input plugins you can think of, and we wanted to create a simple, readable and uncomplicated page object.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\n public class ScheduledMessengerPage {\r\n\r\n     @FindBy(label=&quot;Send to:&quot;)\r\n     private Select2 userSelect2;\r\n\r\n     @FindBy(label=&quot;Subject:&quot;)\r\n     private WebElement subject;\r\n\r\n     @FindBy(label=&quot;Body:&quot;)\r\n     private WYSIWYG contentBody;\r\n\r\n     @FindBy(label=&quot;Scheduled For:&quot;)\r\n     private DateTimePicker scheduleFor;\r\n\r\n     @FindBy(label=&quot;Schedule Message&quot;)\r\n     private WebElement submit;\r\n }\r\n<\/pre>\n<p>We cannot do this without extending the <code>ElementLocator<\/code> and <code>FieldDecorator<\/code>.<\/p>\n<p>While extending these classes, we can create new interfaces and classes that will contain all the methods for the custom objects in our AUT's page. Don't worry, we can extend the WebElement interface. \ud83d\ude42<\/p>\n<p>Well, now I can say that our lives are better! \ud83d\ude00<\/p>\n<h3>Customizing the Page Object<\/h3>\n<p>Before we begin this journey of customizing our page object pattern, let's enumerate what we want to achieve.<\/p>\n<ol>\n<li>Our test page objects are more readable when it gets bigger and bigger.<\/li>\n<li>We will\u00a0be able to locate or map elements with their labels.<\/li>\n<li>We can include objects for custom plugins on the page without complicating our page object classes. And create encapsulated methods for them.<\/li>\n<\/ol>\n<p>List of things we need to do:<\/p>\n<ol>\n<li>Custom Class implementing ElementLocatorFactory<\/li>\n<li>Custom Class extending AbstractAnnotations<\/li>\n<li>Custom Class implementing FieldDecorator<\/li>\n<li>A Class implementing InvocationHandler; for our custom object<\/li>\n<li>A public annotation interface<\/li>\n<\/ol>\n<p><em>We will be using Java's string formatting in locating our elements.<\/em><br \/>\nIn some other classes, you might just want to extend the default ones Selenium provided.<\/p>\n<h4>1. Creating <code>Custom Class implementing ElementLocatorFactory<\/code><\/h4>\n<p>The <code>ElementLocatorFactory<\/code> interface only implements one method.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\npublic interface ElementLocatorFactory {\r\n ElementLocator createLocator(Field field);\r\n}\r\n<\/pre>\n<p>This interface will be the one to give the <code>PageFactory<\/code> a custom locator, the <code>findElement<\/code> and <code>findElements<\/code>. This is needed because we are mapping our page objects with an <code>@Annotation<\/code>.<\/p>\n<p>We will use our own annotation interface to map our page objects, and use this annotation to identify if the page object needs to be located by using our custom <code>ElementLocator<\/code>, else give it to the default handlers of Selenium.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\npackage com.skyarch.PageObjects;\r\n\r\nimport java.lang.reflect.Field;\r\n\r\nimport org.openqa.selenium.SearchContext;\r\nimport org.openqa.selenium.support.pagefactory.Annotations;\r\nimport org.openqa.selenium.support.pagefactory.ElementLocator;\r\nimport org.openqa.selenium.support.pagefactory.ElementLocatorFactory;\r\nimport org.openqa.selenium.support.pagefactory.DefaultElementLocator;\r\n\r\nimport com.skyarch.PageObjects.Annotations.FindBy;\r\n\r\npublic class CustomElementLocatorFactory implements ElementLocatorFactory {\r\n    \r\n    private final SearchContext searchContext;\r\n    \r\n    public CustomElementLocatorFactory(SearchContext context) {\r\n        this.searchContext = context;\r\n    }\r\n    \r\n    @Override\r\n    public ElementLocator createLocator(Field field) {\r\n        FindBy annotation = field.getAnnotation(FindBy.class);\r\n        \r\n        if (annotation == null) {\r\n            \/\/ if our annotation is not present give it to selenium's defaults\r\n            return new DefaultElementLocator(this.searchContext, new Annotations(field));\r\n        }\r\n        return new CustomElementLocator(this.searchContext, new CustomAnnotation(field));\r\n    }\r\n}\r\n<\/pre>\n<p>With this, we can redirect all our page objects annotated with our custom annotation to our custom <code>ElementLocator<\/code>.<\/p>\n<h4>2. Now let's create our <code>Custom Class extending AbstractAnnotations<\/code><\/h4>\n<p>This class is the one responsible in building the <code>XPath<\/code> and return a <code>By<\/code> that our <code>CustomElementLocator<\/code> will use to locate the page object.<\/p>\n<p>For this step, we will just extend from the default <code>Annotations<\/code> Class Selenium provided as we only need to customize the XPath builder of the class.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\npackage com.skyarch.PageObjects;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.Arrays;\r\n\r\nimport org.openqa.selenium.By;\r\nimport org.openqa.selenium.support.pagefactory.Annotations;\r\n\r\nimport com.skyarch.PageObjects.Annotations.FindBy;\r\n\r\npublic class CustomAnnotation extends Annotations {\r\n    private Field field;\r\n    \r\n    public CustomAnnotation(Field field, String xpathFormat, String xpathPrefix) {\r\n        super(field);\r\n        this.field = field;\r\n    }\r\n    \r\n    @Override\r\n    public By buildBy() {\r\n        By locateBy = null;\r\n        FindBy findBy = field.getAnnotation(FindBy.class);\r\n\r\n        if (findBy == null) {\r\n            locateBy = super.buildByFromDefault(); \r\n            return locateBy;\r\n        }\r\n\r\n        String xpath;\r\n        \/\/ lets check if there are multiple parameters\r\n        if (findBy.params().length &amp;amp;amp;gt; 0) {\r\n            Object&#x5B;] objs = Arrays.copyOf(findBy.params(), findBy.params().length, Object&#x5B;].class);\r\n            xpath = String.format(findBy.format(), objs);\r\n        }\r\n        else { \r\n            xpath = String.format(findBy.format(), xpathPrefix, findBy.param());    \r\n        }\r\n        locateBy = By.xpath(xpath);\r\n\r\n        if (locateBy == null) {\r\n            throw new IllegalArgumentException(&quot;Cannot determine how to locate element &quot; + field);\r\n        }\r\n        return locateBy;\r\n    }\r\n}\r\n\r\n<\/pre>\n<h4>3. <code>Custom Class implementing FieldDecorator<\/code><\/h4>\n<p>Now this class is the most important, this is the one that casts the page objects into the Interface (as a Proxy class) we want them to be.<br \/>\nThis will be called by <code>PageFactory<\/code> for every single object in our page object class that is being initialized.<br \/>\nIt first gets the <code>ElementLocator<\/code> given by the <code>ElementLocatorFactory<\/code>. Then casts the page object field into a <code>Proxy<\/code> instance of, in Selenium's default a <code>WebElement<\/code>.<\/p>\n<p>In this step, we can add our custom page objects (select, etc.). Yehey!<br \/>\nWe will also be extending the <code>DefaultFieldDecorator<\/code>.<br \/>\n<em>Note: Example custom page objects are in the <a href=\"#side_quest\">Side Quest<\/a> section.<\/em><\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\npackage com.skyarch.PageObjects;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.lang.reflect.InvocationHandler;\r\nimport java.lang.reflect.Proxy;\r\nimport java.util.List;\r\n\r\nimport org.openqa.selenium.WebDriver;\r\nimport org.openqa.selenium.WebElement;\r\nimport org.openqa.selenium.support.pagefactory.DefaultFieldDecorator;\r\nimport org.openqa.selenium.support.pagefactory.ElementLocator;\r\nimport org.openqa.selenium.support.pagefactory.ElementLocatorFactory;\r\n\r\nimport com.skyarch.PageObjects.CustomElements.Handlers.SelectElementWrapperHandler;\r\nimport com.skyarch.PageObjects.CustomElements.Interfaces.SelectInput;\r\n\r\npublic class CustomFieldDecorator extends DefaultFieldDecorator {\r\n\r\n    public CustomFieldDecorator(ElementLocatorFactory factory) {\r\n        super(factory);\r\n    }\r\n    \r\n    @Override\r\n    public Object decorate(ClassLoader loader, Field field) {\r\n        \/\/ this part is where we pre-filter the acceptable interfaces for our page objects\r\n        if (!(WebElement.class.isAssignableFrom(field.getType())\r\n            || String.class.isAssignableFrom(field.getType())\r\n            || SelectInput.class.isAssignableFrom(field.getType()) \/\/ this will be our example custom object\r\n            || List.class.isAssignableFrom(field.getType())\r\n            || isDecoratableList(field))) {\r\n          return null;\r\n        }\r\n\r\n        ElementLocator locator = factory.createLocator(field);\r\n        if (locator == null) {\r\n          return null;\r\n        }\r\n        if (List.class.isAssignableFrom(field.getType())) {\r\n            return proxyForListLocator(loader, locator);\r\n        } else if (SelectInput.class.isAssignableFrom(field.getType())) {\r\n            return proxySelectForLocator(loader, locator);\r\n        } else if (WebElement.class.isAssignableFrom(field.getType())) {\r\n            return proxyForLocator(loader, locator);\r\n        } else {\r\n          return null;\r\n        }\r\n      }\r\n\r\n    protected SelectInput proxySelectForLocator(ClassLoader loader, ElementLocator locator) {\r\n        InvocationHandler handler = new SelectElementWrapperHandler(locator);\r\n    \r\n        SelectInput proxy;\r\n        proxy = (SelectInput) Proxy.newProxyInstance(\r\n            loader, new Class&#x5B;]{SelectInput.class}, handler);\r\n        return proxy;\r\n    }\r\n}\r\n<\/pre>\n<p>Note that <code>SelectInput<\/code> here is a custom interface to support the Selenium's <code>Select<\/code> class.<\/p>\n<p>What we need is our Proxy <code>InvocationHanler<\/code> class for <code>SelectInput<\/code> object to finally work.<\/p>\n<h4>4. <code>A Class implementing InvocationHandler<\/code>, Let's create that!<\/h4>\n<p>Before this, please read more about <a href=\"https:\/\/docs.oracle.com\/javase\/7\/docs\/api\/java\/lang\/reflect\/Proxy.html\">Java Proxy Class<\/a>.<\/p>\n<p>This class will be the one that handles our method calls for our proxy page objects. It will call <code>locator.findElement()<\/code> or <code>locator.findElements()<\/code> and then invokes the method to it.<br \/>\nAnd this is where we cast the <code>WebElement<\/code> into whatever object we want it to be. In this case, a <code>CustomSelect<\/code> (<code>Select<\/code>) Object.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\npackage com.skyarch.PageObjects.CustomElements.Handlers;\r\n\r\nimport java.lang.reflect.InvocationHandler;\r\nimport java.lang.reflect.InvocationTargetException;\r\nimport java.lang.reflect.Method;\r\n\r\nimport org.openqa.selenium.WebElement;\r\nimport org.openqa.selenium.support.pagefactory.ElementLocator;\r\n\r\nimport com.skyarch.PageObjects.CustomElements.CustomSelect;\r\n\r\npublic class SelectElementWrapperHandler implements InvocationHandler {\r\n    private final ElementLocator locator;\r\n\r\n    public SelectElementWrapperHandler(ElementLocator locator) {\r\n        this.locator = locator;\r\n    }\r\n\r\n    @Override\r\n    public Object invoke(Object proxy, Method method, Object&#x5B;] args) throws Throwable {\r\n        WebElement element;\r\n        try {\r\n            element = locator.findElement();\r\n        } catch (Exception e) {\r\n            if (&quot;toString&quot;.equals(method.getName())) {\r\n                return &quot;Proxy select(element) for: &quot; + locator.toString();\r\n            }\r\n            else throw e;\r\n        }\r\n        CustomSelect el = new CustomSelect(element);\r\n        \r\n        try {\r\n            return method.invoke(el, args);\r\n        } catch (InvocationTargetException e) {\r\n            throw e.getCause();\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>Note that the <code>CustomSelect<\/code> class is a class extending Selenium's <code>Select<\/code> class and implementing <code>SelectInput<\/code>, we need to do this because we need the class to be implementing our <code>SelectInput<\/code> interface.<\/p>\n<h4>5. Let's not forget our annotation. <code>A public annotation interface<\/code><\/h4>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\npackage com.skyarch.PageObjects.Annotations;\r\n\r\nimport java.lang.annotation.ElementType;\r\nimport java.lang.annotation.Retention;\r\nimport java.lang.annotation.RetentionPolicy;\r\nimport java.lang.annotation.Target;\r\n\r\n@Retention(RetentionPolicy.RUNTIME)\r\n@Target({ElementType.FIELD, ElementType.TYPE})\r\npublic @interface FindBy {\r\n      String format() default &quot;&quot;;\r\n      String param() default &quot;&quot;;\r\n      String&#x5B;] params() default {};\r\n}\r\n<\/pre>\n<p>You can change the class name so you will not be confused with Selenium's <code>@FindBy<\/code> annotaion.<\/p>\n<p>Now we can declare a page object by:<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\n@FindBy(format = &quot;\/\/label&#x5B;text()='%s']\/following-sibling::select&quot;, param = &quot;Select Gender:&quot;)\r\npublic SelectInput drpGender;\r\n<\/pre>\n<p>You can also customize it more, so you will have a declaration that looks something like:<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\n@FindByFormat(format = &quot;\/\/label&#x5B;text()='%s']\/following-sibling::select&quot;) \/\/ this format will apply to all following @FindBy Annotation until a new @FindByFormat\r\n@FindBy(param = &quot;Select Gender:&quot;)\r\npublic SelectInput drpGender;&lt;\/pre&gt;\r\n&lt;pre&gt;@FindBy(param = &quot;Select City:&quot;)\r\npublic SelectInput drpCity;&lt;\/pre&gt;\r\n&lt;pre&gt;@FindBy(param = &quot;Select Town:&quot;)\r\npublic SelectInput drpTown;&lt;\/pre&gt;\r\n&lt;pre&gt;<\/pre>\n<p>You can customize it more to fit your needs. This is only a guide and a proof that you can do it your way.<br \/>\nThank you for reading. Say\u014dnara!<\/p>\n<h3>Side Quest<\/h3>\n<p>I will give some examples on how we can create classes for our custom page objects.<\/p>\n<p><strong id=\"side_quest\"><code>CustomSelect<\/code> Class<\/strong><\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\npackage com.skyarch.PageObjects.CustomElements;\r\n\r\nimport org.openqa.selenium.By;\r\nimport org.openqa.selenium.WebElement;\r\nimport org.openqa.selenium.support.ui.Select;\r\n\r\nimport com.skyarch.PageObjects.CustomElements.Interfaces.SelectInput;\r\n\r\npublic class CustomSelect extends Select implements SelectInput {\r\n    \r\n    public CustomSelect(WebElement element) {\r\n        super(element);\r\n    }\r\n\r\n    \/*\r\n     * Selects multiple text, applies only to multiple select \r\n     *\/\r\n    @Override\r\n    public void selectByVisibleText(String... texts) {\r\n        for (String text : texts) {\r\n            super.selectByVisibleText(text);\r\n        }       \r\n    }\r\n    \/\/ more custom methods here...\r\n}\r\n\r\n<\/pre>\n<p><strong><code>SelectInput<\/code> Interface<\/strong><\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\npackage com.skyarch.PageObjects.CustomElements.Interfaces;\r\n\r\nimport java.util.List;\r\n\r\nimport org.openqa.selenium.WebElement;\r\n\r\npublic interface SelectInput {\r\n    boolean isMultiple();\r\n    List&amp;amp;amp;amp;amp;lt;WebElement&amp;amp;amp;amp;amp;gt; getOptions();\r\n    List&amp;amp;amp;amp;amp;lt;WebElement&amp;amp;amp;amp;amp;gt; getAllSelectedOptions();\r\n    WebElement getFirstSelectedOption();\r\n    void selectByVisibleText(String text);\r\n    void selectByIndex(int index);\r\n    void selectByValue(String value);\r\n    void deselectAll();\r\n    void deselectByValue(String value);\r\n    void deselectByIndex(int index);\r\n    void deselectByVisibleText(String text);\r\n    void selectByVisibleText(String...texts);\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p><a href=\"https:\/\/www.guru99.com\/upload-download-file-selenium-webdriver.html\">Looking for a way to test uploading &amp; downloading files using Selenium? Here is a great post from our friends at Guru99.<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this blog, we\u00a0will be using Selenium WebDriver (Java) for my example codes. And please note that the codes &#8230;<\/p>\n","protected":false},"author":1,"featured_media":7061,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_locale":"en_US","_original_post":"6720","footnotes":""},"categories":[9],"tags":[140,143,139,142,141],"class_list":{"0":"post-6720","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-dev","8":"tag-java","9":"tag-page-object","10":"tag-selenium","11":"tag-testing","12":"tag-webdriver","13":"en-US"},"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.skyarch.net\/blog\/wp-json\/wp\/v2\/posts\/6720","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.skyarch.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.skyarch.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.skyarch.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.skyarch.net\/blog\/wp-json\/wp\/v2\/comments?post=6720"}],"version-history":[{"count":27,"href":"https:\/\/www.skyarch.net\/blog\/wp-json\/wp\/v2\/posts\/6720\/revisions"}],"predecessor-version":[{"id":12977,"href":"https:\/\/www.skyarch.net\/blog\/wp-json\/wp\/v2\/posts\/6720\/revisions\/12977"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.skyarch.net\/blog\/wp-json\/wp\/v2\/media\/7061"}],"wp:attachment":[{"href":"https:\/\/www.skyarch.net\/blog\/wp-json\/wp\/v2\/media?parent=6720"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.skyarch.net\/blog\/wp-json\/wp\/v2\/categories?post=6720"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.skyarch.net\/blog\/wp-json\/wp\/v2\/tags?post=6720"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}