Giter Club home page Giter Club logo

wrappedelements's Introduction

wrappedelements

Status Made in Ukraine Fuck Russia

A convenient framework wrapper for pages and selenium webelements, that reduces usual waiter, and initialization routine. It will help you to get rid of boilerplate code in UI test automation frameworks. For more information see documentation and examples.

Inspired by Burning wave.

Special thanks to Tyrrz for Terms of use, and Sam Rosental for opensource css to xpath converter.

Terms of use

By using this project or its source code, for any purpose and in any shape or form, you grant your implicit agreement to all the following statements:

  • You condemn Russia and its military aggression against Ukraine
  • You recognize that Russia is an occupant that unlawfully invaded a sovereign state
  • You support Ukraine's territorial integrity, including its claims over temporarily occupied territories of Crimea and Donbas
  • You reject false narratives perpetuated by Russian state propaganda Glory to Ukraine!

Features

  • Clear and simple Page object classes
  • Easy element interaction condition definition on Page object level
  • Increased element reusability for different platforms (Web, iOS, Android)
  • Easy element waiters and timeouts configuration on Page object level
  • Dynamic locators support
  • No element synchronization code on test level as result clear code on test level
  • Extendable custom element configuration and customization system
  • Automatic web driver recreation if it's dead or quit
  • Your own custom blocks will be highly reusable

Setup

Add wrappedelements and selenium-java dependency to assembler build file. It's optional to add any additional logger dependency to activate built-in logging.

Maven

<dependency>
    <groupId>io.github.bobfrostman</groupId>
    <artifactId>wrappedelements</artifactId>
    <version>${wrappedelements.version}</version>
</dependency>
<dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>${selenium.version}</version>
</dependency>

Gradle

implementation group: 'io.github.bobfrostman', name: 'wrappedelements', version: '${wrappedelements.version}'
implementation group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '${selenium.version}'

Ivy

<dependency org="io.github.bobfrostman" name="wrappedelements" rev="${wrappedelements.version}"/>
<dependency org="org.seleniumhq.selenium" name="selenium-java" rev="${selenium.version}"/>

Getting started

  1. Add wrappedelements dependencies to your project (see Setup section)
  2. Create your own pageobject entities as interfaces (extend the IPage interface)
public interface LoginPage extends IPage {

    @WebElement(value = "//form//input")
    ClickableElement loginInput();

    @WebElement(value = "(//form//input)[2]")
    ClickableElement passwordInput();

    @WebElement(value = "//input[@type='submit']")
    ClickableElement loginButton();

} 
  1. Register selenium webdriver instance to be used by wrappedelements framework. To do that provide a driver creation function, and init your page with wrappedelements:
    @BeforeClass
    public void setUp() {
        WrappedElements.config()
                .driverCreator(() -> {
                    WebDriverManager.chromedriver().setup();
                    ChromeOptions options = new ChromeOptions();
                    options.setImplicitWaitTimeout(Duration.ofSeconds(10));
                    return new ChromeDriver();
                });
        loginPage = WrappedElements.initPage(LoginPage.class);
    }
  1. Enjoy writing your clean tests without any explicit waits!
public class TestNGSmokeTest {

    private LoginPage loginPage;

    @BeforeClass
    public void setUp() {
        WrappedElements.config()
                .driverCreator(() -> {
                    WebDriverManager.chromedriver().setup();
                    ChromeOptions options = new ChromeOptions();
                    options.setImplicitWaitTimeout(Duration.ofSeconds(10));
                    return new ChromeDriver();
                });

        loginPage = WrappedElements.initPage(LoginPage.class);
    }

    @BeforeMethod
    public void beforeMethod() {
        loginPage.driver().get("https://www.saucedemo.com/");
    }

    @Test
    public void simpleUIInteractionTest() {
        loginPage.loginInput().sendKeys("standard_user");
        loginPage.passwordInput().sendKeys("secret_sauce");
        loginPage.loginButton().click();

        Assert.assertEquals(loginPage.driver().getCurrentUrl(), "https://www.saucedemo.com/inventory.html");
    }

    @AfterMethod
    public void tearDown() {
        if (loginPage.driver() != null) {
            loginPage.driver().quit();
        }
    }
}

Features overview

Clear and simple Page object classes

Imagine page object model description as an interface! That makes page object classes clear and avoids additional code.

import io.github.bobfrostman.annotation.WebElement;
import io.github.bobfrostman.element.clickable.ClickableElement;
import io.github.bobfrostman.page.IPage;

public interface LoginPage extends IPage {

    @WebElement(value = "//form//input")
    ClickableElement loginInput();

    @WebElement(value = "(//form//input)[2]")
    ClickableElement passwordInput();

    @WebElement(value = "//input[@type='submit']")
    ClickableElement loginButton();

}

Question: How can I create instance of my page object interface?

Quite simple:

LoginPage loginPage = WrappedElements.initPage(LoginPage.class);

No waiters on test level that leaves code clean and clear

All element's interaction waiters encapsulated on page object layer. No additional waiters in element interaction code, - enjoy the usage:)

@Test
public void simpleUIInteractionTest() {
    loginPage.loginInput().sendKeys("standard_user");
    loginPage.passwordInput().sendKeys("secret_sauce");
    loginPage.loginButton().click();
    ...
}

By default wrapped elements will wait for all element become clickable before any active interactions (click, sendKeys, etc). It is possible because of 'Interactor' mechanism, that allows you to configure waits for elements directly on page object layer.

Question: How can I set some different waiter before elements interaction?

Feel free to use 'waitUntil' value of @WebElement annotation.

For instance: You need to wait for element to become just visible (and may not be clickable) on some ui element called 'heisenberg'.

@WebElement(value = "//div[@class='heisenberg']", waitUntil = UNTIL_VISIBLE, timeout = 15)
ClickableElement heisenberg();

Any time you perform any kind of physical interaction (click, sendKeys, etc.), on heisenberg() element, it will wait for heisenberg() to become visible first, with 15 seconds timeout.

Available values are next:

  • IMMEDIATELY (no waiters applied)
  • UNTIL_VISIBLE (waits for element to be displayed before physical interaction)
  • UNTIL_CLICKABLE (waits for element to be displayed and be enabled)
  • VERTICAL_SCROLL_UNTIL_VISIBLE (scrolls down until the element will be visible in the view port)

Question: What if I want to set some custom waiter for all elements by default?

To do that:

  1. Implement your own Interactor class by implementing IElementInteractor interface and register it in WrappedElements framework:
import io.github.bobfrostman.wrapper.interactor.IElementInteractor;

public class WaitUntilMyCustomConditionsMet implements IElementInteractor {

    @Override
    public String name() {
        return "custom_wait";
    }

    Override

    public boolean isReadyForInteraction(String methodName, By by, WebDriver webDriver) {
        //my interaction logic that says if method name is "click" or "sendKeys" or anything else, then wait for my custom conditions
    }
}
  1. Register you interactor in WrappedElements class before your tests run with code:
    WrappedElements.config().defaultElementInteractor(new WaitUntilMyCustomConditionsMet());

That's it. Your cutom waiter will be applied by default for all elements that doesn't have 'waitUntil' value specified explicitly.

Question: What if I want to set some custom waiter for element?

You can create you own waiter and use it with wrappedelements. It is possible by implementing your IElementInteractor interface. Interactor - is the entity that describes the behavior with web element.

To do that:

  1. Implement your own Interactor class by implementing IElementInteractor interface and register it in WrappedElements framework:
import io.github.bobfrostman.wrapper.interactor.IElementInteractor;

public class WaitUntilMyCustomConditionsMet implements IElementInteractor {

    @Override
    public String name() {
        return "custom_wait";
    }

    Override

    public boolean isReadyForInteraction(String methodName, By by, WebDriver webDriver) {
        //my interaction logic that says if method name is "click" or "sendKeys" or anything else, then wait for my custom conditions
    }
}
  1. Register you interactor in WrappedElements class before your tests run with code:
    WrappedElements.config().registerInteractor(new WaitUntilMyCustomConditionsMet());
  1. Now you can specify it in page object class using @WebElement 'waitUntil' value.
    @WebElement(value = "//div[@class='heisenberg']", waitUntil = "custom_wait")
    ClickableElement heisenberg();

Easy element's locator definition for different platforms

It's quite common case - run ui mobile tests on different platforms. Enjoy running same tests with different locators for different platforms without changing test logic:

Page example:

    @WebElement(value = "linkText=someLinkText", name = "Web element")
    @AndroidElement(value = "//span/a", name = "Android element")
    @IOSElement(value = "just_some_ios_element_id", name = "IOS element")
    ClickableElement multiplatformElement();

To run tests on android just invoke:

    WrappedElements.config().setPlatform(IKnowPlatforms.ANDROID);

And element locators will be picked up from @AndroidElement annotation. Specify IOS platform, and framework will run tests with locators in @IOSElement

Dynamic locators support

WrappedElements supports dynamical element locators, we know that sometimes you need some specific element that may be found only dynamically.

Declaration example:

    @WebElement(value = "//div[text() = '%s']|//span[contains(text(), '%s')]")
    ClickableElement elementWithText(String text);
    
    @WebElement(value = "//div[text() = '%d']|//span[contains(text(), '%d')]")
    ClickableElement elementWithDigits(int number);
    
    @WebElement(value = "//li/a[contains(text(), '${first_name} ${ last_name }')]")
    ClickableElement elementWithNamedParameter(@Parameter("last_name") String lastName, @Parameter("first_name") String firstName);

Usage example:

String name = page.elementWithDigits(0).getText();
String lastName = page.elementWithDigits(1).getText();
String someOtherDynamicalText = page.elementWithNamedParameter(lastName, name).getText();
elementWithText(someOtherDynamicalText).click();

Components design

It's quite common case when you have some component on the page that duplicated on different page, and may contain elements that duplicates from page to page. For instance: in your header you have a basket button that contains from basket element button and products count label.

To create BasketButton component you need:

  1. Extend WrappedComponent interface:
public interface BasketButton extends WrappedComponent {

    @WebElement(value = ".//span[@class=\"shopping_cart_badge\"]", waitUntil = IMMEDIATELY)
    ClickableElement notificationsCountLabel();

    @WebElement(value = ".//a[@class=\"shopping_cart_link\"]", waitUntil = VERTICAL_SCROLL_UNTIL_VISIBLE)
    ClickableElement basketButton();

}
  1. Specify locator for your basket button on the page use @WebComponent annotation
public interface Header extends WrappedComponent {

    @WebComponent("//div[@class='basket_button_locator']")
    BasketButton basket();
    
}

In such way your Wrappedelelements will search basketButton() and notificationsCountLabel() by relative locator from from BasketButton component.

Such approach allows you to reuse component with different locators on different pages. WrappedComponents also supports @IOSComponent and @AndroidComponent annotations, so you can define same component with different locators for different platforms

List elements support

Common case when you need to find a list of components or elements on the page, Wrappedelements also supports such functionality

import io.github.bobfrostman.wrapper.element.impl.ClickableElement;

public interface InventoryPage extends IPage {
    @WebComponent("//*[@class='inventory_item']")
    List<InventoryItem> inventoryItems();

    @WebElement(".inventory_item")
    List<ClickableElement> inventoryItems();
}

Note that it's recommended to use xpath locator for list of components.

Built-in elements interaction logger

Wrappedelements as a build-in slf4-api logging interface, so you can see elements interaction's logs by adding different logging libraries to your dependencies: For instance to use logback logging just add next dependencies to your pom.xml

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>${your_logback_version}</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>${your_logback_version}</version>
</dependency>

Feedback

I'm glad to hear any feedback from you via Facebook or Telegram (@foggger)

wrappedelements's People

Contributors

bobfrostman avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar

wrappedelements's Issues

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.