https://github.com/qameta/atlas
https://github.com/qameta/atlas
Last synced: about 2 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/qameta/atlas
- Owner: qameta
- Created: 2018-05-07T05:34:03.000Z (almost 7 years ago)
- Default Branch: main
- Last Pushed: 2023-05-31T19:16:33.000Z (almost 2 years ago)
- Last Synced: 2023-11-07T15:35:12.797Z (over 1 year ago)
- Language: Java
- Size: 64 MB
- Stars: 109
- Watchers: 16
- Forks: 27
- Open Issues: 37
-
Metadata Files:
- Readme: README.adoc
Awesome Lists containing this project
- awesome-java - Atlas
README
NOTE: This is a rebranding of the https://github.com/eroshenkoam/htmlelements[html elements]
project.= Atlas
Next generation of the HtmlElements library is indeed a yet another WebDriver wrapper, but
with a couple of fresh ideas:. Hierarchy of page objects for your application is now a very flexible composition of java interfaces.
. A lot of additional control over elements behaviour is available via extension methods implemented in a fluent manner.
. Actually, pretty much everything is extensible for your convenience: the library provides a baseline design
for your project, allowing you to fine-tune the specifics.== Overview
Atlas has several basic entities that you will be working with:
*WebSite* - instances of this class will serve as top classes of your page objects hierarchy.
*WebPage* - instances of this class will contain a block of web elements.
*Atlas* - returns WebSite instances that are wrapped around the given driver.
[source, java]
----
Atlas atlas = new Atlas();
BaseSite site = atlas.create(driver, BaseSite.class);
----*AtlasWebElement* - is a parent interface for every web element you will be using in your tests.
*ElementsCollection* - an interface, which provides everything to work with a collection of AtlasWebElements.
== Structuring Page Objects
[cols="2",frame="none"]
|=========================================================a|
1) Defining a site: site is top-interface and it contains pages your web application[source, java]
----
public interface BaseSite extends WebSite {@Page
MainPage mainPage();@Page("/search")
SearchPage searchPage();@Page("/account")
AccountPage accountPage();
}
----a|
2) Defining a page: pages may contain elements or whole element blocks.
[source, java]
----
public interface SearchPage extends WebPage {@FindBy("//form[contains(@class, 'search2 suggest2-form')]")
SearchArrow searchArrow();@FindBy("//div[@class='toolbar']")
Toolbar toolbar();@FindBy("//div[@class='user-tooltip']")
UserTooltip userTooltip();
}
----
a|
Web pages usually contain some high-level blocks, that we will neatly organize via a composition
[source, html]
----
...
...----
a|
3) Organizing a block of web elements on the page: you need to extend an interface from AtlasWebElement
and you are good to go
[source, java]
----
public interface Toolbar extends AtlasWebElement {@FindBy(".//span[@class = 'status-text']")
AtlasWebElement statusText();
}
----
a|
Your classes for such blocks of elements can contain sub-blocks, or once you've reached a singular element,
use the AtlasWebElement as it's type.
[source, html]
----
...
...----
a|
4) You can define a collection of elements for a page or for a block
[source, java]
----
public interface Toolbar extends AtlasWebElement {@FindBy(".//button[@class = 'action-button']")
ElementsCollection buttons();@FindBy(".//span[@class = 'status-text']")
AtlasWebElement statusText();
}
----
a|
Button elements have a specific ToolbarButton class to represent them.
[source, html]
----
...
...----
a|
5) You can use parameterized selectors to simplify your work with homogeneous elements
[source, java]
----
public interface SearchResultsPanel extends AtlasWebElement {@Description("Search result for user {{ userName }}")
@FindBy(".//div[contains(@class, 'search-result-item') and contains(., {{ userName }}]")
UserResult user(@Param("userName") String userName);
}
----Invocation of parameterized method:
[source, java]
----
UserResult foundUser = searchPage.resultsPanel().user("User 1");
----
a|
Here parameterized element will match a block of elements for User 1 from search results. Parameters can be used with
collections of elements as well.
[source, html]
----
...
User 1
...
Task 1
...
...----
a|
6) Very often you will have some identical html blocks across different pages, e.g. toolbars, footers, dropdowns,
pickers e.t.c. Once you wrote a class describing such a block, it can be easily reused across all of the pages:
[source, java]
----public interface SearchPage extends WebPage, WithToolbar {
//other elements for search page here
}public interface AccountPage extends WebPage, WithToolbar {
//other elements for account page here
}public interface WithToolbar extends AtlasWebElement {
@FindBy("//div[@class='toolbar']")
Toolbar toolbar();
}
----
a|
First page - base.url/search:
[source, html]
----
...----
Second page - base.url/account:
[source, html]
----
...----
both pages have a toolbar block, represented by the Toolbar class.
|=========================================================== Features
After you have described web pages, it's time to start organizing the logic of working with them.
We prefer to use a BDD approach, where you break down all the interaction logic into simple actions and create
one "step" method per action as it's implementation. That is where all the extension methods from AtlasWebElement
and ElementsCollection classes come in handy.We will use http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matchers.html[harmcrest matchers] for conditioning
=== Working with elements
*Waiting on a condition for a single element.*
`AtlasWebElement` have two different extension methods for waiting.First - `waitUntil(Matcher matcher)` which waits with a configurable timeout and polling on some condition
in a passed matcher, throwing `java.lang.RuntimeException` if condition has not been satisfied.
[source, java]
----
@Step("Make search with input string «{input}»")
public SearchPageSteps makeSearch(String input){
final SearchForm form = onSearchPage().searchPanel().form();
form.waitUntil(Matchers.is(WebElement::isDisplayed)) //waits for element to satisfy a condition
.sendKeys(input); //invokes standard WebElement's method
form.submit();
return this;
}
----Second - `should(Matcher matcher)` which waits the same way on a passed matcher, but throwing
`java.lang.AssertionError` instead.
[source, java]
----
@Step("Check user «{userName}» is found")
public SearchPageSteps checkUserIsFound(String userName){
onSearchPage().resultsPanel().user(userName)
.should("User is not found", Matchers.is(WebElement::isDisplayed));
//makes a waiting assertion here
return this;
}
----=== Working with collections
Collections of elements are meant to be indiscrete objects. Working with individual elements of a collection
should generally be considered an anti-pattern, because elements behind the collection will not be refreshed
on the subsequent calls, and their usage may lead to the `StaleElementReferenceException`.*Waiting and verifying collection state.* `ElementsCollection` has the same `waitUntil()` and `should()` methods as were
described above. Overall logic of their usage should be roughly the same, but with a little difference introduced by
abilities to filter via `filter()` and to perform a mapping transformation via `extract()` methods.
[source, java]
----
@Step("Check that search results contain exactly «{expectedItems}»")
public SearchPageSteps checkSearchResults(List expectedItems){
onSearchPage().resultsForm().usersFound()
.waitUntil(not(empty()) //waiting for elements to load
.extract(user -> user.name().getText()) //extract AtlasWebElement to String
.should(containsInAnyOrder(expectedItems.toArray())); //assertion for a collection
}
----*Filtering*
[source, java]
----
@Step("Check active users contain exactly «{expectedUsers}»")
public SearchPageSteps checkActiveUsersFound(List expectedUsers){
onSearchPage().resultsForm().usersFound()
.waitUntil(not(empty()) //waiting for elements to load
.filter(user -> user.getAttribute("class").contains("selected"))
.extract(user -> user.name().getText()) //extract AtlasWebElement to String
.should(containsInAnyOrder(expectedItems.toArray())); //assertion for a collection
}
----Don't do this! Use a parameterized selector instead.
[source, java]
----
@Step("Select filter checkbox «{name}»")
public SearchPageSteps selectFilterCheckbox(String name){
onSearchPage().searchPanel().filtersTab().checkboxes()
.waitUntil(not(empty()))
.filter(chkbox -> chkbox.getText().contains(name))
.get(0).click(); //don't do this
}
----=== Working with pages
There are different ways to set `BASE_URL` for page1) Forward parameter `url` in `open` Webpage method
[source, java]
----
@Test
public void searchTest() {
onSearchPage().open("http://baseurl.com/search");
}
----2) Define a base url for site in configuration
[source, java]
----
@BeforeTest
public void init() {
Atlas atlas = new Atlas(WebDriverConfiguration(driver, "base.url"));
}
----Then if you annotate a `WebPage` with `@Page` you can specify an url to be opened
when `WebPage` 's `open()` method is called.[source, java]
----public interface BaseSite extends WebSite {
@Page("/search")
SearchPage searchPage();
//elements for search page here
}
----Then after instantiation you can call `open()` method like this:
[source, java]
----
Atlas atlas = new Atlas(WebDriverConfiguration(driver, "http://base.url"));
BaseSite site = atlas.create(driver, BaseSite.class);
site.searchPage().open(); //go to http://base.url/search
----