diff --git a/doc/style/eclipse-java-style.xml b/doc/style/eclipse-java-style.xml deleted file mode 100644 index d5b2d0eb..00000000 --- a/doc/style/eclipse-java-style.xml +++ /dev/null @@ -1,832 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/doc/style/intellij-java-style.xml b/doc/style/intellij-java-style.xml old mode 100644 new mode 100755 diff --git a/doc/style/style.xml b/doc/style/style.xml old mode 100644 new mode 100755 index 53c47993..d29bd4c5 --- a/doc/style/style.xml +++ b/doc/style/style.xml @@ -199,7 +199,6 @@ - org.seleniumhq.selenium selenium-java - 3.141.59 - - - - ru.yandex.qatools.htmlelements - htmlelements-java - 1.20.0 + 4.0.0-beta-1 diff --git a/src/main/java/com/frameworkium/core/htmlelements/annotations/Timeout.java b/src/main/java/com/frameworkium/core/htmlelements/annotations/Timeout.java new file mode 100755 index 00000000..7826a269 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/annotations/Timeout.java @@ -0,0 +1,27 @@ +package com.frameworkium.core.htmlelements.annotations; + +import java.lang.annotation.*; + +/** + * Annotation that is used for setting waiting timeout value, + * which will be used for waiting an element to appear. + *

+ * For example: + *

+ *

+ * @FindBy(css = "my_form_css")
+ * @Timeout(3)
+ * public class MyForm extends HtmlElement {
+ * @FindBy(css = "text_input_css")
+ * @Timeout(3)
+ * private TextInput textInput;
+ * 

+ * // Other elements and methods here + * } + *

+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.FIELD}) +public @interface Timeout { + int value(); +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/element/Button.java b/src/main/java/com/frameworkium/core/htmlelements/element/Button.java new file mode 100755 index 00000000..16e353d7 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/element/Button.java @@ -0,0 +1,16 @@ +package com.frameworkium.core.htmlelements.element; + +import org.openqa.selenium.WebElement; + +/** Represents web page button control. */ +public class Button extends TypifiedElement { + + /** + * Specifies wrapped {@link WebElement}. + * + * @param wrappedElement {@code WebElement} to wrap. + */ + public Button(WebElement wrappedElement) { + super(wrappedElement); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/element/CheckBox.java b/src/main/java/com/frameworkium/core/htmlelements/element/CheckBox.java new file mode 100755 index 00000000..7de94dd1 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/element/CheckBox.java @@ -0,0 +1,72 @@ +package com.frameworkium.core.htmlelements.element; + +import org.openqa.selenium.*; + +import java.util.Optional; + +/** Represents checkbox control. */ +public class CheckBox extends TypifiedElement { + + /** + * Specifies wrapped {@link WebElement}. + * + * @param wrappedElement {@code WebElement} to wrap. + */ + public CheckBox(WebElement wrappedElement) { + super(wrappedElement); + } + + /** + * Finds label corresponding to this checkbox using "following-sibling::label" xpath. + * + * @return Optional of the {@code WebElement} representing label + */ + public Optional getLabel() { + try { + return Optional.of(getWrappedElement().findElement(By.xpath("following-sibling::label"))); + } catch (NoSuchElementException e) { + return Optional.empty(); + } + } + + /** + * Finds the text of the checkbox label. + * + * @return Optional of the label text + */ + public Optional getLabelText() { + return getLabel().map(WebElement::getText); + } + + /** + * The same as {@link #getLabelText()}. + * + * @return Text of the checkbox label or {@code null} if no label has been found. + */ + public String getText() { + return getLabelText().orElse(""); + } + + /** Selects checkbox if it is not already selected. */ + public void select() { + if (!isSelected()) { + getWrappedElement().click(); + } + } + + /** Deselects checkbox if it is not already deselected. */ + public void deselect() { + if (isSelected()) { + getWrappedElement().click(); + } + } + + /** Selects checkbox if passed value is {@code true} and deselects otherwise. */ + public void set(boolean value) { + if (value) { + select(); + } else { + deselect(); + } + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/element/FileInput.java b/src/main/java/com/frameworkium/core/htmlelements/element/FileInput.java new file mode 100755 index 00000000..9135d873 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/element/FileInput.java @@ -0,0 +1,101 @@ +package com.frameworkium.core.htmlelements.element; + +import com.frameworkium.core.common.properties.Property; +import com.frameworkium.core.ui.UITestLifecycle; +import org.openqa.selenium.*; +import org.openqa.selenium.remote.*; +import org.openqa.selenium.support.events.EventFiringWebDriver; + +import java.io.File; +import java.util.List; +import java.util.stream.Collectors; + +import static com.frameworkium.core.htmlelements.utils.HtmlElementUtils.*; + +/** Represents web page file upload element. */ +public class FileInput extends TypifiedElement { + + /** + * Specifies wrapped {@link WebElement}. + * + * @param wrappedElement {@code WebElement} to wrap. + */ + public FileInput(WebElement wrappedElement) { + super(wrappedElement); + } + + /** + * Sets a file to be uploaded. + *

+ * File is searched in the following way: if a resource with a specified name exists in classpath, + * then this resource will be used, otherwise file will be searched on file system. + * + * @param fileName Name of a file or a resource to be uploaded. + */ + public void setFileToUpload(final String fileName) { + // Set local file detector in case of remote driver usage + setLocalFileDetectorIfRequired(); + + String filePath = getFilePath(fileName); + sendKeys(filePath); + } + + /** + * Sets multiple files to be uploaded. + *

+ * Files are searched in the following way: + * if a resource with a specified name exists in classpath, + * then this resource will be used, otherwise file will be searched on file system. + * + * @param fileNames a list of file Names to be uploaded. + */ + public void setFilesToUpload(List fileNames) { + // Set local file detector in case of remote driver usage + setLocalFileDetectorIfRequired(); + + String filePaths = fileNames.stream() + .map(this::getFilePath) + .collect(Collectors.joining("\n")); + sendKeys(filePaths); + } + + private void setLocalFileDetectorIfRequired() { + if (Property.GRID_URL.isSpecified()) { + WebDriver webDriver = UITestLifecycle.get().getWebDriver(); + EventFiringWebDriver efDriver = (EventFiringWebDriver) webDriver; + RemoteWebDriver remoteDriver = (RemoteWebDriver) efDriver.getWrappedDriver(); + remoteDriver.setFileDetector(new LocalFileDetector()); + } + } + + /** + * Submits selected file by simply submitting the whole form, which contains this file input. + */ + public void submit() { + getWrappedElement().submit(); + } + + private WebElement getNotProxiedInputElement() { + return getWrappedElement().findElement(By.xpath(".")); + } + + private void setLocalFileDetector(RemoteWebElement element) { + element.setFileDetector(new LocalFileDetector()); + } + + private String getFilePath(final String fileName) { + if (existsInClasspath(fileName)) { + return getPathForResource(fileName); + } + return getPathForSystemFile(fileName); + } + + private String getPathForResource(final String fileName) { + return getResourceFromClasspath(fileName).getPath(); + } + + private String getPathForSystemFile(final String fileName) { + File file = new File(fileName); + return file.getPath(); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/element/Form.java b/src/main/java/com/frameworkium/core/htmlelements/element/Form.java new file mode 100755 index 00000000..75318a08 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/element/Form.java @@ -0,0 +1,115 @@ +package com.frameworkium.core.htmlelements.element; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import java.util.*; + +import static java.util.Objects.isNull; + +/** + * Represents web page form tag. + * Provides handy way of filling form with data and submitting it. + */ +public class Form extends TypifiedElement { + + private static final String CHECKBOX_FIELD = "checkbox"; + private static final String RADIO_FIELD = "radio"; + private static final String SELECT_FIELD = "select"; + private static final String INPUT_FIELD = "input"; + private static final String FILE_FIELD = "file"; + + /** + * Specifies {@link WebElement} representing form tag. + * + * @param wrappedElement {@code WebElement} to wrap. + */ + public Form(WebElement wrappedElement) { + super(wrappedElement); + } + + /** + * Fills form with data contained in passed map. + * For each map entry if an input with a name coincident with entry key exists + * it is filled with string representation of entry value. + * If an input with such a name is not found the corresponding entry is skipped. + * + * @param data Map containing data to fill form inputs with. + */ + public void fill(Map data) { + data.entrySet().stream() + .map(e -> new AbstractMap.SimpleEntry<>( + findElementByKey(e.getKey()), + Objects.toString(e.getValue(), ""))) + .filter(e -> !isNull(e.getKey())) + .forEach(e -> fillElement(e.getKey(), e.getValue())); + } + + protected WebElement findElementByKey(String key) { + List elements = getWrappedElement().findElements(By.name(key)); + if (elements.isEmpty()) { + return null; + } else { + return elements.get(0); + } + } + + protected void fillElement(WebElement element, String value) { + String elementType = getElementType(element); + + if (CHECKBOX_FIELD.equals(elementType)) { + fillCheckBox(element, value); + } else if (RADIO_FIELD.equals(elementType)) { + fillRadio(element, value); + } else if (INPUT_FIELD.equals(elementType)) { + fillInput(element, value); + } else if (SELECT_FIELD.equals(elementType)) { + fillSelect(element, value); + } else if (FILE_FIELD.equals(elementType)) { + fillFile(element, value); + } + } + + protected String getElementType(WebElement element) { + String tagName = element.getTagName(); + if ("input".equals(tagName)) { + String type = element.getAttribute("type"); + if ("checkbox".equals(type)) { + return CHECKBOX_FIELD; + } else if ("radio".equals(type)) { + return RADIO_FIELD; + } else if ("file".equals(type)) { + return FILE_FIELD; + } else { + return INPUT_FIELD; + } + } else if ("select".equals(tagName)) { + return SELECT_FIELD; + } else if ("textarea".equals(tagName)) { + return INPUT_FIELD; + } else { + return null; + } + } + + protected void fillCheckBox(WebElement element, String value) { + new CheckBox(element).set(Boolean.parseBoolean(value)); + } + + protected void fillRadio(WebElement element, String value) { + new Radio(element).selectByValue(value); + } + + protected void fillInput(WebElement element, String value) { + TextInput input = new TextInput(element); + input.sendKeys(input.getClearCharSequence() + value); + } + + protected void fillSelect(WebElement element, String value) { + new Select(element).selectByValue(value); + } + + protected void fillFile(WebElement element, String value) { + new FileInput(element).setFileToUpload(value); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/element/HtmlElement.java b/src/main/java/com/frameworkium/core/htmlelements/element/HtmlElement.java new file mode 100755 index 00000000..2b2da1e4 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/element/HtmlElement.java @@ -0,0 +1,165 @@ +package com.frameworkium.core.htmlelements.element; + +import org.openqa.selenium.*; + +import java.util.List; + +/** + * The base class to be used for making blocks of elements. + *

+ * To make a class that will represent a block of elements (e.g. web form) + * create a descendant of this class and fill it with elements. + *

+ * For example: + *

+ *

+ * @FindBy(css = "form_css")
+ * public class SearchForm extends HtmlElement {
+ *
+ *   @FindBy(css = "request_input_css")
+ *   private TextInput requestInput;
+ * 

+ * @FindBy(css = "search_button_css") + * private Button searchButton; + *

+ * public TextInput getRequestInput() { + * return requestInput; + * } + *

+ * public Button getSearchButton() { + * return searchButton; + * } + * } + *

+ *

+ * Then you can use created blocks as fields of page objects or you can also + * initialize them directly with methods of + * {@link com.frameworkium.core.htmlelements.loader.HtmlElementLoader} class. + *

+ * Note that this class implements {@link WebElement} interface so you can + * substitute instances of your block classes for + * {@code WebElements} where necessary. + */ +public class HtmlElement implements WebElement, WrapsElement { + private WebElement wrappedElement; + + @Override + public WebElement getWrappedElement() { + return wrappedElement; + } + + /** + * Sets the wrapped {@code WebElement}. This method is used by + * initialization mechanism and is not intended to be used directly. + * + * @param wrappedElement {@code WebElement} to wrap. + */ + public void setWrappedElement(WebElement wrappedElement) { + this.wrappedElement = wrappedElement; + } + + /** + * Determines whether or not this element exists on page. + * + * @return True if the element exists on page, false otherwise. + */ + public boolean exists() { + try { + getWrappedElement().isDisplayed(); + } catch (NoSuchElementException ignored) { + return false; + } + return true; + } + + @Override + public void click() { + wrappedElement.click(); + } + + /** + * If this element is a form, or an element within a form, then this will + * submit this form to the remote server. + * + * @see WebElement#submit() + */ + @Override + public void submit() { + wrappedElement.submit(); + } + + @Override + public void sendKeys(CharSequence... charSequences) { + wrappedElement.sendKeys(charSequences); + } + + @Override + public void clear() { + wrappedElement.clear(); + } + + @Override + public String getTagName() { + return wrappedElement.getTagName(); + } + + @Override + public String getAttribute(String name) { + return wrappedElement.getAttribute(name); + } + + @Override + public boolean isSelected() { + return wrappedElement.isSelected(); + } + + @Override + public boolean isEnabled() { + return wrappedElement.isEnabled(); + } + + @Override + public String getText() { + return wrappedElement.getText(); + } + + @Override + public List findElements(By by) { + return wrappedElement.findElements(by); + } + + @Override + public WebElement findElement(By by) { + return wrappedElement.findElement(by); + } + + @Override + public boolean isDisplayed() { + return wrappedElement.isDisplayed(); + } + + @Override + public Point getLocation() { + return wrappedElement.getLocation(); + } + + @Override + public Dimension getSize() { + return wrappedElement.getSize(); + } + + @Override + public Rectangle getRect() { + return wrappedElement.getRect(); + } + + @Override + public String getCssValue(String name) { + return wrappedElement.getCssValue(name); + } + + @Override + public X getScreenshotAs(OutputType outputType) throws WebDriverException { + return wrappedElement.getScreenshotAs(outputType); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/element/Image.java b/src/main/java/com/frameworkium/core/htmlelements/element/Image.java new file mode 100755 index 00000000..bfd64010 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/element/Image.java @@ -0,0 +1,30 @@ +package com.frameworkium.core.htmlelements.element; + +import org.openqa.selenium.WebElement; + +/** Represents an image {@code } */ +public class Image extends TypifiedElement { + + public Image(WebElement wrappedElement) { + super(wrappedElement); + } + + /** + * Retrieves path to image from "src" attribute + * + * @return Path to the image + */ + public String getSource() { + return getWrappedElement().getAttribute("src"); + } + + /** + * Retrieves alternative text from "alt" attribute + * + * @return alternative text for image + */ + public String getAlt() { + return getWrappedElement().getAttribute("alt"); + } + +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/element/Link.java b/src/main/java/com/frameworkium/core/htmlelements/element/Link.java new file mode 100755 index 00000000..9147f247 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/element/Link.java @@ -0,0 +1,20 @@ +package com.frameworkium.core.htmlelements.element; + +import org.openqa.selenium.WebElement; + +/** Represents an anchor tag/hyperlink. */ +public class Link extends TypifiedElement { + + public Link(WebElement wrappedElement) { + super(wrappedElement); + } + + /** + * Retrieves reference from "href" attribute. + * + * @return Reference associated with hyperlink. + */ + public String getReference() { + return getWrappedElement().getAttribute("href"); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/element/Radio.java b/src/main/java/com/frameworkium/core/htmlelements/element/Radio.java new file mode 100755 index 00000000..e96781de --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/element/Radio.java @@ -0,0 +1,97 @@ +package com.frameworkium.core.htmlelements.element; + +import org.openqa.selenium.*; + +import java.util.List; +import java.util.Optional; + +/** Represents a group of radio buttons. */ +public class Radio extends TypifiedElement { + + /** + * Specifies a radio button of a radio button group that will be used + * to find all other buttons of this group. + * + * @param wrappedElement {@code WebElement} representing radio button. + */ + public Radio(WebElement wrappedElement) { + super(wrappedElement); + } + + /** + * Returns all radio buttons belonging to this group. + * + * @return A list of {@code WebElements} representing radio buttons. + */ + public List getButtons() { + String radioName = getWrappedElement().getAttribute("name"); + + String xpath; + if (radioName == null) { + xpath = "self::* | following::input[@type = 'radio'] | preceding::input[@type = 'radio']"; + } else { + xpath = String.format( + "self::* | following::input[@type = 'radio' and @name = '%s'] | " + + "preceding::input[@type = 'radio' and @name = '%s']", + radioName, radioName); + } + + return getWrappedElement().findElements(By.xpath(xpath)); + } + + /** + * Returns selected radio button. + * + * @return Optional {@code WebElement} representing selected radio button. + */ + public Optional getSelectedButton() { + return getButtons().stream() + .filter(WebElement::isSelected) + .findAny(); + } + + /** + * Indicates if radio group has selected button. + * + * @return {@code true} if radio has selected button and {@code false} otherwise. + */ + public boolean hasSelectedButton() { + return getButtons().stream() + .anyMatch(WebElement::isSelected); + } + + /** + * Selects first radio button that has a value matching the specified argument. + * + * @param value The value to match against. + */ + public void selectByValue(String value) { + WebElement matchingButton = getButtons().stream() + .filter(b -> value.equals(b.getAttribute("value"))) + .findFirst() + .orElseThrow(() -> new NoSuchElementException( + String.format("Cannot locate radio button with value: %s", value))); + + selectButton(matchingButton); + } + + /** + * Selects a radio button by the given index. + * + * @param index Index of a radio button to be selected. + */ + public void selectByIndex(int index) { + selectButton(getButtons().get(index)); + } + + /** + * Selects a radio button if it's not already selected. + * + * @param button {@code WebElement} representing radio button to be selected. + */ + private void selectButton(WebElement button) { + if (!button.isSelected()) { + button.click(); + } + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/element/Select.java b/src/main/java/com/frameworkium/core/htmlelements/element/Select.java new file mode 100755 index 00000000..31e52e88 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/element/Select.java @@ -0,0 +1,93 @@ +package com.frameworkium.core.htmlelements.element; + +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ISelect; + +import java.util.List; + +/** + * Represents a select control. + *

+ * This class wraps the Selenium {@link org.openqa.selenium.support.ui.Select} + * and delegates all method calls to it. + *

+ * But unlike {@code WebDriver} {@code Select} class there are no checks + * performed in the constructor of this class, so it can be used correctly + * with lazy initialization. + */ +public class Select extends TypifiedElement implements ISelect { + + /** + * Specifies wrapped {@link WebElement}. + * Performs no checks unlike {@link org.openqa.selenium.support.ui.Select}. + * All checks are made later in {@link #getSelect()} method. + * + * @param wrappedElement {@code WebElement} to wrap. + */ + public Select(WebElement wrappedElement) { + super(wrappedElement); + } + + /** + * Constructs instance of {@link org.openqa.selenium.support.ui.Select} class. + * + * @return {@link org.openqa.selenium.support.ui.Select} class instance. + */ + private org.openqa.selenium.support.ui.Select getSelect() { + return new org.openqa.selenium.support.ui.Select(getWrappedElement()); + } + + public boolean isMultiple() { + return getSelect().isMultiple(); + } + + public List getOptions() { + return getSelect().getOptions(); + } + + public List getAllSelectedOptions() { + return getSelect().getAllSelectedOptions(); + } + + public WebElement getFirstSelectedOption() { + return getSelect().getFirstSelectedOption(); + } + + /** + * Indicates if select has at least one selected option. + * + * @return {@code true} if select has at least one selected option and + * {@code false} otherwise. + */ + public boolean hasSelectedOption() { + return getOptions().stream().anyMatch(WebElement::isSelected); + } + + public void selectByVisibleText(String text) { + getSelect().selectByVisibleText(text); + } + + public void selectByIndex(int index) { + getSelect().selectByIndex(index); + } + + public void selectByValue(String value) { + getSelect().selectByValue(value); + } + + public void deselectAll() { + getSelect().deselectAll(); + } + + public void deselectByValue(String value) { + getSelect().deselectByValue(value); + } + + public void deselectByIndex(int index) { + getSelect().deselectByIndex(index); + } + + public void deselectByVisibleText(String text) { + getSelect().deselectByVisibleText(text); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/element/Table.java b/src/main/java/com/frameworkium/core/htmlelements/element/Table.java new file mode 100755 index 00000000..7a0f7ce7 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/element/Table.java @@ -0,0 +1,185 @@ +package com.frameworkium.core.htmlelements.element; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import java.util.*; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; + +/** + * Represents a simple table element. + * Provides convenient ways of retrieving data stored in it. + * + * @see com.frameworkium.core.ui.element.AbstractStreamTable + * @see com.frameworkium.core.ui.element.StreamTable + * @see com.frameworkium.core.ui.element.OptimisedStreamTable + */ +public class Table extends TypifiedElement { + + /** + * Specifies {@link WebElement} representing table tag. + * + * @param wrappedElement {@code WebElement} to wrap. + */ + public Table(WebElement wrappedElement) { + super(wrappedElement); + } + + /** + * Returns a list of table heading elements ({@code }). + *

+ * Multiple rows of heading elements, ({@code }), are flattened + * i.e. the second row, ({@code }), will follow the first, which can be + * misleading when the table uses {@code colspan} and {@code rowspan}. + * + * @return List with table heading elements. + */ + public List getHeadings() { + return getWrappedElement().findElements(By.xpath(".//th")); + } + + /** + * Returns text values of table heading elements (contained in "th" tags). + * + * @return List with text values of table heading elements. + */ + public List getHeadingsAsString() { + return getHeadings().stream() + .map(WebElement::getText) + .collect(toList()); + } + + /** + * Returns table cell elements ({@code }), grouped by rows. + * + * @return List where each item is a table row. + */ + public List> getRows() { + return getWrappedElement() + .findElements(By.xpath(".//tr")) + .stream() + .map(rowElement -> rowElement.findElements(By.xpath(".//td"))) + .filter(row -> row.size() > 0) // ignore rows with no tags + .collect(toList()); + } + + /** + * Returns text values of table cell elements ({@code }), grouped by rows. + * + * @return List where each item is text values of a table row. + */ + public List> getRowsAsString() { + return getRows().stream() + .map(row -> row.stream() + .map(WebElement::getText) + .collect(toList())) + .collect(toList()); + } + + /** + * Returns table cell elements ({@code }), grouped by columns. + * + * @return List where each item is a table column. + */ + public List> getColumns() { + List> columns = new ArrayList<>(); + List> rows = getRows(); + + if (rows.isEmpty()) { + return columns; + } + + int columnCount = rows.get(0).size(); + for (int i = 0; i < columnCount; i++) { + List column = new ArrayList<>(); + for (List row : rows) { + column.add(row.get(i)); + } + columns.add(column); + } + + return columns; + } + + /** + * Returns table cell elements ({@code }), of a particular column. + * + * @param index the 1-based index of the desired column + * @return List where each item is a cell of a particular column. + */ + public List getColumnByIndex(int index) { + return getWrappedElement().findElements( + By.cssSelector(String.format("tr > td:nth-of-type(%d)", index))); + } + + /** + * Returns text values of table cell elements ({@code }), grouped by columns. + * + * @return List where each item is text values of a table column. + */ + public List> getColumnsAsString() { + return getColumns().stream() + .map(row -> row.stream() + .map(WebElement::getText) + .collect(toList())) + .collect(toList()); + } + + /** + * Returns table cell element ({@code }), at i-th row and j-th column. + * + * @param i Row number + * @param j Column number + * @return Cell element at i-th row and j-th column. + */ + public WebElement getCellAt(int i, int j) { + return getRows().get(i).get(j); + } + + /** + * Returns list of maps where keys are table headings and values are table row elements ({@code }). + */ + public List> getRowsMappedToHeadings() { + List headingsAsString = getHeadingsAsString(); + return getRows().stream() + .map(row -> row.stream() + .collect(toMap(e -> headingsAsString.get(row.indexOf(e)), identity()))) + .collect(toList()); + } + + /** + * Returns list of maps where keys are passed headings and values are table row elements ({@code }),. + * + * @param headings List containing strings to be used as table headings. + */ + public List> getRowsMappedToHeadings(List headings) { + return getRowsMappedToHeadings().stream() + .map(e -> e.entrySet().stream().filter(m -> headings.contains(m.getKey())) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue))) + .collect(toList()); + } + + /** + * Same as {@link #getRowsMappedToHeadings()} but retrieves text from row elements ({@code }). + */ + public List> getRowsAsStringMappedToHeadings() { + return getRowsMappedToHeadings().stream() + .map(m -> m.entrySet().stream() + .collect(toMap(Map.Entry::getKey, e -> e.getValue().getText()))) + .collect(toList()); + + } + + /** + * Same as {@link #getRowsMappedToHeadings(List)} but retrieves text from row elements ({@code }). + */ + public List> getRowsAsStringMappedToHeadings(List headings) { + return getRowsMappedToHeadings(headings).stream() + .map(m -> m.entrySet().stream() + .collect(toMap(Map.Entry::getKey, e -> e.getValue().getText()))) + .collect(toList()); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/element/TextBlock.java b/src/main/java/com/frameworkium/core/htmlelements/element/TextBlock.java new file mode 100755 index 00000000..eaa31a24 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/element/TextBlock.java @@ -0,0 +1,10 @@ +package com.frameworkium.core.htmlelements.element; + +import org.openqa.selenium.WebElement; + +/** Represents text block on a web page. */ +public class TextBlock extends TypifiedElement { + public TextBlock(WebElement wrappedElement) { + super(wrappedElement); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/element/TextInput.java b/src/main/java/com/frameworkium/core/htmlelements/element/TextInput.java new file mode 100755 index 00000000..ef805efa --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/element/TextInput.java @@ -0,0 +1,61 @@ +package com.frameworkium.core.htmlelements.element; + +import org.apache.commons.lang3.StringUtils; +import org.openqa.selenium.Keys; +import org.openqa.selenium.WebElement; + +import java.util.Optional; + +/** + * Represents text input control + * (such as <input type="text"/> or <textarea/>). + */ +public class TextInput extends TypifiedElement { + + /** + * Specifies wrapped {@link WebElement}. + * + * @param wrappedElement {@code WebElement} to wrap. + */ + public TextInput(WebElement wrappedElement) { + super(wrappedElement); + } + + /** + * Retrieves the text entered into this text input. + * + * @return Text entered into the text input. + */ + @Override + public String getText() { + if ("textarea".equals(getWrappedElement().getTagName())) { + return getWrappedElement().getText(); + } + + return Optional + .ofNullable(getWrappedElement().getAttribute("value")) + .orElse(""); + } + + /** + * Sets the text of this Input. This is different to + * {@link #sendKeys(CharSequence...)} because it will delete any existing + * text first. + *

+ * {@code text} will equal {@link #getText()} after calling this method. + * + * @param text the text to set + */ + public void setText(CharSequence text) { + getWrappedElement().sendKeys(getClearCharSequence() + text); + } + + /** + * Returns sequence of backspaces and deletes that will clear element. + * clear() can't be used because generates separate onchange event + * See https://github.com/yandex-qatools/htmlelements/issues/65 + */ + public String getClearCharSequence() { + return StringUtils.repeat(Keys.DELETE.toString() + Keys.BACK_SPACE, getText().length()); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/element/TypifiedElement.java b/src/main/java/com/frameworkium/core/htmlelements/element/TypifiedElement.java new file mode 100755 index 00000000..f8252900 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/element/TypifiedElement.java @@ -0,0 +1,141 @@ +package com.frameworkium.core.htmlelements.element; + +import org.openqa.selenium.*; + +import java.util.List; + +/** + * The base class to be used for making classes representing typified elements + * i.e web page controls such as text inputs, buttons or more complex elements. + *

+ * There are several already written classes representing standard web page controls: + *

    + *
  • {@link Table}
  • + *
  • {@link TextInput}
  • + *
  • {@link CheckBox}
  • + *
  • {@link Select}
  • + *
  • {@link Link}
  • + *
  • {@link Button}
  • + *
  • {@link Radio}
  • + *
  • {@link TextBlock}
  • + *
  • {@link Image}
  • + *
  • {@link Form}
  • + *
  • {@link FileInput}
  • + *
+ */ +public abstract class TypifiedElement implements WrapsElement, WebElement { + private final WebElement wrappedElement; + + /** + * Specifies wrapped {@link WebElement}. + * + * @param wrappedElement {@code WebElement} to wrap. + */ + protected TypifiedElement(WebElement wrappedElement) { + this.wrappedElement = wrappedElement; + } + + /** + * Determines whether or not this element exists on page. + * + * @return True if the element exists on page, false otherwise. + */ + public boolean exists() { + try { + getWrappedElement().isDisplayed(); + } catch (NoSuchElementException ignored) { + return false; + } + return true; + } + + @Override + public WebElement getWrappedElement() { + return wrappedElement; + } + + @Override + public void click() { + getWrappedElement().click(); + } + + @Override + public void submit() { + getWrappedElement().submit(); + } + + @Override + public void sendKeys(CharSequence... keysToSend) { + getWrappedElement().sendKeys(keysToSend); + } + + @Override + public void clear() { + getWrappedElement().clear(); + } + + @Override + public String getTagName() { + return getWrappedElement().getTagName(); + } + + @Override + public String getAttribute(String name) { + return getWrappedElement().getAttribute(name); + } + + @Override + public boolean isSelected() { + return getWrappedElement().isSelected(); + } + + @Override + public boolean isEnabled() { + return getWrappedElement().isEnabled(); + } + + @Override + public String getText() { + return getWrappedElement().getText(); + } + + @Override + public List findElements(By by) { + return getWrappedElement().findElements(by); + } + + @Override + public WebElement findElement(By by) { + return getWrappedElement().findElement(by); + } + + @Override + public boolean isDisplayed() { + return getWrappedElement().isDisplayed(); + } + + @Override + public Point getLocation() { + return getWrappedElement().getLocation(); + } + + @Override + public Dimension getSize() { + return getWrappedElement().getSize(); + } + + @Override + public Rectangle getRect() { + return getWrappedElement().getRect(); + } + + @Override + public String getCssValue(String propertyName) { + return getWrappedElement().getCssValue(propertyName); + } + + @Override + public X getScreenshotAs(OutputType target) { + return getWrappedElement().getScreenshotAs(target); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/exceptions/HtmlElementsException.java b/src/main/java/com/frameworkium/core/htmlelements/exceptions/HtmlElementsException.java new file mode 100755 index 00000000..098f0abc --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/exceptions/HtmlElementsException.java @@ -0,0 +1,26 @@ +package com.frameworkium.core.htmlelements.exceptions; + +/** + * Thrown during runtime in cases when a block of elements or a + * page object can't be instantiated or initialized. + */ +public class HtmlElementsException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public HtmlElementsException() { + super(); + } + + public HtmlElementsException(String message) { + super(message); + } + + public HtmlElementsException(String message, Throwable cause) { + super(message, cause); + } + + public HtmlElementsException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/loader/HtmlElementLoader.java b/src/main/java/com/frameworkium/core/htmlelements/loader/HtmlElementLoader.java new file mode 100755 index 00000000..a61704b6 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/loader/HtmlElementLoader.java @@ -0,0 +1,129 @@ +package com.frameworkium.core.htmlelements.loader; + + +import com.frameworkium.core.htmlelements.element.HtmlElement; +import com.frameworkium.core.htmlelements.element.TypifiedElement; +import com.frameworkium.core.htmlelements.exceptions.HtmlElementsException; +import com.frameworkium.core.htmlelements.loader.decorator.HtmlElementDecorator; +import com.frameworkium.core.htmlelements.loader.decorator.HtmlElementLocatorFactory; +import com.frameworkium.core.htmlelements.loader.decorator.proxyhandlers.WebElementNamedProxyHandler; +import com.frameworkium.core.htmlelements.pagefactory.CustomElementLocatorFactory; +import org.openqa.selenium.SearchContext; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.PageFactory; +import org.openqa.selenium.support.pagefactory.ElementLocator; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; + +import static com.frameworkium.core.htmlelements.loader.decorator.ProxyFactory.createWebElementProxy; +import static com.frameworkium.core.htmlelements.utils.HtmlElementUtils.*; +import static com.frameworkium.core.htmlelements.utils.HtmlElementUtils.getElementName; + + + +/** Initialises blocks of elements and page objects. */ +public class HtmlElementLoader { + + /** + * Creates an instance of the given class representing a block of elements and initializes its fields + * with lazy proxies. + *

+ * Processes annotation of the given class + * to set the way of locating the block itself and {@link org.openqa.selenium.support.FindBy}, + * {@link org.openqa.selenium.support.FindBys} and {@link org.openqa.selenium.support.FindAll} + * annotations of its fields for setting the way of locating elements of the block. + *

+ * Fields to be proxied: + *

+ *

    + *
  • {@code WebElement}
  • + *
  • List of {@code WebElements}
  • + *
  • Block of elements ({@link HtmlElement} and its successors)
  • + *
  • List of blocks
  • + *
  • Typified element ({@link com.frameworkium.core.htmlelements.element.TypifiedElement} successors)
  • + *
  • List of typified elements
  • + *
+ * + * @param clazz A class to be instantiated and initialized. + * @param searchContext {@code SearchContext} that will be used to look up the elements. + * @return Initialized instance of the specified class. + */ + public static T createHtmlElement(Class clazz, SearchContext searchContext) { + ElementLocator locator = new HtmlElementLocatorFactory(searchContext).createLocator(clazz); + String elementName = getElementName(clazz); + + InvocationHandler handler = new WebElementNamedProxyHandler(locator, elementName); + WebElement elementToWrap = createWebElementProxy(clazz.getClassLoader(), handler); + return createHtmlElement(clazz, elementToWrap); + } + + public static T createHtmlElement(Class elementClass, WebElement elementToWrap) { + try { + T instance = newInstance(elementClass); + instance.setWrappedElement(elementToWrap); + // Recursively initialize elements of the block + populatePageObject(instance, elementToWrap); + return instance; + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException + | InvocationTargetException e) { + throw new HtmlElementsException(e); + } + } + + public static T createTypifiedElement(Class clazz, SearchContext searchContext) { + ElementLocator locator = new HtmlElementLocatorFactory(searchContext).createLocator(clazz); + String elementName = getElementName(clazz); + + InvocationHandler handler = new WebElementNamedProxyHandler(locator, elementName); + WebElement elementToWrap = createWebElementProxy(clazz.getClassLoader(), handler); + + return createTypifiedElement(clazz, elementToWrap); + } + + public static T createTypifiedElement(Class elementClass, WebElement elementToWrap) { + try { + return newInstance(elementClass, elementToWrap); + } catch (NoSuchMethodException + | InstantiationException + | IllegalAccessException + | InvocationTargetException e) { + throw new HtmlElementsException(e); + } + } + + /** + * Initializes fields of the given page object with lazy proxies. + *

+ * Processes {@link org.openqa.selenium.support.FindBy}, + * {@link org.openqa.selenium.support.FindBys} and {@link org.openqa.selenium.support.FindAll} + * annotations of the fields for setting the way of locating them. + *

+ * Fields to be proxied: + *

+ *

    + *
  • {@code WebElement}
  • + *
  • List of {@code WebElements}
  • + *
  • Block of elements ({@link HtmlElement} and its successors)
  • + *
  • List of blocks
  • + *
  • Typified element ({@link com.frameworkium.core.htmlelements.element.TypifiedElement} successors)
  • + *
  • List of typified elements
  • + *
+ * + * @param page Page object to be initialized. + * @param searchContext The {@code WebDriver} instance that will be used to look up the elements. + */ + public static void populatePageObject(Object page, SearchContext searchContext) { + populatePageObject(page, new HtmlElementLocatorFactory(searchContext)); + } + + /** + * Initializes fields of the given page object using specified locator factory. + * + * @param page Page object to be initialized. + * @param locatorFactory Locator factory that will be used to locate elements. + */ + public static void populatePageObject(Object page, CustomElementLocatorFactory locatorFactory) { + PageFactory.initElements(new HtmlElementDecorator(locatorFactory), page); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/HtmlElementClassAnnotationsHandler.java b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/HtmlElementClassAnnotationsHandler.java new file mode 100755 index 00000000..1b897632 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/HtmlElementClassAnnotationsHandler.java @@ -0,0 +1,37 @@ +package com.frameworkium.core.htmlelements.loader.decorator; + +import com.frameworkium.core.htmlelements.element.HtmlElement; +import com.frameworkium.core.htmlelements.exceptions.HtmlElementsException; +import org.openqa.selenium.By; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.FindBy.FindByBuilder; +import org.openqa.selenium.support.pagefactory.AbstractAnnotations; + +/** Handles annotation of {@link HtmlElement} and its successors. */ +public class HtmlElementClassAnnotationsHandler extends AbstractAnnotations { + + private final Class elementClass; + + public HtmlElementClassAnnotationsHandler(Class elementClass) { + this.elementClass = elementClass; + } + + @Override + public By buildBy() { + Class clazz = elementClass; + while (clazz != Object.class) { + if (clazz.isAnnotationPresent(FindBy.class)) { + return new FindByBuilder().buildIt(clazz.getAnnotation(FindBy.class), null); + } + clazz = clazz.getSuperclass(); + } + + throw new HtmlElementsException(String.format( + "Cannot determine how to locate instance of %s", elementClass)); + } + + @Override + public boolean isLookupCached() { + return false; + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/HtmlElementDecorator.java b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/HtmlElementDecorator.java new file mode 100755 index 00000000..462ab2a1 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/HtmlElementDecorator.java @@ -0,0 +1,119 @@ +package com.frameworkium.core.htmlelements.loader.decorator; + +import com.frameworkium.core.htmlelements.element.HtmlElement; +import com.frameworkium.core.htmlelements.element.TypifiedElement; +import com.frameworkium.core.htmlelements.loader.decorator.proxyhandlers.*; +import com.frameworkium.core.htmlelements.pagefactory.CustomElementLocatorFactory; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.pagefactory.*; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.util.List; + +import static com.frameworkium.core.htmlelements.loader.HtmlElementLoader.createHtmlElement; +import static com.frameworkium.core.htmlelements.loader.HtmlElementLoader.createTypifiedElement; +import static com.frameworkium.core.htmlelements.loader.decorator.ProxyFactory.*; +import static com.frameworkium.core.htmlelements.utils.HtmlElementUtils.*; + +/** + * Decorator which is used to decorate fields of blocks and page objects. + *

+ * Will decorate the following fields with lazy proxies: + *

+ *

    + *
  • {@code WebElement}
  • + *
  • {@code List}
  • + *
  • Block of elements ({@link HtmlElement} and its successors)
  • + *
  • List of blocks
  • + *
  • Typified element ({@link com.frameworkium.core.htmlelements.element.TypifiedElement} successors)
  • + *
  • List of typified elements
  • + *
+ *

+ * {@code WebElements}, lists of {@code WebElements}, typified elements and lists of typified elements + * have to be marked with {@link org.openqa.selenium.support.FindBy}, {@link org.openqa.selenium.support.FindBys} + * or {@link org.openqa.selenium.support.FindAll} annotation. + */ +public class HtmlElementDecorator implements FieldDecorator { + protected ElementLocatorFactory factory; + + public HtmlElementDecorator(CustomElementLocatorFactory factory) { + this.factory = factory; + } + + public Object decorate(ClassLoader loader, Field field) { + try { + if (isTypifiedElement(field)) { + return decorateTypifiedElement(loader, field); + } + if (isHtmlElement(field)) { + return decorateHtmlElement(loader, field); + } + if (isWebElement(field) && !field.getName().equals("wrappedElement")) { + return decorateWebElement(loader, field); + } + if (isTypifiedElementList(field)) { + return decorateTypifiedElementList(loader, field); + } + if (isHtmlElementList(field)) { + return decorateHtmlElementList(loader, field); + } + if (isWebElementList(field)) { + return decorateWebElementList(loader, field); + } + return null; + } catch (ClassCastException ignore) { + return null; // See bug #94 and NonElementFieldsTest + } + } + + protected T decorateTypifiedElement(ClassLoader loader, Field field) { + WebElement elementToWrap = decorateWebElement(loader, field); + + //noinspection unchecked + return createTypifiedElement((Class) field.getType(), elementToWrap); + } + + protected T decorateHtmlElement(ClassLoader loader, Field field) { + WebElement elementToWrap = decorateWebElement(loader, field); + + //noinspection unchecked + return createHtmlElement((Class) field.getType(), elementToWrap); + } + + protected WebElement decorateWebElement(ClassLoader loader, Field field) { + ElementLocator locator = factory.createLocator(field); + InvocationHandler handler = new WebElementNamedProxyHandler(locator, getElementName(field)); + + return createWebElementProxy(loader, handler); + } + + protected List decorateTypifiedElementList(ClassLoader loader, Field field) { + @SuppressWarnings("unchecked") + Class elementClass = (Class) getGenericParameterClass(field); + ElementLocator locator = factory.createLocator(field); + String name = getElementName(field); + + InvocationHandler handler = new TypifiedElementListNamedProxyHandler<>(elementClass, locator, name); + + return createTypifiedElementListProxy(loader, handler); + } + + protected List decorateHtmlElementList(ClassLoader loader, Field field) { + @SuppressWarnings("unchecked") + Class elementClass = (Class) getGenericParameterClass(field); + ElementLocator locator = factory.createLocator(field); + String name = getElementName(field); + + InvocationHandler handler = new HtmlElementListNamedProxyHandler<>(elementClass, locator, name); + + return createHtmlElementListProxy(loader, handler); + } + + protected List decorateWebElementList(ClassLoader loader, Field field) { + ElementLocator locator = factory.createLocator(field); + InvocationHandler handler = new WebElementListNamedProxyHandler(locator, getElementName(field)); + + return createWebElementListProxy(loader, handler); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/HtmlElementFieldAnnotationsHandler.java b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/HtmlElementFieldAnnotationsHandler.java new file mode 100755 index 00000000..53452a70 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/HtmlElementFieldAnnotationsHandler.java @@ -0,0 +1,90 @@ +package com.frameworkium.core.htmlelements.loader.decorator; + +import com.frameworkium.core.htmlelements.exceptions.HtmlElementsException; +import org.openqa.selenium.By; +import org.openqa.selenium.support.*; +import org.openqa.selenium.support.pagefactory.Annotations; + +import java.lang.reflect.Field; + +import static com.frameworkium.core.htmlelements.utils.HtmlElementUtils.*; + +/** + * Extends default field annotations handling mechanism with processing + * annotation for blocks and lists of blocks. + */ +public class HtmlElementFieldAnnotationsHandler extends Annotations { + public HtmlElementFieldAnnotationsHandler(Field field) { + super(field); + } + + @Override + public By buildBy() { + if (isHtmlElement(getField()) || isTypifiedElement(getField())) { + return buildByFromHtmlElementAnnotations(); + } + if (isHtmlElementList(getField()) || isTypifiedElementList(getField())) { + return buildByFromHtmlElementListAnnotations(); + } + return super.buildBy(); + } + + private By buildByFromFindAnnotations() { + if (getField().isAnnotationPresent(FindBys.class)) { + FindBys findBys = getField().getAnnotation(FindBys.class); + return new FindBys.FindByBuilder().buildIt(findBys, null); + } + + if (getField().isAnnotationPresent(FindAll.class)) { + FindAll findAll = getField().getAnnotation(FindAll.class); + return new FindAll.FindByBuilder().buildIt(findAll, null); + } + + if (getField().isAnnotationPresent(FindBy.class)) { + FindBy findBy = getField().getAnnotation(FindBy.class); + return new FindBy.FindByBuilder().buildIt(findBy, null); + } + return null; + } + + private By buildByFromHtmlElementAnnotations() { + assertValidAnnotations(); + + By result = buildByFromFindAnnotations(); + if (result != null) { + return result; + } + + Class fieldClass = getField().getType(); + while (fieldClass != Object.class) { + if (fieldClass.isAnnotationPresent(FindBy.class)) { + return new FindBy.FindByBuilder() + .buildIt(fieldClass.getAnnotation(FindBy.class), null); + } + fieldClass = fieldClass.getSuperclass(); + } + + return buildByFromDefault(); + } + + private By buildByFromHtmlElementListAnnotations() { + assertValidAnnotations(); + + By result = buildByFromFindAnnotations(); + if (result != null) { + return result; + } + + Class listParameterClass = getGenericParameterClass(getField()); + while (listParameterClass != Object.class) { + if (listParameterClass.isAnnotationPresent(FindBy.class)) { + return new FindBy.FindByBuilder() + .buildIt(listParameterClass.getAnnotation(FindBy.class), null); + } + listParameterClass = listParameterClass.getSuperclass(); + } + + throw new HtmlElementsException( + String.format("Cannot determine how to locate element %s", getField())); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/HtmlElementLocatorFactory.java b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/HtmlElementLocatorFactory.java new file mode 100755 index 00000000..6672f5d3 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/HtmlElementLocatorFactory.java @@ -0,0 +1,71 @@ +package com.frameworkium.core.htmlelements.loader.decorator; + +import com.frameworkium.core.htmlelements.annotations.Timeout; +import com.frameworkium.core.htmlelements.pagefactory.CustomElementLocatorFactory; +import com.frameworkium.core.htmlelements.utils.HtmlElementUtils; +import org.openqa.selenium.SearchContext; +import org.openqa.selenium.support.pagefactory.AjaxElementLocator; +import org.openqa.selenium.support.pagefactory.ElementLocator; + +import java.lang.reflect.*; + +/** A factory for producing locator instances. */ +public class HtmlElementLocatorFactory implements CustomElementLocatorFactory { + + private final SearchContext searchContext; + + public HtmlElementLocatorFactory(SearchContext searchContext) { + this.searchContext = searchContext; + } + + /** + * Creates locator for the given field. Created locator will process {@link org.openqa.selenium.support.FindBy}, + * {@link org.openqa.selenium.support.FindBy}, {@link org.openqa.selenium.support.FindBys}, + * {@link org.openqa.selenium.support.FindAll} and {@link org.openqa.selenium.support.CacheLookup} annotations. + * + * @param field Field for which locator will be created. + */ + public ElementLocator createLocator(Field field) { + return new AjaxElementLocator(searchContext, getTimeOut(field), new HtmlElementFieldAnnotationsHandler(field)); + } + + /** + * Creates locator for the given field. Created locator will process {@link org.openqa.selenium.support.FindBy}, + * {@link org.openqa.selenium.support.FindBy}, {@link org.openqa.selenium.support.FindBys}, + * {@link org.openqa.selenium.support.FindAll} and {@link org.openqa.selenium.support.CacheLookup} annotations. + * + * @param clazz Class for which locator will be created. + */ + @SuppressWarnings("rawtypes") + public ElementLocator createLocator(Class clazz) { + return new AjaxElementLocator(searchContext, getTimeOut(clazz), new HtmlElementClassAnnotationsHandler(clazz)); + } + + public int getTimeOut(Field field) { + if (field.isAnnotationPresent(Timeout.class)) { + return field.getAnnotation(Timeout.class).value(); + } + if (field.getGenericType() instanceof Class) { + return getTimeOut((Class) field.getGenericType()); + } + return getTimeOut((Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]); + } + + public int getTimeOut(Class clazz) { + try { + Method method = Timeout.class.getMethod("value"); + do { + if (clazz.isAnnotationPresent(Timeout.class)) { + return (Integer) method.invoke(clazz.getAnnotation(Timeout.class)); + } + clazz = clazz.getSuperclass(); + } + while (clazz != Object.class && clazz != null); + } catch (NoSuchMethodException + | InvocationTargetException + | IllegalAccessException ignored) { + } + + return HtmlElementUtils.getImplicitTimeoutInSeconds(); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/ProxyFactory.java b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/ProxyFactory.java new file mode 100755 index 00000000..536932bd --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/ProxyFactory.java @@ -0,0 +1,37 @@ +package com.frameworkium.core.htmlelements.loader.decorator; + +import com.frameworkium.core.htmlelements.element.HtmlElement; +import com.frameworkium.core.htmlelements.element.TypifiedElement; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.WrapsElement; +import org.openqa.selenium.interactions.Locatable; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.util.List; + +/** + * Contains factory methods for creating proxy of blocks, typified elements, page objects + */ +@SuppressWarnings("unchecked") +public class ProxyFactory { + public static T createWebElementProxy(ClassLoader loader, InvocationHandler handler) { + Class[] interfaces = new Class[]{WebElement.class, WrapsElement.class, Locatable.class}; + return (T) Proxy.newProxyInstance(loader, interfaces, handler); + } + + public static List createWebElementListProxy(ClassLoader loader, + InvocationHandler handler) { + return (List) Proxy.newProxyInstance(loader, new Class[]{List.class}, handler); + } + + public static List createTypifiedElementListProxy(ClassLoader loader, + InvocationHandler handler) { + return (List) Proxy.newProxyInstance(loader, new Class[]{List.class}, handler); + } + + public static List createHtmlElementListProxy(ClassLoader loader, + InvocationHandler handler) { + return (List) Proxy.newProxyInstance(loader, new Class[]{List.class}, handler); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/HtmlElementListNamedProxyHandler.java b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/HtmlElementListNamedProxyHandler.java new file mode 100755 index 00000000..7bbed886 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/HtmlElementListNamedProxyHandler.java @@ -0,0 +1,42 @@ +package com.frameworkium.core.htmlelements.loader.decorator.proxyhandlers; + +import com.frameworkium.core.htmlelements.element.HtmlElement; +import org.openqa.selenium.support.pagefactory.ElementLocator; + +import java.lang.reflect.*; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import static com.frameworkium.core.htmlelements.loader.HtmlElementLoader.createHtmlElement; + +public class HtmlElementListNamedProxyHandler implements InvocationHandler { + + private final Class elementClass; + private final ElementLocator locator; + private final String name; + + public HtmlElementListNamedProxyHandler(Class elementClass, ElementLocator locator, String name) { + this.elementClass = elementClass; + this.locator = locator; + this.name = name; + } + + @Override + public Object invoke(Object o, Method method, Object[] objects) throws Throwable { + if ("toString".equals(method.getName())) { + return name; + } + + List elements = locator.findElements().stream() + .map(element -> createHtmlElement(elementClass, element)) + .collect(Collectors.toCollection(LinkedList::new)); + + try { + return method.invoke(elements, objects); + } catch (InvocationTargetException e) { + // Unwrap the underlying exception + throw e.getCause(); + } + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/TypifiedElementListNamedProxyHandler.java b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/TypifiedElementListNamedProxyHandler.java new file mode 100755 index 00000000..92f17fea --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/TypifiedElementListNamedProxyHandler.java @@ -0,0 +1,42 @@ +package com.frameworkium.core.htmlelements.loader.decorator.proxyhandlers; + +import com.frameworkium.core.htmlelements.element.TypifiedElement; +import org.openqa.selenium.support.pagefactory.ElementLocator; + +import java.lang.reflect.*; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import static com.frameworkium.core.htmlelements.loader.HtmlElementLoader.createTypifiedElement; + +public class TypifiedElementListNamedProxyHandler implements InvocationHandler { + + private final Class elementClass; + private final ElementLocator locator; + private final String name; + + public TypifiedElementListNamedProxyHandler(Class elementClass, ElementLocator locator, String name) { + this.elementClass = elementClass; + this.locator = locator; + this.name = name; + } + + @Override + public Object invoke(Object o, Method method, Object[] objects) throws Throwable { + if ("toString".equals(method.getName())) { + return name; + } + + List elements = locator.findElements().stream() + .map(element -> createTypifiedElement(elementClass, element)) + .collect(Collectors.toCollection(LinkedList::new)); + + try { + return method.invoke(elements, objects); + } catch (InvocationTargetException e) { + // Unwrap the underlying exception + throw e.getCause(); + } + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/WebElementListNamedProxyHandler.java b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/WebElementListNamedProxyHandler.java new file mode 100755 index 00000000..27b20e59 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/WebElementListNamedProxyHandler.java @@ -0,0 +1,24 @@ +package com.frameworkium.core.htmlelements.loader.decorator.proxyhandlers; + +import org.openqa.selenium.support.pagefactory.ElementLocator; +import org.openqa.selenium.support.pagefactory.internal.LocatingElementListHandler; + +import java.lang.reflect.Method; + +public class WebElementListNamedProxyHandler extends LocatingElementListHandler { + + private final String name; + + public WebElementListNamedProxyHandler(ElementLocator locator, String name) { + super(locator); + this.name = name; + } + + @Override + public Object invoke(Object o, Method method, Object[] objects) throws Throwable { + if ("toString".equals(method.getName())) { + return name; + } + return super.invoke(o, method, objects); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/WebElementNamedProxyHandler.java b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/WebElementNamedProxyHandler.java new file mode 100755 index 00000000..a5bc2dd7 --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/WebElementNamedProxyHandler.java @@ -0,0 +1,53 @@ +package com.frameworkium.core.htmlelements.loader.decorator.proxyhandlers; + +import com.frameworkium.core.htmlelements.utils.HtmlElementUtils; +import org.openqa.selenium.StaleElementReferenceException; +import org.openqa.selenium.support.pagefactory.ElementLocator; +import org.openqa.selenium.support.pagefactory.internal.LocatingElementHandler; + +import java.lang.reflect.Method; +import java.time.Clock; +import java.util.concurrent.TimeUnit; + +public class WebElementNamedProxyHandler extends LocatingElementHandler { + + private final long timeOutInSeconds; + private final Clock clock; + private final String name; + + public WebElementNamedProxyHandler(ElementLocator locator, String name) { + super(locator); + this.name = name; + this.clock = Clock.systemDefaultZone(); + this.timeOutInSeconds = HtmlElementUtils.getImplicitTimeoutInSeconds(); + } + + @Override + public Object invoke(Object o, Method method, Object[] objects) throws Throwable { + if ("toString".equals(method.getName())) { + return name; + } + + final long end = this.clock.millis() + TimeUnit.SECONDS.toMillis(this.timeOutInSeconds); + + StaleElementReferenceException lastException; + do { + try { + return super.invoke(o, method, objects); + } catch (StaleElementReferenceException e) { + lastException = e; + this.waitFor(); + } + } + while (this.clock.millis() < end); + throw lastException; + } + + protected long sleepFor() { + return 500L; + } + + private void waitFor() throws InterruptedException { + Thread.sleep(this.sleepFor()); + } +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/pagefactory/CustomElementLocatorFactory.java b/src/main/java/com/frameworkium/core/htmlelements/pagefactory/CustomElementLocatorFactory.java new file mode 100755 index 00000000..5770d9fa --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/pagefactory/CustomElementLocatorFactory.java @@ -0,0 +1,12 @@ +package com.frameworkium.core.htmlelements.pagefactory; + +import org.openqa.selenium.support.pagefactory.ElementLocator; +import org.openqa.selenium.support.pagefactory.ElementLocatorFactory; + +/** + * A factory for producing {@link ElementLocator}s. It is expected that a new + * ElementLocator will be returned per call. + */ +public interface CustomElementLocatorFactory extends ElementLocatorFactory { + ElementLocator createLocator(Class clazz); +} diff --git a/src/main/java/com/frameworkium/core/htmlelements/utils/HtmlElementUtils.java b/src/main/java/com/frameworkium/core/htmlelements/utils/HtmlElementUtils.java new file mode 100755 index 00000000..9f593b0e --- /dev/null +++ b/src/main/java/com/frameworkium/core/htmlelements/utils/HtmlElementUtils.java @@ -0,0 +1,156 @@ +package com.frameworkium.core.htmlelements.utils; + + +import com.frameworkium.core.htmlelements.element.HtmlElement; +import com.frameworkium.core.htmlelements.element.TypifiedElement; +import com.frameworkium.core.htmlelements.exceptions.HtmlElementsException; +import com.google.common.collect.Lists; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.RemoteWebDriver; +import org.openqa.selenium.remote.RemoteWebElement; + +import java.lang.reflect.*; +import java.net.URL; +import java.util.List; + +import static org.apache.commons.lang3.reflect.ConstructorUtils.invokeConstructor; + +/** Contains utility methods used in framework. */ +public final class HtmlElementUtils { + + /** + * Default implicit timeout for HtmlElements, should be less than + * any explicit waits to prevent + * {@code org.openqa.selenium.TimeoutException: Supplied function might have stalled} + */ + public static final int DEFAULT_TIMEOUT_SECS = 6; + + private HtmlElementUtils() { + } + + public static T newInstance(Class clazz, Object... args) throws IllegalAccessException, + InstantiationException, NoSuchMethodException, InvocationTargetException { + if (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())) { + Class outerClass = clazz.getDeclaringClass(); + Object outerObject = outerClass.newInstance(); + return invokeConstructor(clazz, Lists.asList(outerObject, args).toArray()); + } + return invokeConstructor(clazz, args); + } + + public static boolean isHtmlElement(Field field) { + return isHtmlElement(field.getType()); + } + + public static boolean isHtmlElement(Class clazz) { + return HtmlElement.class.isAssignableFrom(clazz); + } + + public static boolean isHtmlElement(T instance) { + return instance instanceof HtmlElement; + } + + public static boolean isTypifiedElement(Field field) { + return isTypifiedElement(field.getType()); + } + + public static boolean isTypifiedElement(Class clazz) { + return TypifiedElement.class.isAssignableFrom(clazz); + } + + public static boolean isWebElement(Field field) { + return isWebElement(field.getType()); + } + + public static boolean isWebElement(Class clazz) { + return WebElement.class.isAssignableFrom(clazz); + } + + public static boolean isHtmlElementList(Field field) { + if (!isParametrizedList(field)) { + return false; + } + Class listParameterClass = getGenericParameterClass(field); + return isHtmlElement(listParameterClass); + } + + public static boolean isTypifiedElementList(Field field) { + if (!isParametrizedList(field)) { + return false; + } + Class listParameterClass = getGenericParameterClass(field); + return isTypifiedElement(listParameterClass); + } + + public static boolean isWebElementList(Field field) { + if (!isParametrizedList(field)) { + return false; + } + Class listParameterClass = getGenericParameterClass(field); + return isWebElement(listParameterClass); + } + + public static Class getGenericParameterClass(Field field) { + Type genericType = field.getGenericType(); + return (Class) ((ParameterizedType) genericType).getActualTypeArguments()[0]; + } + + private static boolean isParametrizedList(Field field) { + return isList(field) && hasGenericParameter(field); + } + + private static boolean isList(Field field) { + return List.class.isAssignableFrom(field.getType()); + } + + private static boolean hasGenericParameter(Field field) { + return field.getGenericType() instanceof ParameterizedType; + } + + public static String getElementName(Field field) { + return field.getName(); + } + + public static String getElementName(Class clazz) { + return clazz.getSimpleName(); + } + + public static boolean isRemoteWebElement(WebElement element) { + return element.getClass().equals(RemoteWebElement.class); + } + + public static boolean isOnRemoteWebDriver(WebElement element) { + if (!isRemoteWebElement(element)) { + return false; + } + + // Since subclasses of RemoteWebElement were finally removed in Selenium 2.26.0, WebElements on local drivers + // are also instances of RemoteWebElement class. The only way that we found at the current moment to find out + // whether WebElement instance is on remote driver is to check the class of RemoteWebElement "parent" filed, + // which contains WebDriver instance to which this RemoteWebElement belongs. + // As this field has protected access this is done by reflection. + // TODO It's is a kind of a dirty hack to be improved in future versions. + RemoteWebElement remoteWebElement = (RemoteWebElement) element; + try { + Field elementParentFiled = RemoteWebElement.class.getDeclaredField("parent"); + elementParentFiled.setAccessible(true); + WebDriver elementParent = (WebDriver) elementParentFiled.get(remoteWebElement); + return elementParent.getClass().equals(RemoteWebDriver.class); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new HtmlElementsException("Unable to find out if WebElement is on remote driver", e); + } + } + + public static boolean existsInClasspath(final String fileName) { + return getResourceFromClasspath(fileName) != null; + } + + public static URL getResourceFromClasspath(final String fileName) { + return Thread.currentThread().getContextClassLoader().getResource(fileName); + } + + public static int getImplicitTimeoutInSeconds() { + return Integer.getInteger("webdriver.timeouts.implicitlywait", DEFAULT_TIMEOUT_SECS); + } +} diff --git a/src/main/java/com/frameworkium/core/ui/ExtraExpectedConditions.java b/src/main/java/com/frameworkium/core/ui/ExtraExpectedConditions.java index 79b46804..e1f129ff 100755 --- a/src/main/java/com/frameworkium/core/ui/ExtraExpectedConditions.java +++ b/src/main/java/com/frameworkium/core/ui/ExtraExpectedConditions.java @@ -3,8 +3,7 @@ import org.openqa.selenium.*; import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.ExpectedConditions; -import ru.yandex.qatools.htmlelements.element.HtmlElement; -import ru.yandex.qatools.htmlelements.element.TypifiedElement; + import java.util.List; import java.util.function.Function; @@ -14,7 +13,8 @@ * Frameworkium extension of {@link ExpectedConditions}. * *

This provides useful {@link ExpectedCondition}'s for our lazy proxied - * {@link WebElement}, {@link TypifiedElement} and {@link HtmlElement}'s. + * {@link WebElement}, {@link com.frameworkium.core.htmlelements.element.TypifiedElement} + * and {@link com.frameworkium.core.htmlelements.element.HtmlElement}'s. * The methods here generally accept {@link WebElement} rather than {@link By} * because we typically use {@code @FindBy} annotations in page objects. */ diff --git a/src/main/java/com/frameworkium/core/ui/driver/drivers/SauceImpl.java b/src/main/java/com/frameworkium/core/ui/driver/drivers/SauceImpl.java index bd52b79a..c2352f41 100644 --- a/src/main/java/com/frameworkium/core/ui/driver/drivers/SauceImpl.java +++ b/src/main/java/com/frameworkium/core/ui/driver/drivers/SauceImpl.java @@ -4,7 +4,6 @@ import com.frameworkium.core.ui.driver.Driver; import com.frameworkium.core.ui.driver.remotes.Sauce; import org.openqa.selenium.*; -import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.RemoteWebDriver; import java.io.File; @@ -76,8 +75,7 @@ private MutableCapabilities getDesktopCapabilities(String platformName) { private MutableCapabilities getAndroidCapabilities() { MutableCapabilities caps = new MutableCapabilities(capabilities); - caps.merge(DesiredCapabilities.android()); - caps.setCapability("platform", "Linux"); + caps.setCapability("platformName", "Android"); if (PLATFORM_VERSION.isSpecified()) { caps.setCapability("version", PLATFORM_VERSION.getValue()); } @@ -88,8 +86,8 @@ private MutableCapabilities getAndroidCapabilities() { private MutableCapabilities getIOSCapabilities() { MutableCapabilities caps = new MutableCapabilities(capabilities); - caps.merge(DesiredCapabilities.iphone()); - caps.setCapability("platform", "OS X 10.10"); + caps.setCapability("deviceName", "iPhone .*"); + caps.setCapability("platformName", "iOS"); if (PLATFORM_VERSION.isSpecified()) { caps.setCapability("version", PLATFORM_VERSION.getValue()); } @@ -109,10 +107,10 @@ private MutableCapabilities getAppiumCapabilities() { caps.setCapability("browserName", ""); switch (platform) { case IOS: - caps.merge(DesiredCapabilities.iphone()); + caps.setCapability("deviceName", "iPhone .*"); return getAppiumCapabilities(caps, "iOS", "Simulator"); case ANDROID: - caps.merge(DesiredCapabilities.android()); + caps.setCapability("platformName", "Android"); return getAppiumCapabilities(caps, "Android", "Emulator"); default: throw new IllegalStateException("Appium is only available on iOS/Android"); diff --git a/src/main/java/com/frameworkium/core/ui/element/AbstractStreamTable.java b/src/main/java/com/frameworkium/core/ui/element/AbstractStreamTable.java index 0c669bf0..66d73728 100644 --- a/src/main/java/com/frameworkium/core/ui/element/AbstractStreamTable.java +++ b/src/main/java/com/frameworkium/core/ui/element/AbstractStreamTable.java @@ -1,8 +1,8 @@ package com.frameworkium.core.ui.element; +import com.frameworkium.core.htmlelements.element.HtmlElement; import com.google.common.collect.Streams; import org.openqa.selenium.*; -import ru.yandex.qatools.htmlelements.element.HtmlElement; import java.util.Objects; import java.util.Optional; diff --git a/src/main/java/com/frameworkium/core/ui/pages/BasePage.java b/src/main/java/com/frameworkium/core/ui/pages/BasePage.java index b27af7c5..e24fe61c 100755 --- a/src/main/java/com/frameworkium/core/ui/pages/BasePage.java +++ b/src/main/java/com/frameworkium/core/ui/pages/BasePage.java @@ -1,6 +1,7 @@ package com.frameworkium.core.ui.pages; import com.frameworkium.core.common.reporting.allure.AllureLogger; +import com.frameworkium.core.htmlelements.loader.HtmlElementLoader; import com.frameworkium.core.ui.UITestLifecycle; import com.frameworkium.core.ui.annotations.Visible; import com.frameworkium.core.ui.capture.ScreenshotCapture; @@ -11,7 +12,6 @@ import org.apache.logging.log4j.Logger; import org.openqa.selenium.*; import org.openqa.selenium.support.ui.Wait; -import ru.yandex.qatools.htmlelements.loader.HtmlElementLoader; import java.time.Duration; diff --git a/src/main/java/com/frameworkium/core/ui/pages/Visibility.java b/src/main/java/com/frameworkium/core/ui/pages/Visibility.java index 52c6546a..b67c6850 100644 --- a/src/main/java/com/frameworkium/core/ui/pages/Visibility.java +++ b/src/main/java/com/frameworkium/core/ui/pages/Visibility.java @@ -1,10 +1,10 @@ package com.frameworkium.core.ui.pages; +import com.frameworkium.core.htmlelements.element.HtmlElement; import com.frameworkium.core.ui.ExtraExpectedConditions; import com.frameworkium.core.ui.annotations.*; import org.openqa.selenium.*; import org.openqa.selenium.support.ui.Wait; -import ru.yandex.qatools.htmlelements.element.HtmlElement; import java.lang.annotation.Annotation; import java.lang.reflect.Field; @@ -14,9 +14,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static com.frameworkium.core.htmlelements.utils.HtmlElementUtils.*; import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOf; import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfAllElements; -import static ru.yandex.qatools.htmlelements.utils.HtmlElementUtils.*; /** * All things Frameworkium-related dealing with PageObject element visibility. diff --git a/src/test/groovy/com/frameworkium/core/ui/pages/VisibilitySpec.groovy b/src/test/groovy/com/frameworkium/core/ui/pages/VisibilitySpec.groovy index 777e5e22..a834557c 100644 --- a/src/test/groovy/com/frameworkium/core/ui/pages/VisibilitySpec.groovy +++ b/src/test/groovy/com/frameworkium/core/ui/pages/VisibilitySpec.groovy @@ -1,5 +1,6 @@ package com.frameworkium.core.ui.pages +import com.frameworkium.core.htmlelements.element.TextInput import com.frameworkium.core.ui.pages.pageobjects.PageObjects import org.openqa.selenium.JavascriptExecutor import org.openqa.selenium.TimeoutException @@ -9,7 +10,6 @@ import org.openqa.selenium.support.events.EventFiringWebDriver import org.openqa.selenium.support.ui.FluentWait import org.openqa.selenium.support.ui.Sleeper import org.openqa.selenium.support.ui.Wait -import ru.yandex.qatools.htmlelements.element.TextInput import spock.lang.Specification import java.time.Clock diff --git a/src/test/groovy/com/frameworkium/core/ui/pages/pageobjects/PageObjects.groovy b/src/test/groovy/com/frameworkium/core/ui/pages/pageobjects/PageObjects.groovy index 82c2d177..766281a9 100644 --- a/src/test/groovy/com/frameworkium/core/ui/pages/pageobjects/PageObjects.groovy +++ b/src/test/groovy/com/frameworkium/core/ui/pages/pageobjects/PageObjects.groovy @@ -1,5 +1,9 @@ package com.frameworkium.core.ui.pages.pageobjects +import com.frameworkium.core.htmlelements.annotations.Timeout +import com.frameworkium.core.htmlelements.element.CheckBox +import com.frameworkium.core.htmlelements.element.HtmlElement +import com.frameworkium.core.htmlelements.element.TextInput import com.frameworkium.core.ui.annotations.ForceVisible import com.frameworkium.core.ui.annotations.Invisible import com.frameworkium.core.ui.annotations.Visible @@ -8,11 +12,7 @@ import groovy.transform.InheritConstructors import org.openqa.selenium.WebElement import org.openqa.selenium.support.CacheLookup import org.openqa.selenium.support.FindBy -import ru.yandex.qatools.htmlelements.annotations.Name -import ru.yandex.qatools.htmlelements.annotations.Timeout -import ru.yandex.qatools.htmlelements.element.CheckBox -import ru.yandex.qatools.htmlelements.element.HtmlElement -import ru.yandex.qatools.htmlelements.element.TextInput + class PageObjects { @@ -105,7 +105,6 @@ class PageObjects { @ForceVisible @Invisible @Timeout(42) - @Name("named element") @FindBy(css = "html") @CacheLookup CheckBox myTypifiedElement diff --git a/src/test/java/com/frameworkium/core/ui/ExtraExpectedConditionsTest.java b/src/test/java/com/frameworkium/core/ui/ExtraExpectedConditionsTest.java index b3c03c83..4d11e400 100644 --- a/src/test/java/com/frameworkium/core/ui/ExtraExpectedConditionsTest.java +++ b/src/test/java/com/frameworkium/core/ui/ExtraExpectedConditionsTest.java @@ -1,8 +1,8 @@ package com.frameworkium.core.ui; +import com.frameworkium.core.htmlelements.element.Link; import com.frameworkium.core.ui.element.StreamTable; import org.testng.annotations.Test; -import ru.yandex.qatools.htmlelements.element.Link; import java.util.ArrayList; import java.util.List; diff --git a/src/test/java/com/frameworkium/integration/angularjs/pages/DeveloperGuidePage.java b/src/test/java/com/frameworkium/integration/angularjs/pages/DeveloperGuidePage.java index bf95a2ff..c05dd8f9 100755 --- a/src/test/java/com/frameworkium/integration/angularjs/pages/DeveloperGuidePage.java +++ b/src/test/java/com/frameworkium/integration/angularjs/pages/DeveloperGuidePage.java @@ -1,27 +1,24 @@ package com.frameworkium.integration.angularjs.pages; +import com.frameworkium.core.htmlelements.element.Link; +import com.frameworkium.core.htmlelements.element.TextInput; import com.frameworkium.core.ui.ExtraExpectedConditions; import com.frameworkium.core.ui.annotations.Visible; import com.frameworkium.core.ui.pages.BasePage; import com.frameworkium.core.ui.pages.PageFactory; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; -import ru.yandex.qatools.htmlelements.annotations.Name; -import ru.yandex.qatools.htmlelements.element.Link; -import ru.yandex.qatools.htmlelements.element.TextInput; + public class DeveloperGuidePage extends BasePage { - @Name("Developer guide search") @Visible @FindBy(css = "input[name='as_q']") private TextInput searchField; - @Name("Bootstrap search item") @FindBy(linkText = "Bootstrap") private Link bootstrapSearchItem; - @Name("Loading") @FindBy(id = "loading") private WebElement loading; diff --git a/src/test/java/com/frameworkium/integration/seleniumhq/components/HeaderComponent.java b/src/test/java/com/frameworkium/integration/seleniumhq/components/HeaderComponent.java index 3c88d5e2..603c727a 100644 --- a/src/test/java/com/frameworkium/integration/seleniumhq/components/HeaderComponent.java +++ b/src/test/java/com/frameworkium/integration/seleniumhq/components/HeaderComponent.java @@ -1,10 +1,11 @@ package com.frameworkium.integration.seleniumhq.components; +import com.frameworkium.core.htmlelements.element.HtmlElement; +import com.frameworkium.core.htmlelements.element.Link; import com.frameworkium.core.ui.pages.PageFactory; import com.frameworkium.integration.seleniumhq.pages.SeleniumDownloadPage; import org.openqa.selenium.support.FindBy; -import ru.yandex.qatools.htmlelements.element.HtmlElement; -import ru.yandex.qatools.htmlelements.element.Link; + @FindBy(id = "header") public class HeaderComponent extends HtmlElement { diff --git a/src/test/java/com/frameworkium/integration/seleniumhq/pages/SeleniumDownloadPage.java b/src/test/java/com/frameworkium/integration/seleniumhq/pages/SeleniumDownloadPage.java index ebba875d..19f5f8e4 100644 --- a/src/test/java/com/frameworkium/integration/seleniumhq/pages/SeleniumDownloadPage.java +++ b/src/test/java/com/frameworkium/integration/seleniumhq/pages/SeleniumDownloadPage.java @@ -1,12 +1,13 @@ package com.frameworkium.integration.seleniumhq.pages; +import com.frameworkium.core.htmlelements.element.Link; import com.frameworkium.core.ui.annotations.Visible; import com.frameworkium.core.ui.pages.BasePage; import com.frameworkium.core.ui.pages.PageFactory; import com.frameworkium.integration.seleniumhq.components.HeaderComponent; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.ui.ExpectedConditions; -import ru.yandex.qatools.htmlelements.element.Link; + import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOf; diff --git a/src/test/java/com/frameworkium/integration/theinternet/pages/CheckboxesPage.java b/src/test/java/com/frameworkium/integration/theinternet/pages/CheckboxesPage.java index 1de76354..a1d2aaeb 100644 --- a/src/test/java/com/frameworkium/integration/theinternet/pages/CheckboxesPage.java +++ b/src/test/java/com/frameworkium/integration/theinternet/pages/CheckboxesPage.java @@ -1,11 +1,11 @@ package com.frameworkium.integration.theinternet.pages; +import com.frameworkium.core.htmlelements.element.CheckBox; import com.frameworkium.core.ui.annotations.Visible; import com.frameworkium.core.ui.pages.BasePage; import io.qameta.allure.Step; import org.openqa.selenium.support.FindBy; -import ru.yandex.qatools.htmlelements.annotations.Name; -import ru.yandex.qatools.htmlelements.element.CheckBox; + import java.util.List; import java.util.stream.Stream; @@ -13,7 +13,6 @@ public class CheckboxesPage extends BasePage { @Visible(checkAtMost = 1) - @Name("All checkboxes") @FindBy(css = "form input[type='checkbox']") private List allCheckboxes; diff --git a/src/test/java/com/frameworkium/integration/theinternet/pages/DragAndDropPage.java b/src/test/java/com/frameworkium/integration/theinternet/pages/DragAndDropPage.java index b4494d6e..fc3edc49 100644 --- a/src/test/java/com/frameworkium/integration/theinternet/pages/DragAndDropPage.java +++ b/src/test/java/com/frameworkium/integration/theinternet/pages/DragAndDropPage.java @@ -7,7 +7,6 @@ import io.restassured.RestAssured; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; -import ru.yandex.qatools.htmlelements.annotations.Name; import java.util.List; import java.util.stream.Collectors; @@ -23,7 +22,6 @@ public class DragAndDropPage extends BasePage { private static final String JQUERY_JS_URI = "https://code.jquery.com/jquery-3.2.1.min.js"; @Visible - @Name("List of headers") @FindBy(css = "header") private List boxes; diff --git a/src/test/java/com/frameworkium/integration/theinternet/pages/DynamicLoadingExamplePage.java b/src/test/java/com/frameworkium/integration/theinternet/pages/DynamicLoadingExamplePage.java index dc5761e1..f792ea56 100644 --- a/src/test/java/com/frameworkium/integration/theinternet/pages/DynamicLoadingExamplePage.java +++ b/src/test/java/com/frameworkium/integration/theinternet/pages/DynamicLoadingExamplePage.java @@ -1,28 +1,25 @@ package com.frameworkium.integration.theinternet.pages; +import com.frameworkium.core.htmlelements.annotations.Timeout; +import com.frameworkium.core.htmlelements.element.Button; +import com.frameworkium.core.htmlelements.element.HtmlElement; import com.frameworkium.core.ui.annotations.Invisible; import com.frameworkium.core.ui.annotations.Visible; import com.frameworkium.core.ui.pages.BasePage; import com.frameworkium.core.ui.pages.PageFactory; import io.qameta.allure.Step; import org.openqa.selenium.support.FindBy; -import ru.yandex.qatools.htmlelements.annotations.Name; -import ru.yandex.qatools.htmlelements.annotations.Timeout; -import ru.yandex.qatools.htmlelements.element.Button; -import ru.yandex.qatools.htmlelements.element.HtmlElement; import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOf; public class DynamicLoadingExamplePage extends BasePage { @Visible - @Name("Start button") @FindBy(css = "#start button") private Button startButton; @Invisible @Timeout(0) // prevents page load taking 5s due to implicit timeout - @Name("Hidden element") @FindBy(id = "finish") private HtmlElement dynamicElement; diff --git a/src/test/java/com/frameworkium/integration/theinternet/pages/HoversPage.java b/src/test/java/com/frameworkium/integration/theinternet/pages/HoversPage.java index bc2851b2..f467b935 100644 --- a/src/test/java/com/frameworkium/integration/theinternet/pages/HoversPage.java +++ b/src/test/java/com/frameworkium/integration/theinternet/pages/HoversPage.java @@ -7,17 +7,14 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.support.FindBy; -import ru.yandex.qatools.htmlelements.annotations.Name; public class HoversPage extends BasePage { @Visible - @Name("First Figure") @FindBy(css = "div.figure:nth-of-type(1)") private WebElement firstFigure; @Invisible - @Name("First Figure Caption") @FindBy(css = "div.figure:nth-of-type(1) div.figcaption") private WebElement firstFigureCaption; diff --git a/src/test/java/com/frameworkium/integration/theinternet/pages/JavaScriptAlertsPage.java b/src/test/java/com/frameworkium/integration/theinternet/pages/JavaScriptAlertsPage.java index e65a76f1..bd641397 100644 --- a/src/test/java/com/frameworkium/integration/theinternet/pages/JavaScriptAlertsPage.java +++ b/src/test/java/com/frameworkium/integration/theinternet/pages/JavaScriptAlertsPage.java @@ -1,23 +1,21 @@ package com.frameworkium.integration.theinternet.pages; +import com.frameworkium.core.htmlelements.element.Button; import com.frameworkium.core.ui.annotations.Visible; import com.frameworkium.core.ui.pages.BasePage; import io.qameta.allure.Step; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; -import ru.yandex.qatools.htmlelements.annotations.Name; -import ru.yandex.qatools.htmlelements.element.Button; + import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOf; public class JavaScriptAlertsPage extends BasePage { @Visible - @Name("JS Alert button") @FindBy(css = "button[onclick='jsAlert()']") private Button jsAlertButton; - @Name("Result area") @FindBy(css = "p#result") private WebElement resultArea; diff --git a/src/test/java/com/frameworkium/integration/theinternet/pages/KeyPressesPage.java b/src/test/java/com/frameworkium/integration/theinternet/pages/KeyPressesPage.java index aa4ed3e5..23c8655c 100644 --- a/src/test/java/com/frameworkium/integration/theinternet/pages/KeyPressesPage.java +++ b/src/test/java/com/frameworkium/integration/theinternet/pages/KeyPressesPage.java @@ -7,7 +7,6 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.support.FindBy; -import ru.yandex.qatools.htmlelements.annotations.Name; public class KeyPressesPage extends BasePage { @@ -15,7 +14,6 @@ public class KeyPressesPage extends BasePage { @FindBy(css = "div.example") private WebElement container; - @Name("Result") @FindBy(css = "p#result") private WebElement result; diff --git a/src/test/java/com/frameworkium/integration/theinternet/pages/WelcomePage.java b/src/test/java/com/frameworkium/integration/theinternet/pages/WelcomePage.java index 42c424b7..d8190287 100644 --- a/src/test/java/com/frameworkium/integration/theinternet/pages/WelcomePage.java +++ b/src/test/java/com/frameworkium/integration/theinternet/pages/WelcomePage.java @@ -1,13 +1,12 @@ package com.frameworkium.integration.theinternet.pages; +import com.frameworkium.core.htmlelements.element.Link; import com.frameworkium.core.ui.annotations.ForceVisible; import com.frameworkium.core.ui.annotations.Visible; import com.frameworkium.core.ui.pages.BasePage; import com.frameworkium.core.ui.pages.PageFactory; import io.qameta.allure.Step; import org.openqa.selenium.support.FindBy; -import ru.yandex.qatools.htmlelements.annotations.Name; -import ru.yandex.qatools.htmlelements.element.Link; import java.time.Duration; @@ -16,29 +15,23 @@ public class WelcomePage extends BasePage { @Visible - @Name("Checkboxes link") @FindBy(linkText = "Checkboxes") private Link checkboxesLink; // ForceVisible not strictly required, just testing it doesn't error @ForceVisible - @Name("Drag and Drop link") @FindBy(linkText = "Drag and Drop") private Link dragAndDropLink; - @Name("Dynamic Loading link") @FindBy(linkText = "Dynamic Loading") private Link dynamicLoadingLink; - @Name("Hovers Link") @FindBy(linkText = "Hovers") private Link hoversLink; - @Name("JavaScript Alerts Link") @FindBy(linkText = "JavaScript Alerts") private Link javascriptAlertsLink; - @Name("Key Presses Link") @FindBy(linkText = "Key Presses") private Link keyPressesLink; diff --git a/src/test/java/com/frameworkium/integration/theinternet/tests/TheInternetExampleTests.java b/src/test/java/com/frameworkium/integration/theinternet/tests/TheInternetExampleTests.java index f77b1579..33ac59d7 100644 --- a/src/test/java/com/frameworkium/integration/theinternet/tests/TheInternetExampleTests.java +++ b/src/test/java/com/frameworkium/integration/theinternet/tests/TheInternetExampleTests.java @@ -77,7 +77,7 @@ public void javascriptAlerts() { .clickAlertButtonAndAccept(); assertThat(javascriptAlerts.getResultText()) - .isEqualTo("You successfuly clicked an alert"); + .isEqualTo("You successfully clicked an alert"); } @Issue("INT-12")