├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature_request.md ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── maven.yml │ └── release-draft.yml ├── .gitignore ├── .travis.yml.disable ├── LICENSE ├── README.md ├── doc └── style │ ├── intellij-java-style.xml │ └── style.xml ├── docker-compose-ci.yml ├── jitpack.yml ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── frameworkium │ │ └── core │ │ ├── api │ │ ├── Endpoint.java │ │ ├── dto │ │ │ └── AbstractDTO.java │ │ ├── services │ │ │ └── BaseService.java │ │ └── tests │ │ │ └── BaseAPITest.java │ │ ├── common │ │ ├── listeners │ │ │ ├── CreateJiraCycleListener.java │ │ │ ├── MethodInterceptor.java │ │ │ ├── ResultLoggerListener.java │ │ │ └── TestListener.java │ │ ├── properties │ │ │ └── Property.java │ │ ├── reporting │ │ │ ├── TestIdUtils.java │ │ │ ├── allure │ │ │ │ ├── AllureLogger.java │ │ │ │ └── AllureProperties.java │ │ │ ├── jira │ │ │ │ ├── JiraConfig.java │ │ │ │ ├── dto │ │ │ │ │ ├── attachment │ │ │ │ │ │ ├── AttachmentDto.java │ │ │ │ │ │ └── AttachmentListDto.java │ │ │ │ │ ├── cycle │ │ │ │ │ │ ├── CreateCycleSuccessDto.java │ │ │ │ │ │ ├── CreateNewCycleDto.java │ │ │ │ │ │ ├── CycleDto.java │ │ │ │ │ │ ├── CycleLightDto.java │ │ │ │ │ │ └── CycleListDto.java │ │ │ │ │ ├── execution │ │ │ │ │ │ ├── AddTestToCycleOperationDto.java │ │ │ │ │ │ ├── ExecutionDto.java │ │ │ │ │ │ ├── ExecutionLightDto.java │ │ │ │ │ │ └── UpdateExecutionOperationDto.java │ │ │ │ │ ├── executionsearch │ │ │ │ │ │ ├── ExecutionSearchDto.java │ │ │ │ │ │ └── ExecutionSearchListDto.java │ │ │ │ │ ├── project │ │ │ │ │ │ └── ProjectDto.java │ │ │ │ │ ├── status │ │ │ │ │ │ └── StatusDto.java │ │ │ │ │ └── version │ │ │ │ │ │ └── VersionDto.java │ │ │ │ ├── endpoint │ │ │ │ │ ├── JiraEndpoint.java │ │ │ │ │ └── ZephyrEndpoint.java │ │ │ │ ├── service │ │ │ │ │ ├── AbstractJiraService.java │ │ │ │ │ ├── Attachment.java │ │ │ │ │ ├── Issue.java │ │ │ │ │ ├── IssueLink.java │ │ │ │ │ ├── Project.java │ │ │ │ │ ├── Search.java │ │ │ │ │ └── Version.java │ │ │ │ ├── util │ │ │ │ │ ├── ExecutionSearchUtil.java │ │ │ │ │ └── ExecutionUtil.java │ │ │ │ └── zapi │ │ │ │ │ ├── Attachment.java │ │ │ │ │ ├── Cycle.java │ │ │ │ │ ├── Execution.java │ │ │ │ │ └── ExecutionSearch.java │ │ │ └── spira │ │ │ │ ├── SpiraConfig.java │ │ │ │ └── SpiraExecution.java │ │ └── retry │ │ │ └── RetryFlakyTest.java │ │ ├── htmlelements │ │ ├── annotations │ │ │ └── Timeout.java │ │ ├── element │ │ │ ├── Button.java │ │ │ ├── CheckBox.java │ │ │ ├── FileInput.java │ │ │ ├── Form.java │ │ │ ├── HtmlElement.java │ │ │ ├── Image.java │ │ │ ├── Link.java │ │ │ ├── Radio.java │ │ │ ├── Select.java │ │ │ ├── Table.java │ │ │ ├── TextBlock.java │ │ │ ├── TextInput.java │ │ │ └── TypifiedElement.java │ │ ├── exceptions │ │ │ └── HtmlElementsException.java │ │ ├── loader │ │ │ ├── HtmlElementLoader.java │ │ │ └── decorator │ │ │ │ ├── HtmlElementClassAnnotationsHandler.java │ │ │ │ ├── HtmlElementDecorator.java │ │ │ │ ├── HtmlElementFieldAnnotationsHandler.java │ │ │ │ ├── HtmlElementLocatorFactory.java │ │ │ │ ├── ProxyFactory.java │ │ │ │ └── proxyhandlers │ │ │ │ ├── HtmlElementListNamedProxyHandler.java │ │ │ │ ├── TypifiedElementListNamedProxyHandler.java │ │ │ │ ├── WebElementListNamedProxyHandler.java │ │ │ │ └── WebElementNamedProxyHandler.java │ │ ├── pagefactory │ │ │ └── CustomElementLocatorFactory.java │ │ └── utils │ │ │ └── HtmlElementUtils.java │ │ └── ui │ │ ├── ExtraExpectedConditions.java │ │ ├── UITestLifecycle.java │ │ ├── annotations │ │ ├── ForceVisible.java │ │ ├── Invisible.java │ │ └── Visible.java │ │ ├── browsers │ │ └── UserAgent.java │ │ ├── capture │ │ ├── CaptureEndpoint.java │ │ ├── ElementHighlighter.java │ │ ├── ScreenshotCapture.java │ │ └── model │ │ │ ├── Browser.java │ │ │ ├── Command.java │ │ │ ├── SoftwareUnderTest.java │ │ │ └── message │ │ │ ├── CreateExecution.java │ │ │ └── CreateScreenshot.java │ │ ├── driver │ │ ├── AbstractDriver.java │ │ ├── Driver.java │ │ ├── DriverSetup.java │ │ ├── drivers │ │ │ ├── BrowserStackImpl.java │ │ │ ├── ChromeImpl.java │ │ │ ├── EdgeImpl.java │ │ │ ├── FirefoxImpl.java │ │ │ ├── GridImpl.java │ │ │ ├── InternetExplorerImpl.java │ │ │ ├── SafariImpl.java │ │ │ └── SauceImpl.java │ │ ├── lifecycle │ │ │ ├── DriverLifecycle.java │ │ │ ├── MultiUseDriverLifecycle.java │ │ │ └── SingleUseDriverLifecycle.java │ │ └── remotes │ │ │ ├── BrowserStack.java │ │ │ └── Sauce.java │ │ ├── element │ │ ├── AbstractStreamTable.java │ │ ├── OptimisedStreamTable.java │ │ └── StreamTable.java │ │ ├── js │ │ └── JavascriptWait.java │ │ ├── listeners │ │ ├── CaptureListener.java │ │ ├── LoggingListener.java │ │ ├── SauceLabsListener.java │ │ ├── ScreenshotListener.java │ │ └── VideoListener.java │ │ ├── pages │ │ ├── BasePage.java │ │ ├── PageFactory.java │ │ └── Visibility.java │ │ ├── proxy │ │ └── SeleniumProxyFactory.java │ │ ├── tests │ │ └── BaseUITest.java │ │ └── video │ │ ├── UrlFetcher.java │ │ └── VideoCapture.java └── resources │ ├── Empty.properties │ ├── Empty.yaml │ ├── FirefoxGrid.yaml │ ├── META-INF │ └── services │ │ └── org.testng.ITestNGListener │ └── log4j2.xml └── test ├── groovy └── com │ └── frameworkium │ └── core │ ├── api │ └── dto │ │ └── AbstractDTOSpec.groovy │ ├── common │ ├── reporting │ │ ├── TestIdUtilsSpec.groovy │ │ └── jira │ │ │ ├── service │ │ │ ├── AttachmentSpec.groovy │ │ │ ├── IssueLinkSpec.groovy │ │ │ ├── IssueSpec.groovy │ │ │ ├── ProjectSpec.groovy │ │ │ ├── SearchSpec.groovy │ │ │ └── VersionSpec.groovy │ │ │ ├── util │ │ │ ├── ExecutionSearchUtilSpec.groovy │ │ │ └── ExecutionUtilSpec.groovy │ │ │ └── zapi │ │ │ ├── AttachmentSpec.groovy │ │ │ ├── CycleSpec.groovy │ │ │ ├── ExecutionSearchSpec.groovy │ │ │ └── ExecutionSpec.groovy │ └── retry │ │ └── RetryFlakyTestSpec.groovy │ └── ui │ ├── ExtraExpectedConditionsSpec.groovy │ ├── capture │ └── ElementHighlighterSpec.groovy │ ├── driver │ └── lifecycle │ │ ├── MultiUseDriverLifecycleSpec.groovy │ │ └── SingleUseDriverLifecycleSpec.groovy │ ├── listeners │ └── ScreenshotListenerSpec.groovy │ ├── pages │ ├── VisibilitySpec.groovy │ └── pageobjects │ │ └── PageObjects.groovy │ ├── proxy │ └── SeleniumProxyFactorySpec.groovy │ └── video │ └── VideoCaptureSpec.groovy ├── java └── com │ └── frameworkium │ ├── core │ ├── api │ │ └── dto │ │ │ ├── LowLevelDTO.java │ │ │ └── TopLevelDTO.java │ └── ui │ │ ├── ExtraExpectedConditionsTest.java │ │ └── pages │ │ └── pageobjects │ │ └── TestPage.java │ └── integration │ ├── CustomFirefoxImpl.java │ ├── angularjs │ ├── pages │ │ └── DeveloperGuidePage.java │ └── tests │ │ └── DocumentationTest.java │ ├── capture │ └── api │ │ ├── constant │ │ └── CaptureEndpoint.java │ │ ├── dto │ │ ├── executions │ │ │ ├── Browser.java │ │ │ ├── CreateExecution.java │ │ │ ├── ExecutionID.java │ │ │ ├── ExecutionResponse.java │ │ │ ├── ExecutionResults.java │ │ │ └── SoftwareUnderTest.java │ │ └── screenshots │ │ │ ├── Command.java │ │ │ ├── CreateScreenshot.java │ │ │ └── Screenshot.java │ │ ├── service │ │ ├── BaseCaptureService.java │ │ ├── executions │ │ │ └── ExecutionService.java │ │ └── screenshots │ │ │ └── ScreenshotService.java │ │ └── tests │ │ └── CaptureExecutionAPITest.java │ ├── frameworkium │ ├── pages │ │ └── JQueryDemoPage.java │ └── tests │ │ └── FrameworkiumBugsTest.java │ ├── restfulbooker │ └── api │ │ ├── constant │ │ └── BookerEndpoint.java │ │ ├── dto │ │ └── booking │ │ │ ├── Booking.java │ │ │ ├── BookingDates.java │ │ │ ├── BookingID.java │ │ │ ├── BookingResponse.java │ │ │ ├── CreateBookingResponse.java │ │ │ └── search │ │ │ └── SearchParamsMapper.java │ │ ├── service │ │ ├── AbstractBookerService.java │ │ ├── booking │ │ │ └── BookingService.java │ │ └── ping │ │ │ └── PingService.java │ │ └── tests │ │ ├── BookerTest.java │ │ └── SearchBookerTest.java │ ├── seleniumhq │ ├── components │ │ └── HeaderComponent.java │ ├── pages │ │ ├── HomePage.java │ │ └── SeleniumDownloadPage.java │ └── tests │ │ └── SeleniumTest.java │ ├── theinternet │ ├── pages │ │ ├── CheckboxesPage.java │ │ ├── DragAndDropPage.java │ │ ├── DynamicLoadingExamplePage.java │ │ ├── HoversPage.java │ │ ├── JavaScriptAlertsPage.java │ │ ├── KeyPressesPage.java │ │ ├── SortableDataTablesPage.java │ │ └── WelcomePage.java │ └── tests │ │ └── TheInternetExampleTests.java │ ├── wikipedia │ ├── pages │ │ ├── EnglishCountiesPage.java │ │ └── EnglishCountiesUsingListsPage.java │ └── tests │ │ └── EnglishCountiesTest.java │ └── wiremock │ ├── WireMockJiraSetup.java │ └── WireMockJiraTeardown.java └── resources └── capture-screenshot.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Handle line endings automatically for files detected as text 2 | # and leave all files detected as binary untouched. 3 | * text=auto 4 | 5 | # 6 | # The above will handle all files NOT found below 7 | # 8 | # These files are text and should be normalized (Convert crlf => lf) 9 | *.css text 10 | *.df text 11 | *.groovy text 12 | *.htm text 13 | *.html text 14 | *.java text 15 | *.js text 16 | *.json text 17 | *.jsp text 18 | *.jspf text 19 | *.jspx text 20 | *.md text 21 | *.properties text 22 | *.sh text 23 | *.tld text 24 | *.txt text 25 | *.tag text 26 | *.tagx text 27 | *.xml text 28 | *.yml text 29 | *.yaml text 30 | 31 | # These files are binary and should be left untouched 32 | # (binary is a macro for -text -diff) 33 | *.class binary 34 | *.dll binary 35 | *.ear binary 36 | *.gif binary 37 | *.ico binary 38 | *.jar binary 39 | *.jpg binary 40 | *.jpeg binary 41 | *.png binary 42 | *.so binary 43 | *.war binary 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug issue to help us improve 4 | title: "Bug issue title here" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the Issue** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. '...' 16 | 2. '....' 17 | 3. See error 18 | 19 | **Expected Behaviour** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Versions (please complete the following information):** 26 | - Frameworkium-core version: [e.g. 2.7.2] 27 | - OS: [e.g. Windows 10] 28 | - Browser [e.g. Chrome, Firefox] 29 | - Version [e.g. 59] 30 | 31 | **Additional Context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature/Enhancement request 3 | about: Suggest an idea or improvement for this project 4 | title: "Feature request title here" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "maven" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | 9 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: '$RESOLVED_VERSION' 2 | tag-template: '$RESOLVED_VERSION' 3 | categories: 4 | - title: 'New Features' 5 | labels: 6 | - 'feature request' 7 | - title: 'Improvements' 8 | labels: 9 | - 'enhancement' 10 | - title: 'Bug Fixes' 11 | labels: 12 | - 'bug' 13 | change-template: '* $TITLE (#$NUMBER) - @$AUTHOR' 14 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 15 | version-resolver: 16 | major: 17 | labels: 18 | - 'major' 19 | minor: 20 | labels: 21 | - 'minor' 22 | patch: 23 | labels: 24 | - 'patch' 25 | default: patch 26 | template: | 27 | ## Changes 28 | $CHANGES 29 | ### Notes 30 | - None 31 | -------------------------------------------------------------------------------- /.github/workflows/release-draft.yml: -------------------------------------------------------------------------------- 1 | name: Release Draft 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | update_draft_release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: toolmantim/release-drafter@v5.2.0 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.class 4 | *.exe 5 | 6 | # Packages # 7 | ############ 8 | # it's better to unpack these files and commit the raw source 9 | # git has its own built in compression methods 10 | *.7z 11 | *.dmg 12 | *.gz 13 | *.iso 14 | *.jar 15 | *.rar 16 | *.tar 17 | *.zip 18 | 19 | # Logs and databases # 20 | ###################### 21 | *.log 22 | 23 | # OS generated files # 24 | ###################### 25 | .DS_Store* 26 | ehthumbs.db 27 | Icon? 28 | Thumbs.db 29 | 30 | # Maven # 31 | ######### 32 | target/ 33 | 34 | # IDEs # 35 | ######## 36 | # IntelliJ # 37 | *.iml 38 | *.ipr 39 | *.iws 40 | .idea 41 | # Eclipse # 42 | .classpath 43 | .metadata/ 44 | .project 45 | .settings 46 | .checkstyle 47 | # NetBeans # 48 | nbactions.xml 49 | 50 | # Other things # 51 | ################ 52 | ajcore.*.txt 53 | test-output/ 54 | logs/ 55 | /screenshots/ 56 | capturedVideo/ 57 | .allure 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Frameworkium Core 2 | 3 | ![build](https://github.com/frameworkium/frameworkium-core/workflows/frameworkium%20build/badge.svg) 4 | ![release](https://img.shields.io/github/v/release/frameworkium/frameworkium-core) 5 | [![codecov.io][codecov-svg]][codecov] 6 | [![Maintainability][cc-badge]][codeclimate] 7 | 8 | A Framework for writing maintainable Selenium and REST API tests in Java. 9 | 10 | ## Release Notes 11 | 12 | See the [Frameworkium Release Notes][release-notes]. 13 | 14 | ## Usage 15 | 16 | To get started you can visit the [Frameworkium documentation][guidance]. 17 | 18 | For a "quick start" template and samples see the [Frameworkium examples project][frameworkium-eg]. 19 | 20 | [status-svg]: https://travis-ci.org/Frameworkium/frameworkium-core.svg?branch=master 21 | [status]: https://travis-ci.org/Frameworkium/frameworkium-core 22 | [codecov-svg]: https://codecov.io/gh/Frameworkium/frameworkium-core/branch/master/graph/badge.svg 23 | [codecov]: https://codecov.io/gh/Frameworkium/frameworkium-core 24 | [codeclimate]: https://codeclimate.com/github/Frameworkium/frameworkium-core/maintainability 25 | [cc-badge]: https://api.codeclimate.com/v1/badges/72b73eb5861ba89d9d6d/maintainability 26 | [release-notes]: https://github.com/Frameworkium/frameworkium-core/releases 27 | [frameworkium-eg]: https://github.com/Frameworkium/frameworkium-examples 28 | [guidance]: https://frameworkium.github.io 29 | -------------------------------------------------------------------------------- /docker-compose-ci.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | wiremock: 4 | image: rodolpheche/wiremock:2.27.2 5 | ports: 6 | - "8080:8080" 7 | command: [ 'rodolpheche/wiremock', '--port 8080', '--verbose', '--record-mappings' ] 8 | volumes: 9 | - "./src/test/resources/mappings:/home/wiremock" 10 | healthcheck: 11 | test: [ "CMD", "curl", "-f", "http://localhost", "||", "exit 1" ] 12 | interval: 30s 13 | timeout: 10s 14 | retries: 3 15 | start_period: 3s 16 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk11 3 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/api/Endpoint.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.api; 2 | 3 | /** 4 | * Intended for an enum to store the various endpoints of your API under test. 5 | * 6 | *

The following is an example implementation: 7 | *

{@code
 8 |  * public enum MyEndpoint implements Endpoint {
 9 |  *
10 |  *     BASE_URI("https://xxx),
11 |  *     YYY_ID("/yyy/%d");
12 |  *
13 |  *     private String url;
14 |  *
15 |  *     MyEndpoint(String url) {
16 |  *         this.url = url;
17 |  *     }
18 |  *
19 |  *     public String getUrl(Object... params) {
20 |  *         return String.format(url, params);
21 |  *     }
22 |  *  }}
23 | * 24 | *

The key feature is an enum entry for each endpoint where the url String can 25 | * contain a {@code String.format} to enable easy injection of parameters. 26 | */ 27 | public interface Endpoint { 28 | 29 | /** 30 | * Calls {@link String#format(String, Object...)} on the url. 31 | * 32 | * @param params Arguments referenced by the format specifiers in the url. 33 | * @return A formatted String representing the URL of the given constant. 34 | */ 35 | String getUrl(Object... params); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/api/dto/AbstractDTO.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.api.dto; 2 | 3 | import java.io.Serializable; 4 | import org.apache.commons.lang3.SerializationUtils; 5 | import org.apache.commons.lang3.builder.EqualsBuilder; 6 | import org.apache.commons.lang3.builder.HashCodeBuilder; 7 | import org.apache.commons.lang3.builder.ReflectionToStringBuilder; 8 | import org.apache.commons.lang3.builder.ToStringStyle; 9 | 10 | public abstract class AbstractDTO implements Serializable, Cloneable { 11 | 12 | @Override 13 | public int hashCode() { 14 | return HashCodeBuilder.reflectionHashCode(this); 15 | } 16 | 17 | @Override 18 | public boolean equals(Object obj) { 19 | return EqualsBuilder.reflectionEquals(this, obj); 20 | } 21 | 22 | @Override 23 | @SuppressWarnings("unchecked") 24 | protected T clone() throws CloneNotSupportedException { 25 | try { 26 | return (T) SerializationUtils.clone(this); 27 | } catch (Exception e) { 28 | throw new CloneNotSupportedException(e.getMessage()); 29 | } 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return ReflectionToStringBuilder.toString( 35 | this, ToStringStyle.SHORT_PREFIX_STYLE); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/api/tests/BaseAPITest.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.api.tests; 2 | 3 | import com.frameworkium.core.common.reporting.allure.AllureProperties; 4 | import org.apache.logging.log4j.LogManager; 5 | import org.apache.logging.log4j.Logger; 6 | import org.testng.annotations.AfterSuite; 7 | import org.testng.annotations.Test; 8 | 9 | // Uses the listeners in main.resources.META-INF.services.org.testng.ITestNGListener 10 | @Test(groups = "base-api") 11 | public abstract class BaseAPITest { 12 | 13 | protected final Logger logger = LogManager.getLogger(this); 14 | 15 | /** 16 | * Creates the allure properties for the report, after the test run. 17 | */ 18 | @AfterSuite(alwaysRun = true) 19 | public static void createAllureProperties() { 20 | AllureProperties.createAPI(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/listeners/MethodInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.listeners; 2 | 3 | import static com.frameworkium.core.common.properties.Property.JIRA_URL; 4 | import static com.frameworkium.core.common.properties.Property.JQL_QUERY; 5 | import static java.util.stream.Collectors.joining; 6 | import static java.util.stream.Collectors.toList; 7 | 8 | import com.frameworkium.core.common.reporting.TestIdUtils; 9 | import com.frameworkium.core.common.reporting.jira.service.Search; 10 | import java.lang.reflect.Method; 11 | import java.util.List; 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.testng.IMethodInstance; 15 | import org.testng.IMethodInterceptor; 16 | import org.testng.ITestContext; 17 | 18 | public class MethodInterceptor implements IMethodInterceptor { 19 | 20 | private static final Logger logger = LogManager.getLogger(); 21 | 22 | @Override 23 | public List intercept( 24 | List methods, ITestContext context) { 25 | 26 | return filterTestsToRunByJQL(methods); 27 | } 28 | 29 | private List filterTestsToRunByJQL( 30 | List methodsToBeFiltered) { 31 | 32 | if (!(JQL_QUERY.isSpecified() && JIRA_URL.isSpecified())) { 33 | // Can't run the JQL without both JIRA_URL and JQL_QUERY 34 | return methodsToBeFiltered; 35 | } 36 | 37 | logger.info("Filtering specified tests to run with JQL query results"); 38 | 39 | List testIDsFromJQL = 40 | new Search(JQL_QUERY.getValue()).getKeys(); 41 | 42 | List methodsToRun = methodsToBeFiltered.stream() 43 | .filter(m -> TestIdUtils.getIssueOrTmsLinkValue(m).isPresent()) 44 | .filter(m -> testIDsFromJQL.contains( 45 | TestIdUtils.getIssueOrTmsLinkValue(m).orElseThrow(IllegalStateException::new))) 46 | .collect(toList()); 47 | 48 | logTestMethodInformation(methodsToRun); 49 | 50 | return methodsToRun; 51 | } 52 | 53 | private void logTestMethodInformation(List methodsToRun) { 54 | 55 | logger.debug("Running the following test methods:\n{}", () -> 56 | methodsToRun.stream() 57 | .map(m -> getMethodFromIMethod(m).getName()) 58 | .collect(joining("\n"))); 59 | 60 | logger.info("Running {} tests specified by JQL query", methodsToRun.size()); 61 | } 62 | 63 | private Method getMethodFromIMethod(IMethodInstance iMethod) { 64 | return iMethod.getMethod().getConstructorOrMethod().getMethod(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/listeners/TestListener.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.listeners; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.apache.logging.log4j.Logger; 5 | import org.testng.ITestContext; 6 | import org.testng.ITestListener; 7 | import org.testng.ITestResult; 8 | import org.testng.SkipException; 9 | 10 | public class TestListener implements ITestListener { 11 | 12 | private final Logger logger = LogManager.getLogger(); 13 | 14 | @Override 15 | public void onTestStart(ITestResult result) { 16 | logger.info("START {}", getTestIdentifier(result)); 17 | } 18 | 19 | @Override 20 | public void onTestSuccess(ITestResult result) { 21 | logger.info("PASS {}", getTestIdentifier(result)); 22 | } 23 | 24 | @Override 25 | public void onTestFailure(ITestResult result) { 26 | logger.error("FAIL {}", getTestIdentifier(result)); 27 | Throwable cause = result.getThrowable(); 28 | if (cause != null) { 29 | logger.error(cause.getMessage(), cause); 30 | } 31 | } 32 | 33 | @Override 34 | public void onTestSkipped(ITestResult result) { 35 | logger.warn("SKIP {}", getTestIdentifier(result)); 36 | Throwable cause = result.getThrowable(); 37 | if (cause != null && SkipException.class.isAssignableFrom(cause.getClass())) { 38 | logger.warn(cause.getMessage()); 39 | } 40 | } 41 | 42 | private String getTestIdentifier(ITestResult result) { 43 | return String.format("%s.%s", 44 | result.getInstanceName(), 45 | result.getMethod().getMethodName()); 46 | } 47 | 48 | @Override 49 | public void onTestFailedButWithinSuccessPercentage(ITestResult result) { 50 | } 51 | 52 | @Override 53 | public void onStart(ITestContext context) { 54 | } 55 | 56 | @Override 57 | public void onFinish(ITestContext context) { 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/allure/AllureLogger.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.allure; 2 | 3 | import static io.qameta.allure.Allure.getLifecycle; 4 | 5 | import io.qameta.allure.Step; 6 | import io.qameta.allure.model.StepResult; 7 | import java.util.ArrayDeque; 8 | import java.util.Deque; 9 | import java.util.UUID; 10 | import org.apache.logging.log4j.LogManager; 11 | import org.apache.logging.log4j.Logger; 12 | 13 | public class AllureLogger { 14 | 15 | private static final Logger logger = LogManager.getLogger(); 16 | private static final ThreadLocal> STEP_UUID_STACK = 17 | ThreadLocal.withInitial(ArrayDeque::new); 18 | 19 | private AllureLogger() { 20 | // hide default constructor for this util class 21 | } 22 | 23 | /** 24 | * Uses the @Step annotation to log the given log message to Allure. 25 | * 26 | * @param message the message to log to the allure report 27 | */ 28 | @Step("{message}") 29 | public static void logToAllure(String message) { 30 | logger.debug("Logged to allure: " + message); 31 | } 32 | 33 | public static void stepStart(String stepName) { 34 | StepResult result = new StepResult().setName(stepName); 35 | String uuid = UUID.randomUUID().toString(); 36 | getLifecycle().startStep(uuid, result); 37 | STEP_UUID_STACK.get().addFirst(uuid); 38 | } 39 | 40 | public static void stepFinish() { 41 | getLifecycle().stopStep(STEP_UUID_STACK.get().removeFirst()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/JiraConfig.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira; 2 | 3 | public class JiraConfig { 4 | private JiraConfig() { 5 | // hide default constructor for this util class 6 | } 7 | 8 | /** 9 | * These should correspond to your ZAPI result IDs and 10 | * are only used if logging to Zephyr for JIRA. 11 | */ 12 | public static class ZapiStatus { 13 | 14 | public static final int ZAPI_STATUS_PASS = 1; 15 | public static final int ZAPI_STATUS_FAIL = 2; 16 | public static final int ZAPI_STATUS_WIP = 3; 17 | public static final int ZAPI_STATUS_BLOCKED = 4; 18 | } 19 | 20 | /** 21 | * These should correspond to your field options 22 | * if logging a test result to a field. 23 | */ 24 | public static class JiraFieldStatus { 25 | public static final String JIRA_STATUS_PASS = "Pass"; 26 | public static final String JIRA_STATUS_FAIL = "Fail"; 27 | public static final String JIRA_STATUS_WIP = "WIP"; 28 | public static final String JIRA_STATUS_BLOCKED = "Blocked"; 29 | } 30 | 31 | /** 32 | * These should correspond to the workflow transition names required to mark 33 | * the result if using a customised jira issue type & workflow to manage 34 | * tests NB - put all required transitions to get between statuses 35 | * (e.g. restart, then mark result) - each will be tried & ignored if not possible 36 | */ 37 | public static class JiraTransition { 38 | public static final String[] JIRA_TRANSITION_PASS = {"Done"}; 39 | public static final String[] JIRA_TRANSITION_FAIL = {"Done"}; 40 | public static final String[] JIRA_TRANSITION_WIP = {"Reopen", "Start Progress"}; 41 | public static final String[] JIRA_TRANSITION_BLOCKED = {"Done"}; 42 | } 43 | 44 | /** 45 | * These should correspond to your field options 46 | * if logging a test result to a field. 47 | */ 48 | public static class SpiraStatus { 49 | public static final int SPIRA_STATUS_PASS = 2; 50 | public static final int SPIRA_STATUS_FAIL = 1; 51 | public static final int SPIRA_STATUS_WIP = 4; 52 | public static final int SPIRA_STATUS_BLOCKED = 5; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/dto/attachment/AttachmentDto.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.dto.attachment; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | 5 | public class AttachmentDto extends AbstractDTO { 6 | public String fileName; 7 | public String dateCreated; 8 | public String fileSize; 9 | public String fileIcon; 10 | public String author; 11 | public String fileIconAltText; 12 | public String comment; 13 | public String fileId; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/dto/attachment/AttachmentListDto.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.dto.attachment; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | import java.util.List; 5 | 6 | public class AttachmentListDto extends AbstractDTO { 7 | public List data; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/dto/cycle/CreateCycleSuccessDto.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.dto.cycle; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | 5 | public class CreateCycleSuccessDto extends AbstractDTO { 6 | public String id; 7 | public String responseMessage; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/dto/cycle/CreateNewCycleDto.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.dto.cycle; 2 | 3 | import com.fasterxml.jackson.annotation.JsonUnwrapped; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; 6 | import com.frameworkium.core.api.dto.AbstractDTO; 7 | 8 | @JsonDeserialize(builder = CreateNewCycleDto.Builder.class) 9 | public class CreateNewCycleDto extends AbstractDTO { 10 | @JsonUnwrapped 11 | public CycleLightDto cycleLightDto; 12 | public String cloneCycleId; 13 | public String sprintId; 14 | 15 | private CreateNewCycleDto(final Builder builder) { 16 | cycleLightDto = builder.cycleLightDto; 17 | cloneCycleId = builder.cloneCycleId; 18 | sprintId = builder.sprintId; 19 | } 20 | 21 | public static Builder newBuilder() { 22 | return new Builder(); 23 | } 24 | 25 | @JsonPOJOBuilder(withPrefix = "") 26 | public static final class Builder { 27 | @JsonUnwrapped 28 | private CycleLightDto cycleLightDto; 29 | private String cloneCycleId; 30 | private String sprintId; 31 | 32 | private Builder() { 33 | } 34 | 35 | public Builder cycleLightDto(final CycleLightDto cycleLightDto) { 36 | this.cycleLightDto = cycleLightDto; 37 | return this; 38 | } 39 | 40 | public Builder cloneCycleId(final String cloneCycleId) { 41 | this.cloneCycleId = cloneCycleId; 42 | return this; 43 | } 44 | 45 | public Builder sprintId(final String sprintId) { 46 | this.sprintId = sprintId; 47 | return this; 48 | } 49 | 50 | public CreateNewCycleDto build() { 51 | return new CreateNewCycleDto(this); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/dto/cycle/CycleLightDto.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.dto.cycle; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; 6 | import com.frameworkium.core.api.dto.AbstractDTO; 7 | import java.time.LocalDate; 8 | 9 | @JsonDeserialize(builder = CycleLightDto.Builder.class) 10 | public class CycleLightDto extends AbstractDTO { 11 | public String name; 12 | public String build; 13 | public String environment; 14 | public String description; 15 | public LocalDate startDate; 16 | public LocalDate endDate; 17 | public Long projectId; 18 | public Long versionId; 19 | 20 | private CycleLightDto(final Builder builder) { 21 | name = builder.name; 22 | build = builder.build; 23 | environment = builder.environment; 24 | description = builder.description; 25 | startDate = builder.startDate; 26 | endDate = builder.endDate; 27 | projectId = builder.projectId; 28 | versionId = builder.versionId; 29 | } 30 | 31 | public static Builder newBuilder() { 32 | return new Builder(); 33 | } 34 | 35 | @JsonPOJOBuilder(withPrefix = "") 36 | public static final class Builder { 37 | private String name; 38 | private String build; 39 | private String environment; 40 | private String description; 41 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "d/MMM/yy", 42 | with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_VALUES) 43 | private LocalDate startDate; 44 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "d/MMM/yy", 45 | with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_VALUES) 46 | private LocalDate endDate; 47 | private Long projectId; 48 | private Long versionId; 49 | 50 | private Builder() { 51 | } 52 | 53 | public Builder name(final String name) { 54 | this.name = name; 55 | return this; 56 | } 57 | 58 | public Builder environment(final String environment) { 59 | this.environment = environment; 60 | return this; 61 | } 62 | 63 | public Builder description(final String description) { 64 | this.description = description; 65 | return this; 66 | } 67 | 68 | public Builder startDate(final LocalDate startDate) { 69 | this.startDate = startDate; 70 | return this; 71 | } 72 | 73 | public Builder endDate(final LocalDate endDate) { 74 | this.endDate = endDate; 75 | return this; 76 | } 77 | 78 | public Builder projectId(final Long projectId) { 79 | this.projectId = projectId; 80 | return this; 81 | } 82 | 83 | public Builder versionId(final Long versionId) { 84 | this.versionId = versionId; 85 | return this; 86 | } 87 | 88 | public CycleLightDto build() { 89 | return new CycleLightDto(this); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/dto/cycle/CycleListDto.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.dto.cycle; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnyGetter; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | import com.fasterxml.jackson.annotation.JsonCreator; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import com.frameworkium.core.api.dto.AbstractDTO; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class CycleListDto extends AbstractDTO { 12 | public Map map = new HashMap<>(); 13 | public Long recordsCount; 14 | 15 | @JsonCreator 16 | public CycleListDto(@JsonProperty("recordsCount") Long recordsCount) { 17 | this.recordsCount = recordsCount; 18 | } 19 | 20 | @JsonAnySetter 21 | public void setMap(String key, CycleDto cycleDto) { 22 | map.put(key, cycleDto); 23 | } 24 | 25 | @JsonAnyGetter 26 | public Map getMap() { 27 | return map; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/dto/execution/AddTestToCycleOperationDto.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.dto.execution; 2 | 3 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 4 | import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; 5 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 6 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 7 | import com.frameworkium.core.api.dto.AbstractDTO; 8 | import java.util.List; 9 | 10 | @JsonDeserialize(builder = AddTestToCycleOperationDto.Builder.class) 11 | public class AddTestToCycleOperationDto extends AbstractDTO { 12 | public String cycleId; 13 | public List issues; 14 | public String searchId; 15 | public String method; 16 | @JsonSerialize(using = ToStringSerializer.class) 17 | public Long projectId; 18 | @JsonSerialize(using = ToStringSerializer.class) 19 | public Long versionId; 20 | 21 | 22 | private AddTestToCycleOperationDto(final Builder builder) { 23 | cycleId = builder.cycleId; 24 | issues = builder.issues; 25 | searchId = builder.searchId; 26 | method = builder.method; 27 | projectId = builder.projectId; 28 | versionId = builder.versionId; 29 | } 30 | 31 | public static Builder newBuilder() { 32 | return new Builder(); 33 | } 34 | 35 | @JsonPOJOBuilder(withPrefix = "") 36 | public static final class Builder { 37 | private String cycleId; 38 | private List issues; 39 | private String searchId; 40 | private String method; 41 | private Long projectId; 42 | private Long versionId; 43 | 44 | private Builder() { 45 | } 46 | 47 | public Builder cycleId(final String cycleId) { 48 | this.cycleId = cycleId; 49 | return this; 50 | } 51 | 52 | public Builder issues(final List issues) { 53 | this.issues = issues; 54 | return this; 55 | } 56 | 57 | public Builder searchId(final String searchId) { 58 | this.searchId = searchId; 59 | return this; 60 | } 61 | 62 | public Builder method(final String method) { 63 | this.method = method; 64 | return this; 65 | } 66 | 67 | public Builder projectId(final Long projectId) { 68 | this.projectId = projectId; 69 | return this; 70 | } 71 | 72 | public Builder versionId(final Long versionId) { 73 | this.versionId = versionId; 74 | return this; 75 | } 76 | 77 | public AddTestToCycleOperationDto build() { 78 | return new AddTestToCycleOperationDto(this); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/dto/execution/ExecutionDto.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.dto.execution; 2 | 3 | import com.fasterxml.jackson.annotation.JsonUnwrapped; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; 6 | import com.frameworkium.core.api.dto.AbstractDTO; 7 | 8 | @JsonDeserialize(builder = ExecutionDto.Builder.class) 9 | public class ExecutionDto extends AbstractDTO { 10 | @JsonUnwrapped 11 | public ExecutionLightDto executionLightDto; 12 | public String executionStatus; 13 | public String createdBy; 14 | public String modifiedBy; 15 | public Long issueId; 16 | public String summary; 17 | public String label; 18 | public String component; 19 | 20 | private ExecutionDto(Builder builder) { 21 | this.executionLightDto = builder.executionLightDto; 22 | this.executionStatus = builder.executionStatus; 23 | this.createdBy = builder.createdBy; 24 | this.modifiedBy = builder.modifiedBy; 25 | this.issueId = builder.issueId; 26 | this.summary = builder.summary; 27 | this.label = builder.label; 28 | this.component = builder.component; 29 | } 30 | 31 | public static ExecutionDto.Builder newBuilder() { 32 | return new ExecutionDto.Builder(); 33 | } 34 | 35 | @JsonPOJOBuilder(withPrefix = "") 36 | public static final class Builder { 37 | private ExecutionLightDto executionLightDto; 38 | private String executionStatus; 39 | private String createdBy; 40 | private String modifiedBy; 41 | private Long issueId; 42 | private String summary; 43 | private String label; 44 | private String component; 45 | 46 | private Builder() { 47 | } 48 | 49 | public Builder executionLightDto(ExecutionLightDto executionLightDto) { 50 | this.executionLightDto = executionLightDto; 51 | return this; 52 | } 53 | 54 | public Builder executionStatus(String executionStatus) { 55 | this.executionStatus = executionStatus; 56 | return this; 57 | } 58 | 59 | public Builder createdBy(String createdBy) { 60 | this.createdBy = createdBy; 61 | return this; 62 | } 63 | 64 | public Builder modifiedBy(String modifiedBy) { 65 | this.modifiedBy = modifiedBy; 66 | return this; 67 | } 68 | 69 | public Builder issueId(Long issueId) { 70 | this.issueId = issueId; 71 | return this; 72 | } 73 | 74 | public Builder summary(String summary) { 75 | this.summary = summary; 76 | return this; 77 | } 78 | 79 | public Builder label(String label) { 80 | this.label = label; 81 | return this; 82 | } 83 | 84 | public Builder component(String component) { 85 | this.component = component; 86 | return this; 87 | } 88 | 89 | public ExecutionDto build() { 90 | return new ExecutionDto(this); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/dto/execution/UpdateExecutionOperationDto.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.dto.execution; 2 | 3 | import com.fasterxml.jackson.annotation.JsonUnwrapped; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; 6 | import com.frameworkium.core.api.dto.AbstractDTO; 7 | 8 | @JsonDeserialize(builder = UpdateExecutionOperationDto.Builder.class) 9 | public class UpdateExecutionOperationDto extends AbstractDTO { 10 | @JsonUnwrapped 11 | public ExecutionDto executionDto; 12 | public String executedOn; 13 | public String executionBy; 14 | public String executedByDisplay; 15 | public Integer status; 16 | 17 | private UpdateExecutionOperationDto(Builder builder) { 18 | this.executionBy = builder.executionBy; 19 | this.executionDto = builder.executionDto; 20 | this.executedOn = builder.executedOn; 21 | this.executedByDisplay = builder.executedByDisplay; 22 | this.status = builder.status; 23 | } 24 | 25 | public static UpdateExecutionOperationDto.Builder newBuilder() { 26 | return new UpdateExecutionOperationDto.Builder(); 27 | } 28 | 29 | @JsonPOJOBuilder(withPrefix = "") 30 | public static final class Builder { 31 | private ExecutionDto executionDto; 32 | private String executedOn; 33 | private String executionBy; 34 | private String executedByDisplay; 35 | private Integer status; 36 | 37 | private Builder() { 38 | } 39 | 40 | public Builder executionDto(ExecutionDto executionDto) { 41 | this.executionDto = executionDto; 42 | return this; 43 | } 44 | 45 | public Builder executedOn(String executedOn) { 46 | this.executedOn = executedOn; 47 | return this; 48 | } 49 | 50 | public Builder executionBy(String executionBy) { 51 | this.executionBy = executionBy; 52 | return this; 53 | } 54 | 55 | public Builder executedByDisplay(String executedByDisplay) { 56 | this.executedByDisplay = executedByDisplay; 57 | return this; 58 | } 59 | 60 | public Builder status(Integer status) { 61 | this.status = status; 62 | return this; 63 | } 64 | 65 | public UpdateExecutionOperationDto build() { 66 | return new UpdateExecutionOperationDto(this); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/dto/executionsearch/ExecutionSearchDto.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.dto.executionsearch; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import com.fasterxml.jackson.annotation.JsonUnwrapped; 5 | import com.frameworkium.core.api.dto.AbstractDTO; 6 | import com.frameworkium.core.common.reporting.jira.dto.execution.ExecutionLightDto; 7 | import com.frameworkium.core.common.reporting.jira.dto.status.StatusDto; 8 | import java.time.LocalDate; 9 | import java.util.List; 10 | 11 | public class ExecutionSearchDto extends AbstractDTO { 12 | @JsonUnwrapped 13 | public ExecutionLightDto executionLightDto; 14 | public String issueId; 15 | public String issueSummary; 16 | public List labels; 17 | public String issueDescription; 18 | public String project; 19 | public Long projectAvatarId; 20 | public String priority; 21 | public List components; 22 | public StatusDto status; 23 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yy/MMM/dd") 24 | public LocalDate executedOn; 25 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yy/MMM/dd") 26 | public LocalDate creationDate; 27 | public String executedBy; 28 | public String executedByUserName; 29 | public List executionDefects; 30 | public List stepDefects; 31 | public Long executionDefectCount; 32 | public Long stepDefectCount; 33 | public Long totalDefectCount; 34 | public String executedByDisplay; 35 | public String assignee; 36 | public String assigneeUserName; 37 | public String assigneeDisplay; 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/dto/executionsearch/ExecutionSearchListDto.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.dto.executionsearch; 2 | 3 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 4 | import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; 5 | import com.frameworkium.core.api.dto.AbstractDTO; 6 | import java.util.List; 7 | 8 | @JsonDeserialize(builder = ExecutionSearchListDto.Builder.class) 9 | public class ExecutionSearchListDto extends AbstractDTO { 10 | public List executions; 11 | public Long currentIndex; 12 | public Long maxResultAllowed; 13 | public List linksNew; 14 | public Long totalCount; 15 | public List executionIds; 16 | 17 | public ExecutionSearchListDto(Builder builder) { 18 | this.executions = builder.executions; 19 | this.currentIndex = builder.currentIndex; 20 | this.maxResultAllowed = builder.maxResultAllowed; 21 | this.linksNew = builder.linksNew; 22 | this.totalCount = builder.totalCount; 23 | this.executionIds = builder.executionIds; 24 | } 25 | 26 | public static ExecutionSearchListDto.Builder newBuilder() { 27 | return new ExecutionSearchListDto.Builder(); 28 | } 29 | 30 | @JsonPOJOBuilder(withPrefix = "") 31 | public static final class Builder { 32 | private List executions; 33 | private Long currentIndex; 34 | private Long maxResultAllowed; 35 | private List linksNew; 36 | private Long totalCount; 37 | private List executionIds; 38 | 39 | public Builder executions(final List executions) { 40 | this.executions = executions; 41 | return this; 42 | } 43 | 44 | public Builder currentIndex(final Long currentIndex) { 45 | this.currentIndex = currentIndex; 46 | return this; 47 | } 48 | 49 | public Builder maxResultAllowed(final Long maxResultAllowed) { 50 | this.maxResultAllowed = maxResultAllowed; 51 | return this; 52 | } 53 | 54 | public Builder linksNew(final List linksNew) { 55 | this.linksNew = linksNew; 56 | return this; 57 | } 58 | 59 | public Builder totalCount(final Long totalCount) { 60 | this.totalCount = totalCount; 61 | return this; 62 | } 63 | 64 | public Builder executionIds(final List executionIds) { 65 | this.executionIds = executionIds; 66 | return this; 67 | } 68 | 69 | public ExecutionSearchListDto build() { 70 | return new ExecutionSearchListDto(this); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/dto/project/ProjectDto.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.dto.project; 2 | 3 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 4 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 5 | import com.frameworkium.core.api.dto.AbstractDTO; 6 | 7 | public class ProjectDto extends AbstractDTO { 8 | @JsonSerialize(using = ToStringSerializer.class) 9 | public Long id; 10 | public String key; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/dto/status/StatusDto.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.dto.status; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | 5 | public class StatusDto extends AbstractDTO { 6 | public Long id; 7 | public String name; 8 | public String description; 9 | public String color; 10 | public Long type; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/endpoint/JiraEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.endpoint; 2 | 3 | import com.frameworkium.core.api.Endpoint; 4 | import com.frameworkium.core.common.properties.Property; 5 | 6 | public enum JiraEndpoint implements Endpoint { 7 | BASE_URI(Property.JIRA_URL.getValue()), 8 | REST_API_PATH("/rest/api/2"), 9 | PROJECT("/project"), 10 | SEARCH("/search"), 11 | ISSUE("/issue"), 12 | ISSUELINK("/issueLink"), 13 | FIELD("/field"), 14 | VERSION("/version"); 15 | 16 | private final String url; 17 | 18 | JiraEndpoint(String url) { 19 | this.url = url; 20 | } 21 | 22 | /** 23 | * @param params Arguments referenced by the format specifiers in the url. 24 | * @return A formatted String representing the URL of the given constant. 25 | */ 26 | @Override 27 | public String getUrl(Object... params) { 28 | if (url.equals(REST_API_PATH.url) || url.equals(BASE_URI.url)) { 29 | return String.format(url, params); 30 | } 31 | // returns with the rest API path e.g. /rest/api/2/issue 32 | String urlWithRestAPIPath = REST_API_PATH.url + url; 33 | return String.format(urlWithRestAPIPath, params); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/endpoint/ZephyrEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.endpoint; 2 | 3 | import com.frameworkium.core.api.Endpoint; 4 | import com.frameworkium.core.common.properties.Property; 5 | 6 | public enum ZephyrEndpoint implements Endpoint { 7 | BASE_URI(Property.JIRA_URL.getValue()), 8 | REST_API_PATH("/rest/zapi/latest"), 9 | EXECUTION("/execution"), 10 | ADD_TEST_TO_EXECUTION("/execution/addTestsToCycle"), 11 | EXECUTE_SEARCH("/zql/executeSearch"), 12 | CYCLE("/cycle"), 13 | ATTACHMENT("/attachment"), 14 | ATTACHMENT_BY_ENTITY("/attachment/attachmentsByEntity"); 15 | 16 | private final String url; 17 | 18 | ZephyrEndpoint(final String url) { 19 | this.url = url; 20 | } 21 | 22 | 23 | /** 24 | * @param params Arguments referenced by the format specifiers in the url. 25 | * @return A formatted String representing the URL of the given constant. 26 | */ 27 | @Override 28 | public String getUrl(Object... params) { 29 | if (url.equals(REST_API_PATH.url) || url.equals(BASE_URI.url)) { 30 | return url; 31 | } 32 | // returns with the rest API path e.g. /rest/api/2/issue 33 | String urlWithRestAPIPath = REST_API_PATH.url + url; 34 | return String.format(urlWithRestAPIPath, params); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/service/AbstractJiraService.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.service; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | import com.fasterxml.jackson.annotation.PropertyAccessor; 6 | import com.fasterxml.jackson.databind.DeserializationFeature; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 9 | import com.frameworkium.core.api.services.BaseService; 10 | import com.frameworkium.core.common.properties.Property; 11 | import com.frameworkium.core.common.reporting.jira.endpoint.JiraEndpoint; 12 | import io.restassured.RestAssured; 13 | import io.restassured.config.ObjectMapperConfig; 14 | import io.restassured.config.RestAssuredConfig; 15 | import io.restassured.specification.RequestSpecification; 16 | import io.restassured.specification.ResponseSpecification; 17 | 18 | public abstract class AbstractJiraService extends BaseService { 19 | @Override 20 | protected RequestSpecification getRequestSpec() { 21 | return RestAssured.given().log().ifValidationFails() 22 | .baseUri(JiraEndpoint.BASE_URI.getUrl()) 23 | .config(config()) 24 | .relaxedHTTPSValidation() 25 | .auth().preemptive().basic( 26 | Property.JIRA_USERNAME.getValue(), 27 | Property.JIRA_PASSWORD.getValue()); 28 | } 29 | 30 | @Override 31 | protected ResponseSpecification getResponseSpec() { 32 | throw new UnsupportedOperationException("Unimplemented"); 33 | } 34 | 35 | private RestAssuredConfig config() { 36 | return RestAssuredConfig.config().objectMapperConfig( 37 | ObjectMapperConfig.objectMapperConfig().jackson2ObjectMapperFactory( 38 | (type, s) -> { 39 | final ObjectMapper objectMapper = new ObjectMapper(); 40 | objectMapper.registerModule(new JavaTimeModule()); 41 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 42 | objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE); 43 | objectMapper.setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE); 44 | objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); 45 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 46 | return objectMapper; 47 | } 48 | ) 49 | ); 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/service/Attachment.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.service; 2 | 3 | import java.util.List; 4 | 5 | public class Attachment extends AbstractJiraService { 6 | private final Issue issue; 7 | 8 | public Attachment(Issue issue) { 9 | this.issue = issue; 10 | } 11 | 12 | /** 13 | * Returns list of attachment IDs. 14 | */ 15 | public List getIds() { 16 | return issue.getIssue().getList("fields.attachment.id"); 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/service/IssueLink.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.service; 2 | 3 | import com.frameworkium.core.common.reporting.jira.endpoint.JiraEndpoint; 4 | import io.restassured.http.ContentType; 5 | import org.json.JSONException; 6 | import org.json.JSONObject; 7 | 8 | public class IssueLink extends AbstractJiraService { 9 | /** 10 | * Creates an issue link between two issues. 11 | * 12 | * @param type name of issue 13 | * @param inwardIssue inward issue key 14 | * @param outwardIssue outward issue key 15 | */ 16 | public void linkIssues(String type, String inwardIssue, String outwardIssue) { 17 | JSONObject obj = new JSONObject(); 18 | JSONObject typeObj = new JSONObject(); 19 | JSONObject inwardIssueObj = new JSONObject(); 20 | JSONObject outwardIssueObj = new JSONObject(); 21 | 22 | try { 23 | obj.put("type", typeObj); 24 | typeObj.put("name", type); 25 | obj.put("inwardIssue", inwardIssueObj); 26 | inwardIssueObj.put("key", inwardIssue); 27 | obj.put("outwardIssue", outwardIssueObj); 28 | outwardIssueObj.put("key", outwardIssue); 29 | } catch (JSONException e) { 30 | logger.error("Can't create JSON Object for linkIssues", e); 31 | } 32 | 33 | getRequestSpec().log().ifValidationFails() 34 | .basePath(JiraEndpoint.ISSUELINK.getUrl()) 35 | .contentType(ContentType.JSON) 36 | .body(obj.toString()) 37 | .when() 38 | .post(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/service/Project.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.service; 2 | 3 | import static org.apache.http.HttpStatus.SC_OK; 4 | 5 | import com.frameworkium.core.common.reporting.jira.dto.project.ProjectDto; 6 | import com.frameworkium.core.common.reporting.jira.dto.version.VersionDto; 7 | import com.frameworkium.core.common.reporting.jira.endpoint.JiraEndpoint; 8 | import io.restassured.http.ContentType; 9 | import java.util.List; 10 | 11 | public class Project extends AbstractJiraService { 12 | public ProjectDto getProject(String projectIdOrKey) { 13 | return getRequestSpec() 14 | .basePath(JiraEndpoint.PROJECT.getUrl()) 15 | .pathParam("projectIdOrKey", projectIdOrKey) 16 | .contentType(ContentType.JSON) 17 | .get("/{projectIdOrKey}") 18 | .then() 19 | .log().ifValidationFails() 20 | .statusCode(SC_OK) 21 | .extract() 22 | .as(ProjectDto.class); 23 | } 24 | 25 | public List getProjectVersions(String projectIdOrKey) { 26 | return getRequestSpec() 27 | .basePath(JiraEndpoint.PROJECT.getUrl()) 28 | .pathParam("projectIdOrKey", projectIdOrKey) 29 | .get("/{projectIdOrKey}/versions") 30 | .then() 31 | .log().ifValidationFails() 32 | .statusCode(SC_OK) 33 | .extract() 34 | .body().jsonPath() 35 | .getList("", VersionDto.class); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/service/Search.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.service; 2 | 3 | import com.frameworkium.core.common.reporting.jira.endpoint.JiraEndpoint; 4 | import io.restassured.path.json.JsonPath; 5 | import java.util.List; 6 | 7 | public class Search extends AbstractJiraService { 8 | private static final int MAX_SEARCH_RESULTS = 1000; 9 | protected JsonPath searchResults; 10 | 11 | public Search(final String query) { 12 | this(query, 0, MAX_SEARCH_RESULTS); 13 | } 14 | 15 | public Search(final String query, final int startAt, final int maxSearchResults) { 16 | try { 17 | this.searchResults = getRequestSpec().log().ifValidationFails() 18 | .basePath(JiraEndpoint.SEARCH.getUrl()) 19 | .param("jql", query) 20 | .param("startAt", startAt) 21 | .param("maxResults", maxSearchResults) 22 | .when() 23 | .get() 24 | .then().log().ifValidationFails() 25 | .extract().jsonPath(); 26 | } catch (Exception e) { 27 | logger.error("Unable to search for JIRA issue", e); 28 | } 29 | } 30 | 31 | public List getKeys() { 32 | return searchResults.getList("issues.key"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/service/Version.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.service; 2 | 3 | import static org.apache.http.HttpStatus.SC_CREATED; 4 | import static org.apache.http.HttpStatus.SC_OK; 5 | 6 | import com.frameworkium.core.common.reporting.jira.dto.version.VersionDto; 7 | import com.frameworkium.core.common.reporting.jira.endpoint.JiraEndpoint; 8 | 9 | public class Version extends AbstractJiraService { 10 | public VersionDto getVersion(String versionId) { 11 | return getRequestSpec() 12 | .basePath(JiraEndpoint.VERSION.getUrl()) 13 | .pathParam("id", versionId) 14 | .get("/{id}") 15 | .then() 16 | .log().ifValidationFails() 17 | .statusCode(SC_OK) 18 | .extract() 19 | .as(VersionDto.class); 20 | } 21 | 22 | public VersionDto createVersion(VersionDto versionDto) { 23 | return getRequestSpec() 24 | .basePath(JiraEndpoint.VERSION.getUrl()) 25 | .body(versionDto) 26 | .post() 27 | .then() 28 | .log().ifValidationFails() 29 | .statusCode(SC_CREATED) 30 | .extract() 31 | .as(VersionDto.class); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/util/ExecutionSearchUtil.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.util; 2 | 3 | import com.frameworkium.core.common.properties.Property; 4 | import com.frameworkium.core.common.reporting.jira.dto.executionsearch.ExecutionSearchDto; 5 | import com.frameworkium.core.common.reporting.jira.dto.executionsearch.ExecutionSearchListDto; 6 | import com.frameworkium.core.common.reporting.jira.zapi.ExecutionSearch; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.Stream; 10 | import org.apache.commons.lang.StringUtils; 11 | 12 | public class ExecutionSearchUtil { 13 | private final ExecutionSearchListDto result; 14 | 15 | public ExecutionSearchUtil(String query) { 16 | this(new ExecutionSearch(), query); 17 | } 18 | 19 | public ExecutionSearchUtil(ExecutionSearch executionSearch, String query) { 20 | this.result = executionSearch.search(query); 21 | } 22 | 23 | /** 24 | * Get a list of execution Ids optionally filtered by Property.ZAPI_CYCLE_REGEX 25 | * 26 | * @return a list of execution Ids 27 | */ 28 | public List getExecutionIdsByZAPICycleRegex() { 29 | return getExecutionStream() 30 | .map(e -> e.executionLightDto.id.intValue()) 31 | .collect(Collectors.toList()); 32 | } 33 | 34 | /** 35 | * Get a list of execution status Ids optionally filtered by Property.ZAPI_CYCLE_REGEX 36 | * 37 | * @return a list of execution status Ids 38 | */ 39 | public List getExecutionStatusesByZAPICycleRegex() { 40 | return getExecutionStream() 41 | .map(e -> e.status.id.intValue()) 42 | .collect(Collectors.toList()); 43 | } 44 | 45 | private Stream getExecutionStream() { 46 | if (StringUtils.isNotEmpty(Property.ZAPI_CYCLE_REGEX.getValue())) { 47 | return result.executions.stream() 48 | .filter(e -> e.executionLightDto.cycleName.equals(Property.ZAPI_CYCLE_REGEX.getValue())); 49 | } 50 | return result.executions.stream(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/zapi/Attachment.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.zapi; 2 | 3 | import static org.apache.http.HttpStatus.SC_OK; 4 | 5 | import com.frameworkium.core.common.reporting.jira.dto.attachment.AttachmentListDto; 6 | import com.frameworkium.core.common.reporting.jira.endpoint.ZephyrEndpoint; 7 | import com.frameworkium.core.common.reporting.jira.service.AbstractJiraService; 8 | import java.io.File; 9 | 10 | public class Attachment extends AbstractJiraService { 11 | public AttachmentListDto getAttachmentByEntity(Integer entityId, String entityType) { 12 | return getRequestSpec() 13 | .basePath(ZephyrEndpoint.ATTACHMENT_BY_ENTITY.getUrl()) 14 | .queryParam("entityId", entityId) 15 | .queryParam("entityType", entityType) 16 | .when() 17 | .get() 18 | .then().log().ifValidationFails() 19 | .extract() 20 | .as(AttachmentListDto.class); 21 | } 22 | 23 | public void deleteAttachment(Long id) { 24 | getRequestSpec() 25 | .basePath(ZephyrEndpoint.ATTACHMENT.getUrl()) 26 | .pathParam("id", id) 27 | .when() 28 | .delete("/{id}") 29 | .then().log().ifValidationFails() 30 | .statusCode(SC_OK); 31 | } 32 | 33 | public void addAttachments(Integer entityId, String entityType, File file) { 34 | getRequestSpec() 35 | .basePath(ZephyrEndpoint.ATTACHMENT.getUrl()) 36 | .queryParam("entityId", entityId) 37 | .queryParam("entityType", entityType) 38 | .header("X-Atlassian-Token", "nocheck") 39 | .multiPart(file) 40 | .when() 41 | .post() 42 | .then().log().ifValidationFails() 43 | .statusCode(SC_OK); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/zapi/Cycle.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.zapi; 2 | 3 | import static org.apache.http.HttpStatus.SC_OK; 4 | 5 | import com.frameworkium.core.common.reporting.jira.dto.cycle.CreateCycleSuccessDto; 6 | import com.frameworkium.core.common.reporting.jira.dto.cycle.CreateNewCycleDto; 7 | import com.frameworkium.core.common.reporting.jira.dto.cycle.CycleListDto; 8 | import com.frameworkium.core.common.reporting.jira.endpoint.ZephyrEndpoint; 9 | import com.frameworkium.core.common.reporting.jira.service.AbstractJiraService; 10 | 11 | public class Cycle extends AbstractJiraService { 12 | public CycleListDto getListOfCycle(Long projectId, Long versionId) { 13 | return getRequestSpec() 14 | .basePath(ZephyrEndpoint.CYCLE.getUrl()) 15 | .queryParam("projectId", projectId) 16 | .queryParam("versionId", versionId) 17 | .get() 18 | .then() 19 | .log().ifValidationFails() 20 | .statusCode(SC_OK) 21 | .extract().body() 22 | .as(CycleListDto.class); 23 | } 24 | 25 | public CreateCycleSuccessDto createNewCycle(CreateNewCycleDto createNewCycleDto) { 26 | return getRequestSpec() 27 | .basePath(ZephyrEndpoint.CYCLE.getUrl()) 28 | .body(createNewCycleDto) 29 | .post() 30 | .then() 31 | .log().ifValidationFails() 32 | .extract() 33 | .as(CreateCycleSuccessDto.class); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/zapi/Execution.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.zapi; 2 | 3 | import static org.apache.http.HttpStatus.SC_OK; 4 | 5 | import com.frameworkium.core.common.reporting.jira.dto.execution.AddTestToCycleOperationDto; 6 | import com.frameworkium.core.common.reporting.jira.dto.execution.UpdateExecutionOperationDto; 7 | import com.frameworkium.core.common.reporting.jira.endpoint.ZephyrEndpoint; 8 | import com.frameworkium.core.common.reporting.jira.service.AbstractJiraService; 9 | 10 | public class Execution extends AbstractJiraService { 11 | public String addTestsToCycle(AddTestToCycleOperationDto addTestToCycleOperationDto) { 12 | return getRequestSpec() 13 | .basePath(ZephyrEndpoint.ADD_TEST_TO_EXECUTION.getUrl()) 14 | .body(addTestToCycleOperationDto) 15 | .post() 16 | .then() 17 | .log().ifValidationFails() 18 | .statusCode(SC_OK) 19 | .extract() 20 | .asString(); //gets a jobprogresstoken 21 | } 22 | 23 | public String updateExecutionDetails(UpdateExecutionOperationDto updateExecutionOperationDto, 24 | Integer id) { 25 | return getRequestSpec() 26 | .basePath(ZephyrEndpoint.EXECUTION.getUrl()) 27 | .pathParam("id", id) 28 | .body(updateExecutionOperationDto) 29 | .put("/{id}/execute") 30 | .then() 31 | .log().ifValidationFails() 32 | .statusCode(SC_OK) 33 | .extract() 34 | .asString(); // get a success token 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/jira/zapi/ExecutionSearch.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.zapi; 2 | 3 | import com.frameworkium.core.common.reporting.jira.dto.executionsearch.ExecutionSearchListDto; 4 | import com.frameworkium.core.common.reporting.jira.endpoint.ZephyrEndpoint; 5 | import com.frameworkium.core.common.reporting.jira.service.AbstractJiraService; 6 | import io.restassured.specification.RequestSpecification; 7 | 8 | public class ExecutionSearch extends AbstractJiraService { 9 | public ExecutionSearchListDto search(String zqlQuery) { 10 | return search(zqlQuery, null, null, null, null); 11 | } 12 | 13 | public ExecutionSearchListDto search( 14 | String zqlQuery, Integer filterId, Integer offset, Integer maxRecords, String expand) { 15 | RequestSpecification reqspec = getRequestSpec() 16 | .basePath(ZephyrEndpoint.EXECUTE_SEARCH.getUrl()) 17 | .queryParam("zqlQuery", zqlQuery); 18 | 19 | if (filterId != null) { 20 | reqspec.queryParam("filterId", filterId); 21 | } 22 | if (offset != null) { 23 | reqspec.queryParam("offset", offset); 24 | } 25 | if (maxRecords != null) { 26 | reqspec.queryParam("maxRecords", maxRecords); 27 | } 28 | if (expand != null) { 29 | reqspec.queryParam("expand", expand); 30 | } 31 | 32 | return reqspec.when() 33 | .get().then().log().ifValidationFails() 34 | .extract() 35 | .as(ExecutionSearchListDto.class); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/reporting/spira/SpiraConfig.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.spira; 2 | 3 | public class SpiraConfig { 4 | 5 | public static final String USERNAME = "administrator"; 6 | public static final String API_KEY = "{750AE393-737C-434E-A3AB-0FF2D0476E3C}"; 7 | public static final String REST_PATH = "/Services/v4_0/RestService.svc/projects/2"; 8 | 9 | private SpiraConfig() { 10 | } 11 | 12 | public static class SpiraStatus { 13 | public static final int SPIRA_STATUS_PASS = 2; 14 | public static final int SPIRA_STATUS_FAIL = 1; 15 | public static final int SPIRA_STATUS_WIP = 4; 16 | public static final int SPIRA_STATUS_BLOCKED = 5; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/common/retry/RetryFlakyTest.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.retry; 2 | 3 | import com.frameworkium.core.common.properties.Property; 4 | import org.testng.IRetryAnalyzer; 5 | import org.testng.ITestResult; 6 | 7 | public class RetryFlakyTest implements IRetryAnalyzer { 8 | 9 | /** 10 | * Maximum retry count of failed tests, defaults to 1. 11 | */ 12 | static final int MAX_RETRY_COUNT = 13 | Property.MAX_RETRY_COUNT.getIntWithDefault(1); 14 | 15 | private int retryCount = 0; 16 | 17 | @Override 18 | public boolean retry(ITestResult result) { 19 | return retryCount++ < MAX_RETRY_COUNT; 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/annotations/Timeout.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation that is used for setting waiting timeout value, 10 | * which will be used for waiting an element to appear. 11 | *

12 | * For example: 13 | *

14 | *

15 |  * @FindBy(css = "my_form_css")
16 |  * @Timeout(3)
17 |  * public class MyForm extends HtmlElement {
18 |  * @FindBy(css = "text_input_css")
19 |  * @Timeout(3)
20 |  * private TextInput textInput;
21 |  * 

22 | * // Other elements and methods here 23 | * } 24 | *

25 | */ 26 | @Retention(RetentionPolicy.RUNTIME) 27 | @Target({ElementType.TYPE, ElementType.FIELD}) 28 | public @interface Timeout { 29 | int value(); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/element/Button.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.element; 2 | 3 | import org.openqa.selenium.WebElement; 4 | 5 | /** 6 | * Represents web page button control. 7 | */ 8 | public class Button extends TypifiedElement { 9 | 10 | /** 11 | * Specifies wrapped {@link WebElement}. 12 | * 13 | * @param wrappedElement {@code WebElement} to wrap. 14 | */ 15 | public Button(WebElement wrappedElement) { 16 | super(wrappedElement); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/element/CheckBox.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.element; 2 | 3 | import java.util.Optional; 4 | import org.openqa.selenium.By; 5 | import org.openqa.selenium.NoSuchElementException; 6 | import org.openqa.selenium.WebElement; 7 | 8 | /** 9 | * Represents checkbox control. 10 | */ 11 | public class CheckBox extends TypifiedElement { 12 | 13 | /** 14 | * Specifies wrapped {@link WebElement}. 15 | * 16 | * @param wrappedElement {@code WebElement} to wrap. 17 | */ 18 | public CheckBox(WebElement wrappedElement) { 19 | super(wrappedElement); 20 | } 21 | 22 | /** 23 | * Finds label corresponding to this checkbox using "following-sibling::label" xpath. 24 | * 25 | * @return Optional of the {@code WebElement} representing label 26 | */ 27 | public Optional getLabel() { 28 | try { 29 | return Optional.of(getWrappedElement().findElement(By.xpath("following-sibling::label"))); 30 | } catch (NoSuchElementException e) { 31 | return Optional.empty(); 32 | } 33 | } 34 | 35 | /** 36 | * Finds the text of the checkbox label. 37 | * 38 | * @return Optional of the label text 39 | */ 40 | public Optional getLabelText() { 41 | return getLabel().map(WebElement::getText); 42 | } 43 | 44 | /** 45 | * The same as {@link #getLabelText()}. 46 | * 47 | * @return Text of the checkbox label or {@code null} if no label has been found. 48 | */ 49 | public String getText() { 50 | return getLabelText().orElse(""); 51 | } 52 | 53 | /** 54 | * Selects checkbox if it is not already selected. 55 | */ 56 | public void select() { 57 | if (!isSelected()) { 58 | getWrappedElement().click(); 59 | } 60 | } 61 | 62 | /** 63 | * Deselects checkbox if it is not already deselected. 64 | */ 65 | public void deselect() { 66 | if (isSelected()) { 67 | getWrappedElement().click(); 68 | } 69 | } 70 | 71 | /** 72 | * Selects checkbox if passed value is {@code true} and deselects otherwise. 73 | */ 74 | public void set(boolean value) { 75 | if (value) { 76 | select(); 77 | } else { 78 | deselect(); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/element/Image.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.element; 2 | 3 | import org.openqa.selenium.WebElement; 4 | 5 | /** 6 | * Represents an image {@code } 7 | */ 8 | public class Image extends TypifiedElement { 9 | 10 | public Image(WebElement wrappedElement) { 11 | super(wrappedElement); 12 | } 13 | 14 | /** 15 | * Retrieves path to image from "src" attribute 16 | * 17 | * @return Path to the image 18 | */ 19 | public String getSource() { 20 | return getWrappedElement().getAttribute("src"); 21 | } 22 | 23 | /** 24 | * Retrieves alternative text from "alt" attribute 25 | * 26 | * @return alternative text for image 27 | */ 28 | public String getAlt() { 29 | return getWrappedElement().getAttribute("alt"); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/element/Link.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.element; 2 | 3 | import org.openqa.selenium.WebElement; 4 | 5 | /** 6 | * Represents an anchor tag/hyperlink. 7 | */ 8 | public class Link extends TypifiedElement { 9 | 10 | public Link(WebElement wrappedElement) { 11 | super(wrappedElement); 12 | } 13 | 14 | /** 15 | * Retrieves reference from "href" attribute. 16 | * 17 | * @return Reference associated with hyperlink. 18 | */ 19 | public String getReference() { 20 | return getWrappedElement().getAttribute("href"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/element/Select.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.element; 2 | 3 | import java.util.List; 4 | import org.openqa.selenium.WebElement; 5 | import org.openqa.selenium.support.ui.ISelect; 6 | 7 | /** 8 | * Represents a select control. 9 | *

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

13 | * But unlike {@code WebDriver} {@code Select} class there are no checks 14 | * performed in the constructor of this class, so it can be used correctly 15 | * with lazy initialization. 16 | */ 17 | public class Select extends TypifiedElement implements ISelect { 18 | 19 | /** 20 | * Specifies wrapped {@link WebElement}. 21 | * Performs no checks unlike {@link org.openqa.selenium.support.ui.Select}. 22 | * All checks are made later in {@link #getSelect()} method. 23 | * 24 | * @param wrappedElement {@code WebElement} to wrap. 25 | */ 26 | public Select(WebElement wrappedElement) { 27 | super(wrappedElement); 28 | } 29 | 30 | /** 31 | * Constructs instance of {@link org.openqa.selenium.support.ui.Select} class. 32 | * 33 | * @return {@link org.openqa.selenium.support.ui.Select} class instance. 34 | */ 35 | private org.openqa.selenium.support.ui.Select getSelect() { 36 | return new org.openqa.selenium.support.ui.Select(getWrappedElement()); 37 | } 38 | 39 | public boolean isMultiple() { 40 | return getSelect().isMultiple(); 41 | } 42 | 43 | public List getOptions() { 44 | return getSelect().getOptions(); 45 | } 46 | 47 | public List getAllSelectedOptions() { 48 | return getSelect().getAllSelectedOptions(); 49 | } 50 | 51 | public WebElement getFirstSelectedOption() { 52 | return getSelect().getFirstSelectedOption(); 53 | } 54 | 55 | /** 56 | * Indicates if select has at least one selected option. 57 | * 58 | * @return {@code true} if select has at least one selected option and 59 | * {@code false} otherwise. 60 | */ 61 | public boolean hasSelectedOption() { 62 | return getOptions().stream().anyMatch(WebElement::isSelected); 63 | } 64 | 65 | public void selectByVisibleText(String text) { 66 | getSelect().selectByVisibleText(text); 67 | } 68 | 69 | public void selectByIndex(int index) { 70 | getSelect().selectByIndex(index); 71 | } 72 | 73 | public void selectByValue(String value) { 74 | getSelect().selectByValue(value); 75 | } 76 | 77 | public void deselectAll() { 78 | getSelect().deselectAll(); 79 | } 80 | 81 | public void deselectByValue(String value) { 82 | getSelect().deselectByValue(value); 83 | } 84 | 85 | public void deselectByIndex(int index) { 86 | getSelect().deselectByIndex(index); 87 | } 88 | 89 | public void deselectByVisibleText(String text) { 90 | getSelect().deselectByVisibleText(text); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/element/TextBlock.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.element; 2 | 3 | import org.openqa.selenium.WebElement; 4 | 5 | /** 6 | * Represents text block on a web page. 7 | */ 8 | public class TextBlock extends TypifiedElement { 9 | public TextBlock(WebElement wrappedElement) { 10 | super(wrappedElement); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/element/TextInput.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.element; 2 | 3 | import java.util.Optional; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.openqa.selenium.Keys; 6 | import org.openqa.selenium.WebElement; 7 | 8 | /** 9 | * Represents text input control 10 | * (such as <input type="text"/> or <textarea/>). 11 | */ 12 | public class TextInput extends TypifiedElement { 13 | 14 | /** 15 | * Specifies wrapped {@link WebElement}. 16 | * 17 | * @param wrappedElement {@code WebElement} to wrap. 18 | */ 19 | public TextInput(WebElement wrappedElement) { 20 | super(wrappedElement); 21 | } 22 | 23 | /** 24 | * Retrieves the text entered into this text input. 25 | * 26 | * @return Text entered into the text input. 27 | */ 28 | @Override 29 | public String getText() { 30 | if ("textarea".equals(getWrappedElement().getTagName())) { 31 | return getWrappedElement().getText(); 32 | } 33 | 34 | return Optional 35 | .ofNullable(getWrappedElement().getAttribute("value")) 36 | .orElse(""); 37 | } 38 | 39 | /** 40 | * Sets the text of this Input. This is different to 41 | * {@link #sendKeys(CharSequence...)} because it will delete any existing 42 | * text first. 43 | *

44 | * {@code text} will equal {@link #getText()} after calling this method. 45 | * 46 | * @param text the text to set 47 | */ 48 | public void setText(CharSequence text) { 49 | getWrappedElement().sendKeys(getClearCharSequence() + text); 50 | } 51 | 52 | /** 53 | * Returns sequence of backspaces and deletes that will clear element. 54 | * clear() can't be used because generates separate onchange event 55 | * See https://github.com/yandex-qatools/htmlelements/issues/65 56 | */ 57 | public String getClearCharSequence() { 58 | return StringUtils.repeat(Keys.DELETE.toString() + Keys.BACK_SPACE, getText().length()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/exceptions/HtmlElementsException.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.exceptions; 2 | 3 | /** 4 | * Thrown during runtime in cases when a block of elements or a 5 | * page object can't be instantiated or initialized. 6 | */ 7 | public class HtmlElementsException extends RuntimeException { 8 | 9 | private static final long serialVersionUID = 1L; 10 | 11 | public HtmlElementsException() { 12 | super(); 13 | } 14 | 15 | public HtmlElementsException(String message) { 16 | super(message); 17 | } 18 | 19 | public HtmlElementsException(String message, Throwable cause) { 20 | super(message, cause); 21 | } 22 | 23 | public HtmlElementsException(Throwable cause) { 24 | super(cause); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/loader/decorator/HtmlElementClassAnnotationsHandler.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.loader.decorator; 2 | 3 | import com.frameworkium.core.htmlelements.element.HtmlElement; 4 | import com.frameworkium.core.htmlelements.exceptions.HtmlElementsException; 5 | import org.openqa.selenium.By; 6 | import org.openqa.selenium.support.FindBy; 7 | import org.openqa.selenium.support.FindBy.FindByBuilder; 8 | import org.openqa.selenium.support.pagefactory.AbstractAnnotations; 9 | 10 | /** 11 | * Handles annotation of {@link HtmlElement} and its successors. 12 | */ 13 | public class HtmlElementClassAnnotationsHandler extends AbstractAnnotations { 14 | 15 | private final Class elementClass; 16 | 17 | public HtmlElementClassAnnotationsHandler(Class elementClass) { 18 | this.elementClass = elementClass; 19 | } 20 | 21 | @Override 22 | public By buildBy() { 23 | Class clazz = elementClass; 24 | while (clazz != Object.class) { 25 | if (clazz.isAnnotationPresent(FindBy.class)) { 26 | return new FindByBuilder().buildIt(clazz.getAnnotation(FindBy.class), null); 27 | } 28 | clazz = clazz.getSuperclass(); 29 | } 30 | 31 | throw new HtmlElementsException(String.format( 32 | "Cannot determine how to locate instance of %s", elementClass)); 33 | } 34 | 35 | @Override 36 | public boolean isLookupCached() { 37 | return false; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/loader/decorator/ProxyFactory.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.loader.decorator; 2 | 3 | import com.frameworkium.core.htmlelements.element.HtmlElement; 4 | import com.frameworkium.core.htmlelements.element.TypifiedElement; 5 | import java.lang.reflect.InvocationHandler; 6 | import java.lang.reflect.Proxy; 7 | import java.util.List; 8 | import org.openqa.selenium.WebElement; 9 | import org.openqa.selenium.WrapsElement; 10 | import org.openqa.selenium.interactions.Locatable; 11 | 12 | /** 13 | * Contains factory methods for creating proxy of blocks, typified elements, page objects 14 | */ 15 | @SuppressWarnings("unchecked") 16 | public class ProxyFactory { 17 | public static T createWebElementProxy(ClassLoader loader, 18 | InvocationHandler handler) { 19 | Class[] interfaces = new Class[] {WebElement.class, WrapsElement.class, Locatable.class}; 20 | return (T) Proxy.newProxyInstance(loader, interfaces, handler); 21 | } 22 | 23 | public static List createWebElementListProxy(ClassLoader loader, 24 | InvocationHandler handler) { 25 | return (List) Proxy.newProxyInstance(loader, new Class[] {List.class}, handler); 26 | } 27 | 28 | public static List createTypifiedElementListProxy( 29 | ClassLoader loader, 30 | InvocationHandler handler) { 31 | return (List) Proxy.newProxyInstance(loader, new Class[] {List.class}, handler); 32 | } 33 | 34 | public static List createHtmlElementListProxy(ClassLoader loader, 35 | InvocationHandler handler) { 36 | return (List) Proxy.newProxyInstance(loader, new Class[] {List.class}, handler); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/HtmlElementListNamedProxyHandler.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.loader.decorator.proxyhandlers; 2 | 3 | import static com.frameworkium.core.htmlelements.loader.HtmlElementLoader.createHtmlElement; 4 | 5 | import com.frameworkium.core.htmlelements.element.HtmlElement; 6 | import java.lang.reflect.InvocationHandler; 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.lang.reflect.Method; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | import org.openqa.selenium.support.pagefactory.ElementLocator; 13 | 14 | public class HtmlElementListNamedProxyHandler implements InvocationHandler { 15 | 16 | private final Class elementClass; 17 | private final ElementLocator locator; 18 | private final String name; 19 | 20 | public HtmlElementListNamedProxyHandler(Class elementClass, ElementLocator locator, 21 | String name) { 22 | this.elementClass = elementClass; 23 | this.locator = locator; 24 | this.name = name; 25 | } 26 | 27 | @Override 28 | public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 29 | if ("toString".equals(method.getName())) { 30 | return name; 31 | } 32 | 33 | List elements = locator.findElements().stream() 34 | .map(element -> createHtmlElement(elementClass, element)) 35 | .collect(Collectors.toCollection(LinkedList::new)); 36 | 37 | try { 38 | return method.invoke(elements, objects); 39 | } catch (InvocationTargetException e) { 40 | // Unwrap the underlying exception 41 | throw e.getCause(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/TypifiedElementListNamedProxyHandler.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.loader.decorator.proxyhandlers; 2 | 3 | import static com.frameworkium.core.htmlelements.loader.HtmlElementLoader.createTypifiedElement; 4 | 5 | import com.frameworkium.core.htmlelements.element.TypifiedElement; 6 | import java.lang.reflect.InvocationHandler; 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.lang.reflect.Method; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | import org.openqa.selenium.support.pagefactory.ElementLocator; 13 | 14 | public class TypifiedElementListNamedProxyHandler 15 | implements InvocationHandler { 16 | 17 | private final Class elementClass; 18 | private final ElementLocator locator; 19 | private final String name; 20 | 21 | public TypifiedElementListNamedProxyHandler(Class elementClass, ElementLocator locator, 22 | String name) { 23 | this.elementClass = elementClass; 24 | this.locator = locator; 25 | this.name = name; 26 | } 27 | 28 | @Override 29 | public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 30 | if ("toString".equals(method.getName())) { 31 | return name; 32 | } 33 | 34 | List elements = locator.findElements().stream() 35 | .map(element -> createTypifiedElement(elementClass, element)) 36 | .collect(Collectors.toCollection(LinkedList::new)); 37 | 38 | try { 39 | return method.invoke(elements, objects); 40 | } catch (InvocationTargetException e) { 41 | // Unwrap the underlying exception 42 | throw e.getCause(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/WebElementListNamedProxyHandler.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.loader.decorator.proxyhandlers; 2 | 3 | import java.lang.reflect.Method; 4 | import org.openqa.selenium.support.pagefactory.ElementLocator; 5 | import org.openqa.selenium.support.pagefactory.internal.LocatingElementListHandler; 6 | 7 | public class WebElementListNamedProxyHandler extends LocatingElementListHandler { 8 | 9 | private final String name; 10 | 11 | public WebElementListNamedProxyHandler(ElementLocator locator, String name) { 12 | super(locator); 13 | this.name = name; 14 | } 15 | 16 | @Override 17 | public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 18 | if ("toString".equals(method.getName())) { 19 | return name; 20 | } 21 | return super.invoke(o, method, objects); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/loader/decorator/proxyhandlers/WebElementNamedProxyHandler.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.loader.decorator.proxyhandlers; 2 | 3 | import com.frameworkium.core.htmlelements.utils.HtmlElementUtils; 4 | import java.lang.reflect.Method; 5 | import java.time.Clock; 6 | import java.util.concurrent.TimeUnit; 7 | import org.openqa.selenium.StaleElementReferenceException; 8 | import org.openqa.selenium.support.pagefactory.ElementLocator; 9 | import org.openqa.selenium.support.pagefactory.internal.LocatingElementHandler; 10 | 11 | public class WebElementNamedProxyHandler extends LocatingElementHandler { 12 | 13 | private final long timeOutInSeconds; 14 | private final Clock clock; 15 | private final String name; 16 | 17 | public WebElementNamedProxyHandler(ElementLocator locator, String name) { 18 | super(locator); 19 | this.name = name; 20 | this.clock = Clock.systemDefaultZone(); 21 | this.timeOutInSeconds = HtmlElementUtils.getImplicitTimeoutInSeconds(); 22 | } 23 | 24 | @Override 25 | public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 26 | if ("toString".equals(method.getName())) { 27 | return name; 28 | } 29 | 30 | final long end = this.clock.millis() + TimeUnit.SECONDS.toMillis(this.timeOutInSeconds); 31 | 32 | StaleElementReferenceException lastException; 33 | do { 34 | try { 35 | return super.invoke(o, method, objects); 36 | } catch (StaleElementReferenceException e) { 37 | lastException = e; 38 | this.waitFor(); 39 | } 40 | } 41 | while (this.clock.millis() < end); 42 | throw lastException; 43 | } 44 | 45 | protected long sleepFor() { 46 | return 500L; 47 | } 48 | 49 | private void waitFor() throws InterruptedException { 50 | Thread.sleep(this.sleepFor()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/htmlelements/pagefactory/CustomElementLocatorFactory.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.htmlelements.pagefactory; 2 | 3 | import org.openqa.selenium.support.pagefactory.ElementLocator; 4 | import org.openqa.selenium.support.pagefactory.ElementLocatorFactory; 5 | 6 | /** 7 | * A factory for producing {@link ElementLocator}s. It is expected that a new 8 | * ElementLocator will be returned per call. 9 | */ 10 | public interface CustomElementLocatorFactory extends ElementLocatorFactory { 11 | ElementLocator createLocator(Class clazz); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/annotations/ForceVisible.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.FIELD) 10 | public @interface ForceVisible { 11 | 12 | /** 13 | * Default value. 14 | */ 15 | String value() default ""; 16 | 17 | /** 18 | * If checking for visibility of a list of elements, setting a value 19 | * will only check for visibility of the first n elements of the list. 20 | */ 21 | int checkAtMost() default -1; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/annotations/Invisible.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.FIELD) 10 | public @interface Invisible { 11 | 12 | /** 13 | * Default value. 14 | */ 15 | String value() default ""; 16 | 17 | /** 18 | * If checking for invisibility of a list of elements, setting a value 19 | * will only check for invisibility of the first n elements of the list. 20 | */ 21 | int checkAtMost() default -1; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/annotations/Visible.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.FIELD) 10 | public @interface Visible { 11 | 12 | /** 13 | * Default value. 14 | */ 15 | String value() default ""; 16 | 17 | /** 18 | * If checking for visibility of a list of elements, setting a value 19 | * will only check for visibility of the first n elements of the list. 20 | */ 21 | int checkAtMost() default -1; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/browsers/UserAgent.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.browsers; 2 | 3 | import org.openqa.selenium.JavascriptExecutor; 4 | 5 | public class UserAgent { 6 | 7 | public static final String SCRIPT = "return navigator.userAgent;"; 8 | 9 | private UserAgent() { 10 | // hidden 11 | } 12 | 13 | public static String getUserAgent(JavascriptExecutor driver) { 14 | try { 15 | return (String) driver.executeScript(SCRIPT); 16 | } catch (Exception ignored) { 17 | return null; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/capture/CaptureEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.capture; 2 | 3 | import com.frameworkium.core.api.Endpoint; 4 | import com.frameworkium.core.common.properties.Property; 5 | 6 | /** 7 | * The various Endpoints of Capture. 8 | */ 9 | enum CaptureEndpoint implements Endpoint { 10 | 11 | BASE_URI(Property.CAPTURE_URL.getValue()), 12 | EXECUTIONS(BASE_URI.url + "/executions"), 13 | SCREENSHOT(BASE_URI.url + "/screenshot"); 14 | 15 | private String url; 16 | 17 | CaptureEndpoint(String url) { 18 | this.url = url; 19 | } 20 | 21 | @Override 22 | public String getUrl(Object... params) { 23 | return String.format(url, params); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/capture/ElementHighlighter.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.capture; 2 | 3 | import org.openqa.selenium.JavascriptExecutor; 4 | import org.openqa.selenium.StaleElementReferenceException; 5 | import org.openqa.selenium.WebDriver; 6 | import org.openqa.selenium.WebElement; 7 | 8 | public class ElementHighlighter { 9 | 10 | private JavascriptExecutor js; 11 | private WebElement previousElem; 12 | 13 | public ElementHighlighter(WebDriver driver) { 14 | js = (JavascriptExecutor) driver; 15 | } 16 | 17 | /** 18 | * Highlight a WebElement. 19 | * 20 | * @param webElement to highlight 21 | */ 22 | public void highlightElement(WebElement webElement) { 23 | 24 | previousElem = webElement; // remember the new element 25 | try { 26 | // TODO: save the previous border 27 | js.executeScript("arguments[0].style.border='3px solid red'", webElement); 28 | } catch (StaleElementReferenceException ignored) { 29 | // something went wrong, but no need to crash for highlighting 30 | } 31 | } 32 | 33 | /** 34 | * Unhighlight the previously highlighted WebElement. 35 | */ 36 | public void unhighlightPrevious() { 37 | 38 | try { 39 | // unhighlight the previously highlighted element 40 | js.executeScript("arguments[0].style.border='none'", previousElem); 41 | } catch (StaleElementReferenceException ignored) { 42 | // the page was reloaded/changed, the same element isn't there 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/capture/model/Browser.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.capture.model; 2 | 3 | import static com.frameworkium.core.common.properties.Property.BROWSER; 4 | import static com.frameworkium.core.common.properties.Property.BROWSER_VERSION; 5 | import static com.frameworkium.core.common.properties.Property.DEVICE; 6 | import static com.frameworkium.core.common.properties.Property.PLATFORM; 7 | import static com.frameworkium.core.common.properties.Property.PLATFORM_VERSION; 8 | 9 | import com.fasterxml.jackson.annotation.JsonInclude; 10 | import com.frameworkium.core.ui.UITestLifecycle; 11 | import com.frameworkium.core.ui.driver.DriverSetup; 12 | import java.util.Optional; 13 | import net.sf.uadetector.ReadableUserAgent; 14 | import net.sf.uadetector.UserAgentStringParser; 15 | import net.sf.uadetector.service.UADetectorServiceFactory; 16 | 17 | @JsonInclude(JsonInclude.Include.NON_NULL) 18 | public class Browser { 19 | 20 | public String name; 21 | public String version; 22 | public String device; 23 | public String platform; 24 | public String platformVersion; 25 | 26 | /** 27 | * Create browser object. 28 | */ 29 | public Browser() { 30 | 31 | Optional userAgent = UITestLifecycle.get().getUserAgent(); 32 | if (userAgent.isPresent() && !userAgent.get().isEmpty()) { 33 | UserAgentStringParser uaParser = UADetectorServiceFactory.getResourceModuleParser(); 34 | ReadableUserAgent agent = uaParser.parse(userAgent.get()); 35 | 36 | this.name = agent.getName(); 37 | this.version = agent.getVersionNumber().toVersionString(); 38 | this.device = agent.getDeviceCategory().getName(); 39 | this.platform = agent.getOperatingSystem().getName(); 40 | this.platformVersion = agent.getOperatingSystem().getVersionNumber().toVersionString(); 41 | 42 | } else { 43 | // Fall-back to the Property class 44 | if (BROWSER.isSpecified()) { 45 | this.name = BROWSER.getValue().toLowerCase(); 46 | } else { 47 | this.name = DriverSetup.DEFAULT_BROWSER.toString(); 48 | } 49 | if (BROWSER_VERSION.isSpecified()) { 50 | this.version = BROWSER_VERSION.getValue(); 51 | } 52 | if (DEVICE.isSpecified()) { 53 | this.device = DEVICE.getValue(); 54 | } 55 | if (PLATFORM.isSpecified()) { 56 | this.platform = PLATFORM.getValue(); 57 | } 58 | if (PLATFORM_VERSION.isSpecified()) { 59 | this.platformVersion = PLATFORM_VERSION.getValue(); 60 | } 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/capture/model/Command.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.capture.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.frameworkium.core.ui.driver.Driver; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.openqa.selenium.WebElement; 7 | 8 | @JsonInclude(JsonInclude.Include.NON_NULL) 9 | public class Command { 10 | 11 | public String action; 12 | public String using; 13 | public String value; 14 | 15 | public Command(String action, String using, String value) { 16 | this.action = action; 17 | this.using = using; 18 | this.value = value; 19 | } 20 | 21 | public Command(String action, WebElement element) { 22 | this.action = action; 23 | setUsingAndValue(element); 24 | } 25 | 26 | private void setUsingAndValue(WebElement element) { 27 | // TODO: Improve this. Use hacky solution in LoggingListener? 28 | if (Driver.isNative()) { 29 | this.using = "n/a"; 30 | this.value = "n/a"; 31 | } else { 32 | if (StringUtils.isNotBlank(element.getAttribute("id"))) { 33 | this.using = "id"; 34 | this.value = element.getAttribute("id"); 35 | } else if (!(element.getText()).isEmpty()) { 36 | this.using = "linkText"; 37 | this.value = element.getText(); 38 | } else if (!element.getTagName().isEmpty()) { 39 | this.using = "css"; 40 | this.value = element.getTagName() + "." 41 | + element.getAttribute("class").replace(" ", "."); 42 | } else { 43 | // must be something weird 44 | this.using = "n/a"; 45 | this.value = "n/a"; 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/capture/model/SoftwareUnderTest.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.capture.model; 2 | 3 | import static com.frameworkium.core.common.properties.Property.SUT_NAME; 4 | import static com.frameworkium.core.common.properties.Property.SUT_VERSION; 5 | 6 | public class SoftwareUnderTest { 7 | 8 | public String name; 9 | public String version; 10 | 11 | /** 12 | * Software under test object. 13 | */ 14 | public SoftwareUnderTest() { 15 | if (SUT_NAME.isSpecified()) { 16 | this.name = SUT_NAME.getValue(); 17 | } 18 | if (SUT_VERSION.isSpecified()) { 19 | this.version = SUT_VERSION.getValue(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/capture/model/message/CreateExecution.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.capture.model.message; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.frameworkium.core.ui.capture.model.Browser; 5 | import com.frameworkium.core.ui.capture.model.SoftwareUnderTest; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | 9 | @JsonInclude(JsonInclude.Include.NON_NULL) 10 | public final class CreateExecution { 11 | 12 | private static final Logger logger = LogManager.getLogger(); 13 | public String testID; 14 | public Browser browser; 15 | public SoftwareUnderTest softwareUnderTest; 16 | public String nodeAddress; 17 | 18 | /** 19 | * Create Capture execution. 20 | */ 21 | public CreateExecution(String testID, String nodeAddress) { 22 | 23 | logger.debug("CreateExecution: testID='{}', nodeAddress='{}", testID, nodeAddress); 24 | this.testID = testID; 25 | this.browser = new Browser(); 26 | this.softwareUnderTest = new SoftwareUnderTest(); 27 | this.nodeAddress = nodeAddress; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/capture/model/message/CreateScreenshot.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.capture.model.message; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.frameworkium.core.ui.capture.model.Command; 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | 8 | @JsonInclude(JsonInclude.Include.NON_NULL) 9 | public class CreateScreenshot { 10 | 11 | private static final Logger logger = LogManager.getLogger(); 12 | public Command command; 13 | public String url; 14 | public String executionID; 15 | public String errorMessage; 16 | public String screenshotBase64; 17 | 18 | /** 19 | * Create screenshot object. 20 | */ 21 | public CreateScreenshot( 22 | String executionID, Command command, String url, 23 | String errorMessage, String screenshotBase64) { 24 | 25 | logger.debug("Creating screenshot: executionID='{}', " 26 | + "Command.action='{}', url='{}', " 27 | + "errorMessage='{}', screenshotBase64.length={}", 28 | executionID, 29 | command.action, url, 30 | errorMessage, screenshotBase64.length()); 31 | this.executionID = executionID; 32 | this.command = command; 33 | this.url = url; 34 | this.errorMessage = errorMessage; 35 | this.screenshotBase64 = screenshotBase64; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/driver/AbstractDriver.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.driver; 2 | 3 | import static java.util.concurrent.TimeUnit.SECONDS; 4 | 5 | import com.frameworkium.core.common.properties.Property; 6 | import com.frameworkium.core.ui.capture.ScreenshotCapture; 7 | import com.frameworkium.core.ui.driver.remotes.BrowserStack; 8 | import com.frameworkium.core.ui.driver.remotes.Sauce; 9 | import com.frameworkium.core.ui.listeners.CaptureListener; 10 | import com.frameworkium.core.ui.listeners.LoggingListener; 11 | import com.frameworkium.core.ui.proxy.SeleniumProxyFactory; 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.openqa.selenium.Capabilities; 15 | import org.openqa.selenium.ImmutableCapabilities; 16 | import org.openqa.selenium.remote.CapabilityType; 17 | import org.openqa.selenium.support.events.EventFiringWebDriver; 18 | 19 | public abstract class AbstractDriver implements Driver { 20 | 21 | protected static final Logger logger = LogManager.getLogger(); 22 | 23 | private EventFiringWebDriver webDriverWrapper; 24 | 25 | private static Capabilities addProxyIfRequired(Capabilities caps) { 26 | if (Property.PROXY.isSpecified()) { 27 | return caps.merge(createProxyCapabilities(Property.PROXY.getValue())); 28 | } else { 29 | return caps; 30 | } 31 | } 32 | 33 | private static Capabilities createProxyCapabilities(String proxyProperty) { 34 | return new ImmutableCapabilities( 35 | CapabilityType.PROXY, 36 | SeleniumProxyFactory.createProxy(proxyProperty)); 37 | } 38 | 39 | private static boolean isMaximiseRequired() { 40 | boolean ableToMaximise = !Sauce.isDesired() 41 | && !BrowserStack.isDesired() 42 | && !Driver.isNative(); 43 | 44 | return ableToMaximise && Property.MAXIMISE.getBoolean(); 45 | } 46 | 47 | @Override 48 | public EventFiringWebDriver getWebDriver() { 49 | return this.webDriverWrapper; 50 | } 51 | 52 | /** 53 | * Creates the Wrapped Driver object and maximises if required. 54 | */ 55 | public void initialise() { 56 | this.webDriverWrapper = setupEventFiringWebDriver(getCapabilities()); 57 | maximiseBrowserIfRequired(); 58 | } 59 | 60 | private EventFiringWebDriver setupEventFiringWebDriver(Capabilities capabilities) { 61 | Capabilities caps = addProxyIfRequired(capabilities); 62 | logger.debug("Browser Capabilities: " + caps); 63 | EventFiringWebDriver eventFiringWD = new EventFiringWebDriver(getWebDriver(caps)); 64 | eventFiringWD.register(new LoggingListener()); 65 | if (ScreenshotCapture.isRequired()) { 66 | eventFiringWD.register(new CaptureListener()); 67 | } 68 | if (!Driver.isNative()) { 69 | eventFiringWD.manage().timeouts().setScriptTimeout(10, SECONDS); 70 | } 71 | return eventFiringWD; 72 | } 73 | 74 | private void maximiseBrowserIfRequired() { 75 | if (isMaximiseRequired()) { 76 | this.webDriverWrapper.manage().window().maximize(); 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/driver/Driver.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.driver; 2 | 3 | import static com.frameworkium.core.common.properties.Property.APP_PATH; 4 | 5 | import org.openqa.selenium.Capabilities; 6 | import org.openqa.selenium.WebDriver; 7 | import org.openqa.selenium.support.events.EventFiringWebDriver; 8 | 9 | public interface Driver { 10 | 11 | /** 12 | * Check whether the driver is for a mobile device. 13 | */ 14 | static boolean isMobile() { 15 | return false; 16 | } 17 | 18 | /** 19 | * Check whether the driver is for a native mobile app. 20 | */ 21 | static boolean isNative() { 22 | return APP_PATH.isSpecified(); 23 | } 24 | 25 | /** 26 | * Method to set-up the driver object. 27 | */ 28 | void initialise(); 29 | 30 | /** 31 | * Implemented in each Driver Type to specify the capabilities of that browser. 32 | * 33 | * @return Capabilities of each browser 34 | */ 35 | Capabilities getCapabilities(); 36 | 37 | /** 38 | * Returns the correct WebDriver object for the Driver Type. 39 | * 40 | * @param capabilities Capabilities of the browser 41 | * @return {@link WebDriver} object for the browser 42 | */ 43 | WebDriver getWebDriver(Capabilities capabilities); 44 | 45 | /** 46 | * Getter for the driver that wraps the initialised driver. 47 | * 48 | * @return EventFiringWebDriver 49 | */ 50 | EventFiringWebDriver getWebDriver(); 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/driver/drivers/ChromeImpl.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.driver.drivers; 2 | 3 | import com.frameworkium.core.common.properties.Property; 4 | import com.frameworkium.core.ui.driver.AbstractDriver; 5 | import org.openqa.selenium.Capabilities; 6 | import org.openqa.selenium.WebDriver; 7 | import org.openqa.selenium.chrome.ChromeDriver; 8 | import org.openqa.selenium.chrome.ChromeOptions; 9 | 10 | public class ChromeImpl extends AbstractDriver { 11 | 12 | @Override 13 | public ChromeOptions getCapabilities() { 14 | var chromeOptions = new ChromeOptions(); 15 | chromeOptions.setHeadless(Property.HEADLESS.getBoolean()); 16 | return chromeOptions; 17 | } 18 | 19 | @Override 20 | public WebDriver getWebDriver(Capabilities capabilities) { 21 | final ChromeOptions chromeOptions; 22 | if (capabilities instanceof ChromeOptions) { 23 | chromeOptions = (ChromeOptions) capabilities; 24 | } else { 25 | chromeOptions = new ChromeOptions().merge(capabilities); 26 | } 27 | return new ChromeDriver(chromeOptions); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/driver/drivers/EdgeImpl.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.driver.drivers; 2 | 3 | import com.frameworkium.core.ui.driver.AbstractDriver; 4 | import org.openqa.selenium.Capabilities; 5 | import org.openqa.selenium.WebDriver; 6 | import org.openqa.selenium.edge.EdgeDriver; 7 | import org.openqa.selenium.edge.EdgeOptions; 8 | 9 | public class EdgeImpl extends AbstractDriver { 10 | 11 | @Override 12 | public EdgeOptions getCapabilities() { 13 | return new EdgeOptions(); 14 | } 15 | 16 | @Override 17 | public WebDriver getWebDriver(Capabilities capabilities) { 18 | final EdgeOptions edgeOptions; 19 | if (capabilities instanceof EdgeOptions) { 20 | edgeOptions = (EdgeOptions) capabilities; 21 | } else { 22 | edgeOptions = new EdgeOptions().merge(capabilities); 23 | } 24 | return new EdgeDriver(edgeOptions); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/driver/drivers/FirefoxImpl.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.driver.drivers; 2 | 3 | import com.frameworkium.core.common.properties.Property; 4 | import com.frameworkium.core.ui.driver.AbstractDriver; 5 | import org.openqa.selenium.Capabilities; 6 | import org.openqa.selenium.WebDriver; 7 | import org.openqa.selenium.firefox.FirefoxDriver; 8 | import org.openqa.selenium.firefox.FirefoxDriverLogLevel; 9 | import org.openqa.selenium.firefox.FirefoxOptions; 10 | 11 | public class FirefoxImpl extends AbstractDriver { 12 | 13 | @Override 14 | public FirefoxOptions getCapabilities() { 15 | FirefoxOptions firefoxOptions = new FirefoxOptions(); 16 | firefoxOptions.setHeadless(Property.HEADLESS.getBoolean()); 17 | firefoxOptions.setLogLevel(FirefoxDriverLogLevel.INFO); 18 | return firefoxOptions; 19 | } 20 | 21 | @Override 22 | public WebDriver getWebDriver(Capabilities capabilities) { 23 | final FirefoxOptions firefoxOptions; 24 | if (capabilities instanceof FirefoxOptions) { 25 | firefoxOptions = (FirefoxOptions) capabilities; 26 | } else { 27 | firefoxOptions = new FirefoxOptions().merge(capabilities); 28 | } 29 | return new FirefoxDriver(firefoxOptions); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/driver/drivers/GridImpl.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.driver.drivers; 2 | 3 | import static com.frameworkium.core.common.properties.Property.APPLICATION_NAME; 4 | import static com.frameworkium.core.common.properties.Property.BROWSER_VERSION; 5 | import static com.frameworkium.core.common.properties.Property.PLATFORM; 6 | import static com.frameworkium.core.common.properties.Property.PLATFORM_VERSION; 7 | 8 | import com.frameworkium.core.common.properties.Property; 9 | import com.frameworkium.core.ui.driver.AbstractDriver; 10 | import java.net.MalformedURLException; 11 | import java.net.URL; 12 | import org.openqa.selenium.Capabilities; 13 | import org.openqa.selenium.MutableCapabilities; 14 | import org.openqa.selenium.WebDriver; 15 | import org.openqa.selenium.remote.RemoteWebDriver; 16 | 17 | public class GridImpl extends AbstractDriver { 18 | 19 | private final URL remoteURL; 20 | private final Capabilities capabilities; 21 | 22 | /** 23 | * Implementation of driver for the Selenium Grid . 24 | */ 25 | public GridImpl(Capabilities capabilities) { 26 | this.capabilities = capabilities; 27 | try { 28 | this.remoteURL = new URL(Property.GRID_URL.getValue()); 29 | } catch (MalformedURLException e) { 30 | throw new RuntimeException(e); 31 | } 32 | } 33 | 34 | @Override 35 | public Capabilities getCapabilities() { 36 | MutableCapabilities mutableCapabilities = new MutableCapabilities(capabilities); 37 | if (BROWSER_VERSION.isSpecified()) { 38 | mutableCapabilities.setCapability("version", BROWSER_VERSION.getValue()); 39 | } 40 | if (PLATFORM.isSpecified()) { 41 | mutableCapabilities.setCapability("platform", PLATFORM_VERSION.getValue()); 42 | } 43 | if (APPLICATION_NAME.isSpecified()) { 44 | mutableCapabilities.setCapability("applicationName", APPLICATION_NAME.getValue()); 45 | } 46 | return mutableCapabilities; 47 | } 48 | 49 | @Override 50 | public WebDriver getWebDriver(Capabilities capabilities) { 51 | return new RemoteWebDriver(remoteURL, capabilities); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/driver/drivers/InternetExplorerImpl.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.driver.drivers; 2 | 3 | import com.frameworkium.core.ui.driver.AbstractDriver; 4 | import org.openqa.selenium.Capabilities; 5 | import org.openqa.selenium.WebDriver; 6 | import org.openqa.selenium.ie.InternetExplorerDriver; 7 | import org.openqa.selenium.ie.InternetExplorerOptions; 8 | import org.openqa.selenium.remote.CapabilityType; 9 | 10 | public class InternetExplorerImpl extends AbstractDriver { 11 | 12 | @Override 13 | public InternetExplorerOptions getCapabilities() { 14 | InternetExplorerOptions ieOptions = new InternetExplorerOptions(); 15 | ieOptions.setCapability(CapabilityType.ForSeleniumServer.ENSURING_CLEAN_SESSION, true); 16 | ieOptions.setCapability(InternetExplorerDriver.ENABLE_PERSISTENT_HOVERING, true); 17 | ieOptions.setCapability("requireWindowFocus", true); 18 | return ieOptions; 19 | } 20 | 21 | @Override 22 | public WebDriver getWebDriver(Capabilities capabilities) { 23 | return new InternetExplorerDriver(new InternetExplorerOptions(capabilities)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/driver/drivers/SafariImpl.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.driver.drivers; 2 | 3 | import com.frameworkium.core.ui.driver.AbstractDriver; 4 | import org.openqa.selenium.Capabilities; 5 | import org.openqa.selenium.WebDriver; 6 | import org.openqa.selenium.safari.SafariDriver; 7 | import org.openqa.selenium.safari.SafariOptions; 8 | 9 | public class SafariImpl extends AbstractDriver { 10 | 11 | @Override 12 | public SafariOptions getCapabilities() { 13 | var safariOptions = new SafariOptions(); 14 | safariOptions.setCapability("safari.cleanSession", true); 15 | return safariOptions; 16 | } 17 | 18 | @Override 19 | public WebDriver getWebDriver(Capabilities capabilities) { 20 | return new SafariDriver(new SafariOptions(capabilities)); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/driver/lifecycle/DriverLifecycle.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.driver.lifecycle; 2 | 3 | import com.frameworkium.core.ui.driver.Driver; 4 | import java.util.function.Supplier; 5 | import org.openqa.selenium.WebDriver; 6 | 7 | /** 8 | * Controls the lifecycle of the {@link Driver}(s). 9 | * 10 | *

The methods need to be called in order: 11 | *

    12 | *
  1. {@link #initDriverPool(Supplier)} 13 | * (once until {@link #tearDownDriverPool()} has been called) 14 | *
  2. {@link #initBrowserBeforeTest(Supplier)} 15 | * (once until {@link #tearDownDriver()} has been called) 16 | *
  3. {@link #getWebDriver()} (n times only after the above and before the below) 17 | *
  4. {@link #tearDownDriver()} 18 | * (once after {@link #initBrowserBeforeTest(Supplier)} has been called) 19 | *
  5. {@link #tearDownDriverPool()} (once but multiple calls do nothing) 20 | *
21 | */ 22 | public interface DriverLifecycle { 23 | 24 | /** 25 | * Will initialise a pool of {@link Driver}s if required. 26 | * 27 | * @param driverSupplier the {@link Supplier} that creates {@link Driver}s 28 | * @throws IllegalStateException if trying to re-initialise existing pool 29 | */ 30 | default void initDriverPool(Supplier driverSupplier) { 31 | } 32 | 33 | /** 34 | * Will set the current {@link ThreadLocal} {@link Driver} to be the next 35 | * available from the pool or will add the {@link Driver} created by the 36 | * supplied {@link Supplier}. 37 | * 38 | * @param driverSupplier the {@link Supplier} that creates {@link Driver}s 39 | * @throws java.util.NoSuchElementException if this pool is empty 40 | */ 41 | void initBrowserBeforeTest(Supplier driverSupplier); 42 | 43 | /** 44 | * @return the {@link WebDriver} in use by the current thread. 45 | * @throws NullPointerException if called before 46 | * {@link #initBrowserBeforeTest(Supplier)} or 47 | * after {@link #tearDownDriver()}. 48 | */ 49 | WebDriver getWebDriver(); 50 | 51 | /** 52 | * Tears down the driver, ready for reinitialisation, if required, by 53 | * {@link #initBrowserBeforeTest(Supplier)}. 54 | */ 55 | void tearDownDriver(); 56 | 57 | /** 58 | * Clears the driver pool, if exists, ready to run run 59 | * {@link #initDriverPool(Supplier)} again if required. 60 | */ 61 | default void tearDownDriverPool() { 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/driver/lifecycle/SingleUseDriverLifecycle.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.driver.lifecycle; 2 | 3 | import com.frameworkium.core.ui.driver.Driver; 4 | import java.util.function.Supplier; 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | import org.openqa.selenium.WebDriver; 8 | 9 | /** 10 | * {@link #initDriverPool(Supplier)} and {@link #tearDownDriverPool()} do not do 11 | * anything for {@link SingleUseDriverLifecycle} and can be omitted. 12 | * 13 | * @see DriverLifecycle 14 | */ 15 | public class SingleUseDriverLifecycle implements DriverLifecycle { 16 | 17 | private static final Logger logger = LogManager.getLogger(); 18 | 19 | private static final ThreadLocal threadLocalDriver = new ThreadLocal<>(); 20 | 21 | /** 22 | * Sets the {@link Driver} created by the supplied {@link Supplier} to the 23 | * {@link ThreadLocal} driver. 24 | * 25 | * @param driverSupplier the {@link Supplier} that creates {@link Driver}s 26 | */ 27 | @Override 28 | public void initBrowserBeforeTest(Supplier driverSupplier) { 29 | threadLocalDriver.set(driverSupplier.get()); 30 | } 31 | 32 | @Override 33 | public WebDriver getWebDriver() { 34 | return threadLocalDriver.get().getWebDriver().getWrappedDriver(); 35 | } 36 | 37 | /** 38 | * Calls {@code quit()} on the underlying driver. 39 | */ 40 | @Override 41 | public void tearDownDriver() { 42 | try { 43 | threadLocalDriver.get().getWebDriver().quit(); 44 | } catch (Exception e) { 45 | logger.error("Failed to quit browser."); 46 | logger.debug("Failed to quit browser", e); 47 | throw e; 48 | } finally { 49 | threadLocalDriver.remove(); 50 | } 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/driver/remotes/BrowserStack.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.driver.remotes; 2 | 3 | import static com.frameworkium.core.common.properties.Property.BROWSER_STACK; 4 | 5 | import java.net.MalformedURLException; 6 | import java.net.URL; 7 | 8 | public class BrowserStack { 9 | 10 | private BrowserStack() { 11 | // hide default constructor for this util class 12 | } 13 | 14 | public static URL getURL() throws MalformedURLException { 15 | return new URL(String.format("https://%s:%s@hub-cloud.browserstack.com/wd/hub", 16 | System.getenv("BROWSER_STACK_USERNAME"), 17 | System.getenv("BROWSER_STACK_ACCESS_KEY"))); 18 | } 19 | 20 | public static boolean isDesired() { 21 | return BROWSER_STACK.getBoolean(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/driver/remotes/Sauce.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.driver.remotes; 2 | 3 | import com.frameworkium.core.common.properties.Property; 4 | import com.google.common.collect.ImmutableMap; 5 | import com.saucelabs.common.SauceOnDemandAuthentication; 6 | import com.saucelabs.common.SauceOnDemandSessionIdProvider; 7 | import com.saucelabs.saucerest.SauceREST; 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.net.MalformedURLException; 11 | import java.net.URL; 12 | 13 | public class Sauce { 14 | 15 | private static final SauceOnDemandAuthentication sauceAuth = 16 | new SauceOnDemandAuthentication( 17 | System.getenv("SAUCE_USERNAME"), 18 | System.getenv("SAUCE_ACCESS_KEY")); 19 | 20 | private static final SauceREST client = 21 | new SauceREST( 22 | sauceAuth.getUsername(), 23 | sauceAuth.getAccessKey()); 24 | 25 | public static URL getURL() { 26 | try { 27 | return new URL(String.format( 28 | "https://%s:%s@ondemand.saucelabs.com/wd/hub", 29 | sauceAuth.getUsername(), 30 | sauceAuth.getAccessKey())); 31 | } catch (MalformedURLException e) { 32 | throw new IllegalArgumentException(e); 33 | } 34 | } 35 | 36 | public static boolean isDesired() { 37 | return Property.SAUCE.getBoolean(); 38 | } 39 | 40 | public static void updateJobName(SauceOnDemandSessionIdProvider sessionIdProvider, String name) { 41 | 42 | client.updateJobInfo( 43 | sessionIdProvider.getSessionId(), 44 | ImmutableMap.of("name", name)); 45 | } 46 | 47 | public static void uploadFile(File file) throws IOException { 48 | client.uploadFile(file); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/element/OptimisedStreamTable.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.element; 2 | 3 | import java.util.List; 4 | import java.util.stream.Stream; 5 | import org.openqa.selenium.By; 6 | import org.openqa.selenium.WebElement; 7 | import org.openqa.selenium.support.FindBy; 8 | 9 | /** 10 | * {@link OptimisedStreamTable} is an {@link AbstractStreamTable}. 11 | * 12 | *

Along with the assumptions made by {@link AbstractStreamTable} this class 13 | * assumes the header cells are all selectable by {@code thead > tr > th}, 14 | * the rows are all selectable by {@code tbody > tr} and cells inside the rows 15 | * are selectable by {@code td}. 16 | * 17 | *

{@link OptimisedStreamTable} is approximately twice as fast as 18 | * {@link StreamTable} but it cannot cope with hidden columns or rows. 19 | */ 20 | public class OptimisedStreamTable extends AbstractStreamTable { 21 | 22 | @FindBy(css = "thead > tr > th") 23 | private List headerCells; 24 | 25 | @FindBy(css = "tbody > tr") 26 | private List rows; 27 | 28 | @Override 29 | protected Stream headerCells() { 30 | return headerCells.stream(); 31 | } 32 | 33 | @Override 34 | protected Stream rows() { 35 | return rows.stream(); 36 | } 37 | 38 | @Override 39 | protected By cellLocator() { 40 | return By.cssSelector("td"); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/element/StreamTable.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.element; 2 | 3 | import java.util.List; 4 | import java.util.stream.Stream; 5 | import org.openqa.selenium.By; 6 | import org.openqa.selenium.WebElement; 7 | import org.openqa.selenium.support.FindBy; 8 | 9 | /** 10 | * {@link StreamTable} is an {@link AbstractStreamTable}. 11 | * 12 | *

Along with the assumptions made by {@link AbstractStreamTable} this class 13 | * assumes the header cells are all selectable by {@code thead > tr > th}. 14 | * the rows are all selectable by {@code tbody > tr} and cells inside the rows 15 | * are selectable by {@code td}. 16 | * 17 | *

It can also cope where entire columns or rows are hidden. 18 | */ 19 | public class StreamTable extends AbstractStreamTable { 20 | 21 | @FindBy(css = "thead > tr > th") 22 | private List headerCells; 23 | 24 | @FindBy(css = "tbody > tr") 25 | private List rows; 26 | 27 | @Override 28 | protected Stream headerCells() { 29 | return headerCells.stream().filter(WebElement::isDisplayed); 30 | } 31 | 32 | @Override 33 | protected Stream rows() { 34 | return rows.stream().filter(WebElement::isDisplayed); 35 | } 36 | 37 | @Override 38 | protected By cellLocator() { 39 | return By.cssSelector("td"); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/js/JavascriptWait.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.js; 2 | 3 | import com.frameworkium.core.ui.ExtraExpectedConditions; 4 | import com.paulhammant.ngwebdriver.NgWebDriver; 5 | import org.openqa.selenium.JavascriptExecutor; 6 | import org.openqa.selenium.WebDriver; 7 | import org.openqa.selenium.support.ui.Wait; 8 | 9 | /** 10 | * Frameworkium implementation of waiting for JS events on page-load. 11 | */ 12 | public class JavascriptWait { 13 | 14 | private final Wait wait; 15 | private final JavascriptExecutor javascriptExecutor; 16 | 17 | public JavascriptWait( 18 | JavascriptExecutor javascriptExecutor, Wait wait) { 19 | this.wait = wait; 20 | this.javascriptExecutor = javascriptExecutor; 21 | } 22 | 23 | /** 24 | * Default entry to {@link JavascriptWait}. 25 | * The following actions are waited for: 26 | *

    27 | *
  1. Document state to be ready
  2. 28 | *
  3. If page is using Angular, it will detect and wait
  4. 29 | *
30 | */ 31 | public void waitForJavascriptEventsOnLoad() { 32 | waitForDocumentReady(); 33 | waitForAngular(); 34 | } 35 | 36 | /** 37 | * If a page is using a supported JS framework, it will wait until it's ready. 38 | */ 39 | public void waitForJavascriptFramework() { 40 | waitForAngular(); 41 | } 42 | 43 | private void waitForDocumentReady() { 44 | wait.until(ExtraExpectedConditions.documentBodyReady()); 45 | } 46 | 47 | private void waitForAngular() { 48 | new NgWebDriver(javascriptExecutor).waitForAngularRequestsToFinish(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/listeners/SauceLabsListener.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.listeners; 2 | 3 | import static com.frameworkium.core.common.properties.Property.APP_PATH; 4 | 5 | import com.frameworkium.core.ui.driver.Driver; 6 | import com.frameworkium.core.ui.driver.remotes.Sauce; 7 | import com.saucelabs.common.SauceOnDemandSessionIdProvider; 8 | import com.saucelabs.testng.SauceOnDemandTestListener; 9 | import java.io.File; 10 | import java.io.IOException; 11 | import org.apache.logging.log4j.LogManager; 12 | import org.apache.logging.log4j.Logger; 13 | import org.testng.ITestContext; 14 | import org.testng.ITestResult; 15 | 16 | public class SauceLabsListener extends SauceOnDemandTestListener { 17 | 18 | private static final Logger logger = LogManager.getLogger(); 19 | 20 | private static final boolean IS_RUNNING_ON_SAUCE_LABS = Sauce.isDesired(); 21 | 22 | @Override 23 | public void onStart(ITestContext testContext) { 24 | if (IS_RUNNING_ON_SAUCE_LABS) { 25 | super.onStart(testContext); 26 | 27 | if (Driver.isNative()) { 28 | try { 29 | Sauce.uploadFile(new File(APP_PATH.getValue())); 30 | } catch (IOException ioe) { 31 | logger.error("Error uploading file", ioe); 32 | } 33 | } 34 | } 35 | } 36 | 37 | @Override 38 | public void onTestStart(ITestResult result) { 39 | if (IS_RUNNING_ON_SAUCE_LABS) { 40 | // TODO: thread safe? 41 | Sauce.updateJobName( 42 | (SauceOnDemandSessionIdProvider) result.getInstance(), 43 | result.getTestClass().getRealClass().getSimpleName()); 44 | 45 | super.onTestStart(result); 46 | } 47 | } 48 | 49 | @Override 50 | public void onTestFailure(ITestResult tr) { 51 | if (IS_RUNNING_ON_SAUCE_LABS) { 52 | super.onTestFailure(tr); 53 | } 54 | } 55 | 56 | @Override 57 | public void onTestSuccess(ITestResult tr) { 58 | if (IS_RUNNING_ON_SAUCE_LABS) { 59 | super.onTestSuccess(tr); 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/listeners/VideoListener.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.listeners; 2 | 3 | import com.frameworkium.core.ui.UITestLifecycle; 4 | import com.frameworkium.core.ui.video.VideoCapture; 5 | import org.testng.ITestContext; 6 | import org.testng.ITestResult; 7 | import org.testng.TestListenerAdapter; 8 | 9 | public class VideoListener extends TestListenerAdapter { 10 | 11 | @Override 12 | public void onTestStart(ITestResult iTestResult) { 13 | if (VideoCapture.isRequired()) { 14 | VideoCapture.saveTestSessionID( 15 | iTestResult.getName(), 16 | UITestLifecycle.get().getRemoteSessionId()); 17 | } 18 | } 19 | 20 | @Override 21 | public void onFinish(ITestContext iTestContext) { 22 | if (VideoCapture.isRequired()) { 23 | VideoCapture videoCapture = new VideoCapture(); 24 | iTestContext 25 | .getFailedTests() 26 | .getAllResults() 27 | .stream() 28 | .map(ITestResult::getName) 29 | .forEach(videoCapture::fetchAndSaveVideo); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/pages/PageFactory.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.pages; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.time.Duration; 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | 8 | public class PageFactory { 9 | 10 | private static final Logger logger = LogManager.getLogger(); 11 | 12 | protected PageFactory() { 13 | } 14 | 15 | public static > T newInstance(Class clazz) { 16 | return instantiatePageObject(clazz).get(); 17 | } 18 | 19 | public static > T newInstance( 20 | Class clazz, Duration timeout) { 21 | return instantiatePageObject(clazz).get(timeout); 22 | } 23 | 24 | public static > T newInstance( 25 | Class clazz, String url) { 26 | return instantiatePageObject(clazz).get(url); 27 | } 28 | 29 | public static > T newInstance( 30 | Class clazz, String url, Duration timeout) { 31 | return instantiatePageObject(clazz).get(url, timeout); 32 | } 33 | 34 | private static > T instantiatePageObject(Class clazz) { 35 | try { 36 | return clazz.getDeclaredConstructor().newInstance(); 37 | } catch (InstantiationException | IllegalAccessException 38 | | NoSuchMethodException | InvocationTargetException e) { 39 | logger.fatal("Unable to instantiate PageObject", e); 40 | throw new IllegalStateException("Unable to instantiate PageObject", e); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/proxy/SeleniumProxyFactory.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.proxy; 2 | 3 | import java.net.URI; 4 | import java.net.URISyntaxException; 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | import org.openqa.selenium.Proxy; 8 | 9 | public class SeleniumProxyFactory { 10 | 11 | private static final Logger logger = LogManager.getLogger(); 12 | 13 | /** 14 | * Valid inputs are system, autodetect, direct or http://{hostname}:{port} 15 | * 16 | *

This does not currently cope with PAC (Proxy auto-configuration from URL) 17 | * 18 | * @param proxyProperty the string representing the proxy required 19 | * @return a Selenium {@link Proxy} representation of proxyProperty 20 | */ 21 | public static Proxy createProxy(String proxyProperty) { 22 | Proxy proxy = new Proxy(); 23 | switch (proxyProperty.toLowerCase()) { 24 | case "system": 25 | logger.debug("Using system proxy"); 26 | proxy.setProxyType(Proxy.ProxyType.SYSTEM); 27 | break; 28 | case "autodetect": 29 | logger.debug("Using autodetect proxy"); 30 | proxy.setProxyType(Proxy.ProxyType.AUTODETECT); 31 | break; 32 | case "direct": 33 | logger.debug("Using direct i.e. (no) proxy"); 34 | proxy.setProxyType(Proxy.ProxyType.DIRECT); 35 | break; 36 | default: 37 | return createManualProxy(proxyProperty); 38 | } 39 | return proxy; 40 | } 41 | 42 | private static Proxy createManualProxy(String proxyProperty) { 43 | String proxyString = getProxyURL(proxyProperty); 44 | logger.debug("All protocols to use proxy address: {}", proxyString); 45 | Proxy proxy = new Proxy(); 46 | proxy.setProxyType(Proxy.ProxyType.MANUAL) 47 | .setHttpProxy(proxyString) 48 | .setFtpProxy(proxyString) 49 | .setSslProxy(proxyString); 50 | return proxy; 51 | } 52 | 53 | private static String getProxyURL(String proxyProperty) { 54 | try { 55 | URI proxyURI = new URI(proxyProperty); 56 | String host = proxyURI.getHost(); 57 | int port = proxyURI.getPort(); 58 | if (host == null || port == -1) { 59 | throw new URISyntaxException( 60 | proxyProperty, "invalid host or port"); 61 | } 62 | return String.format("%s:%d", host, port); 63 | } catch (NullPointerException | URISyntaxException e) { 64 | String message = "Invalid proxy specified, acceptable values are: " 65 | + "system, autodetect, direct or http://{hostname}:{port}."; 66 | logger.fatal(message); 67 | throw new IllegalArgumentException(message, e); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/frameworkium/core/ui/video/UrlFetcher.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.video; 2 | 3 | import io.restassured.RestAssured; 4 | import io.restassured.response.Response; 5 | import java.net.URL; 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.concurrent.TimeoutException; 8 | import org.apache.http.HttpStatus; 9 | import org.apache.logging.log4j.LogManager; 10 | import org.apache.logging.log4j.Logger; 11 | 12 | public class UrlFetcher { 13 | 14 | private static final Logger logger = LogManager.getLogger(); 15 | 16 | /** 17 | * @param url the url to GET 18 | * @param maxTries max number of tries to GET url 19 | * @return the bytes from the downloaded URL 20 | * @throws TimeoutException if download fails and max tries have been exceeded 21 | */ 22 | public byte[] fetchWithRetry(URL url, int maxTries) throws TimeoutException { 23 | logger.debug("Downloading: " + url); 24 | for (int i = 0; i < maxTries; i++) { 25 | Response response = RestAssured.get(url); 26 | if (response.getStatusCode() == HttpStatus.SC_OK) { 27 | return response.asByteArray(); 28 | } 29 | logger.debug("Retrying download: " + url); 30 | 31 | try { 32 | TimeUnit.SECONDS.sleep(2); 33 | } catch (InterruptedException e) { 34 | throw new IllegalStateException(e); 35 | } 36 | } 37 | throw new TimeoutException(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/Empty.properties: -------------------------------------------------------------------------------- 1 | applicationName= 2 | appPath= 3 | browser= 4 | browserStack= 5 | browserVersion= 6 | build= 7 | captureURL= 8 | customBrowserImpl= 9 | device= 10 | gridURL= 11 | headless= 12 | jiraPassword= 13 | jiraResultFieldName= 14 | jiraResultTransition= 15 | jiraURL= 16 | jiraUsername= 17 | jqlQuery= 18 | maximise= 19 | maxRetryCount= 20 | platform= 21 | platformVersion= 22 | proxy= 23 | resolution= 24 | resultVersion= 25 | reuseBrowser= 26 | sauce= 27 | spiraURL= 28 | sutName= 29 | sutVersion= 30 | threads= 31 | videoCaptureUrl= 32 | zapiCycleRegEx= -------------------------------------------------------------------------------- /src/main/resources/Empty.yaml: -------------------------------------------------------------------------------- 1 | applicationName: 2 | appPath: 3 | browser: 4 | browserStack: 5 | browserVersion: 6 | build: 7 | captureURL: 8 | customBrowserImpl: 9 | device: 10 | gridURL: 11 | headless: 12 | jiraPassword: 13 | jiraResultFieldName: 14 | jiraResultTransition: 15 | jiraURL: 16 | jiraUsername: 17 | jqlQuery: 18 | maximise: 19 | maxRetryCount: 20 | platform: 21 | platformVersion: 22 | proxy: 23 | resolution: 24 | resultVersion: 25 | reuseBrowser: 26 | sauce: 27 | spiraURL: 28 | sutName: 29 | sutVersion: 30 | threads: 31 | videoCaptureUrl: 32 | zapiCycleRegEx: -------------------------------------------------------------------------------- /src/main/resources/FirefoxGrid.yaml: -------------------------------------------------------------------------------- 1 | applicationName: 2 | appPath: 3 | browser: Firefox 4 | browserStack: 5 | browserVersion: 6 | build: 7 | captureURL: 8 | customBrowserImpl: 9 | device: 10 | gridURL: http://localhost:4444/wd/hub 11 | headless: 12 | jiraPassword: 13 | jiraResultFieldName: 14 | jiraResultTransition: 15 | jiraURL: 16 | jiraUsername: 17 | jqlQuery: 18 | maximise: 19 | maxRetryCount: 2 20 | platform: 21 | platformVersion: 22 | proxy: 23 | resolution: 24 | resultVersion: 25 | reuseBrowser: 26 | sauce: 27 | spiraURL: 28 | sutName: 29 | sutVersion: 30 | threads: 31 | videoCaptureUrl: 32 | zapiCycleRegEx: -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.testng.ITestNGListener: -------------------------------------------------------------------------------- 1 | io.qameta.allure.testng.AllureTestNg 2 | com.frameworkium.core.common.listeners.TestListener 3 | com.frameworkium.core.common.listeners.ResultLoggerListener 4 | com.frameworkium.core.ui.listeners.ScreenshotListener 5 | com.frameworkium.core.ui.listeners.CaptureListener 6 | com.frameworkium.core.ui.listeners.SauceLabsListener 7 | com.frameworkium.core.ui.listeners.VideoListener 8 | com.frameworkium.core.common.listeners.MethodInterceptor -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/test/groovy/com/frameworkium/core/api/dto/AbstractDTOSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.api.dto 2 | 3 | import spock.lang.Specification 4 | 5 | class AbstractDTOSpec extends Specification { 6 | 7 | def sut = new TopLevelDTO() 8 | 9 | def "two different DTOs with the same data are equal but not =="() { 10 | given: 11 | def other = new TopLevelDTO() 12 | expect: 13 | sut.equals(other) 14 | !sut.is(other) // == in Java 15 | sut.hashCode() == other.hashCode() 16 | } 17 | 18 | def "two different DTOs with different data are not equal"() { 19 | given: 20 | def other = new TopLevelDTO() 21 | other.lowLevelDTO.data = "foo" 22 | expect: 23 | sut != other 24 | !sut.is(other) // == in Java 25 | sut.hashCode() != other.hashCode() 26 | } 27 | 28 | def "DTOs of different types are not equal"() { 29 | given: 30 | def other = new LowLevelDTO() 31 | expect: 32 | sut != other 33 | !sut.is(other) // == in Java 34 | sut.hashCode() != other.hashCode() 35 | } 36 | 37 | def "a DTO is not equal to a non-DTO"() { 38 | given: 39 | def other = new Object() 40 | expect: 41 | sut != other 42 | !sut.is(other) // == in Java 43 | sut.hashCode() != other.hashCode() 44 | } 45 | 46 | def "Cloning a DTOs makes a deep not shallow clone"() { 47 | given: 48 | def clone = sut.clone() 49 | when: 50 | clone.lowLevelDTO.data = "something" 51 | then: 52 | sut.lowLevelDTO.data != "something" 53 | } 54 | 55 | def "toString() creates readable output"() { 56 | expect: 57 | sut.toString() == 'TopLevelDTO[lowLevelDTO=LowLevelDTO[data=initial],stringList=[1, a]]' 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/test/groovy/com/frameworkium/core/common/reporting/TestIdUtilsSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting 2 | 3 | import io.qameta.allure.Issue 4 | import io.qameta.allure.TmsLink 5 | import spock.lang.Specification 6 | import spock.lang.Unroll 7 | 8 | @Unroll 9 | class TestIdUtilsSpec extends Specification { 10 | 11 | def "GetIssueOrTmsLinkValue gets TmsLink or Issue value for method #methodName"() { 12 | when: 13 | def value = TestIdUtils.getIssueOrTmsLinkValue( 14 | TestIdData.getMethod(methodName)) 15 | then: 16 | value == expectedValue 17 | where: 18 | methodName | expectedValue 19 | "none" | Optional.empty() 20 | "tmsLink" | Optional.of("TMSLink") 21 | "issue" | Optional.of("ISSUE") 22 | "bothSame" | Optional.of("SAME") 23 | "bothDifferent" | Optional.of("TMSLink") 24 | "multipleTmsLink" | Optional.empty() 25 | "multipleTmsLinkAndIssue" | Optional.empty() 26 | "multipleIssue" | Optional.empty() 27 | } 28 | 29 | def "GetIssueOrTmsLinkValues get TmsLink or Issue values for method #methodName"() { 30 | when: 31 | def value = TestIdUtils.getIssueOrTmsLinkValues( 32 | TestIdData.getMethod(methodName)) 33 | then: 34 | value == expectedValue 35 | where: 36 | methodName | expectedValue 37 | "none" | [] 38 | "tmsLink" | ["TMSLink"] 39 | "issue" | ["ISSUE"] 40 | "bothSame" | ["SAME"] 41 | "bothDifferent" | ["TMSLink"] 42 | "multipleTmsLink" | ["TMSLink1", "TMSLink2"] 43 | "multipleTmsLinkAndIssue" | ["TMSLink3", "TMSLink4"] 44 | "multipleIssue" | ["Issue3", "Issue4"] 45 | } 46 | 47 | class TestIdData { 48 | 49 | void none() {} 50 | 51 | @TmsLink("TMSLink") 52 | void tmsLink() {} 53 | 54 | @Issue("ISSUE") 55 | void issue() {} 56 | 57 | @TmsLink("SAME") 58 | @Issue("SAME") 59 | void bothSame() {} 60 | 61 | @TmsLink("TMSLink") 62 | @Issue("ISSUE") 63 | void bothDifferent() {} 64 | 65 | @TmsLink("TMSLink1") 66 | @TmsLink("TMSLink2") 67 | void multipleTmsLink() {} 68 | 69 | @Issue("Issue1") 70 | @Issue("Issue2") 71 | @TmsLink("TMSLink3") 72 | @TmsLink("TMSLink4") 73 | void multipleTmsLinkAndIssue() {} 74 | 75 | @Issue("Issue3") 76 | @Issue("Issue4") 77 | void multipleIssue() {} 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /src/test/groovy/com/frameworkium/core/common/reporting/jira/service/AttachmentSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.service 2 | 3 | 4 | import groovy.json.JsonBuilder 5 | import io.restassured.path.json.JsonPath 6 | import spock.lang.Specification 7 | 8 | class AttachmentSpec extends Specification { 9 | def "Get attachment Ids"() { 10 | given: 11 | String issueKey = "KEY-${UUID.randomUUID().toString()}" 12 | JsonPath mockedResponse = createMockedResponse(issueKey) 13 | Issue mockedIssue = Stub(Issue) 14 | Attachment attachment = new Attachment(mockedIssue) 15 | when: 16 | mockedIssue.getIssue() >> mockedResponse 17 | List ids = attachment.getIds() 18 | then: 19 | with(ids) { 20 | ids == ["10000", "10001"] 21 | } 22 | } 23 | 24 | private static JsonPath createMockedResponse(String issueKey) { 25 | def obj = ["id" : "6767", 26 | "key" : issueKey, 27 | "fields": [ 28 | "watcher" : ["isWatching": false, 29 | "watchCount": 1], 30 | "attachment": [[ 31 | "id" : "10000", 32 | "filename" : "picture1.jpg", 33 | "created" : "2017-12-07T09:23:19.542+0000", 34 | "size" : 23123, 35 | "mimeType" : "image/jpeg", 36 | "content" : "http://www.example.com/jira/attachments/10000", 37 | "thumbnail": "http://www.example.com/jira/secure/thumbnail/10000" 38 | ], 39 | [ 40 | "id" : "10001", 41 | "filename" : "picture2.jpg", 42 | "created" : "2017-11-11T09:23:19.542+0000", 43 | "size" : 45644, 44 | "mimeType" : "image/jpeg", 45 | "content" : "http://www.example.com/jira/attachments/10001", 46 | "thumbnail": "http://www.example.com/jira/secure/thumbnail/10001" 47 | ] 48 | ] 49 | ] 50 | ] 51 | String body = new JsonBuilder(obj).toString() 52 | JsonPath jsonPath = JsonPath.from(body) 53 | return jsonPath 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/groovy/com/frameworkium/core/common/reporting/jira/service/IssueLinkSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.service 2 | 3 | import com.frameworkium.core.common.reporting.jira.endpoint.JiraEndpoint 4 | import com.github.tomakehurst.wiremock.client.WireMock 5 | import com.github.tomakehurst.wiremock.verification.LoggedRequest 6 | import groovy.json.JsonBuilder 7 | import spock.lang.Shared 8 | import spock.lang.Specification 9 | 10 | import static com.github.tomakehurst.wiremock.client.WireMock.* 11 | import static com.github.tomakehurst.wiremock.common.Metadata.metadata 12 | 13 | class IssueLinkSpec extends Specification { 14 | String stubId = UUID.randomUUID().toString() // to uniquely identify stub for cleanup later 15 | @Shared 16 | WireMock wireMock = new WireMock("localhost", 8080) 17 | 18 | IssueLink issueLink = new IssueLink() 19 | String type = "Duplicate" 20 | final String inwardIssue = "KEY-${UUID.randomUUID().toString()}" 21 | final String outwardIssue = "KEY-${UUID.randomUUID().toString()}" 22 | 23 | def setupSpec() { 24 | System.properties["jiraURL"] = "http://localhost:8080" 25 | System.properties["jiraUsername"] = "username" 26 | System.properties["jiraPassword"] = "password" 27 | } 28 | 29 | def cleanup() { 30 | wireMock.removeStubsByMetadataPattern(matchingJsonPath(/$.id/, equalTo(stubId))) 31 | } 32 | 33 | def "Create link between two JIRA issues"() { 34 | given: 35 | String issueLinkUrl = JiraEndpoint.ISSUELINK.getUrl() 36 | def response = createMockedResponse(outwardIssue, inwardIssue, type) 37 | wireMock.register(post(urlPathEqualTo(issueLinkUrl)) 38 | .withMetadata(metadata().attr("id", stubId)) //used for remove stub at cleanup 39 | .withRequestBody(equalToJson(response)) 40 | .willReturn(aResponse().withStatus(201)) 41 | ) 42 | when: 43 | issueLink.linkIssues(type, inwardIssue, outwardIssue) 44 | then: 45 | List loggedRequests = wireMock.find(postRequestedFor(urlPathMatching(issueLinkUrl)) 46 | .withRequestBody(equalTo(response))) 47 | loggedRequests[0].bodyAsString == response 48 | } 49 | 50 | private static def createMockedResponse(String outwardIssue, String inwardIssue, String type) { 51 | def obj = [ 52 | "outwardIssue": ["key": outwardIssue], 53 | "inwardIssue" : ["key": inwardIssue], 54 | "type" : ["name": type] 55 | ] 56 | return new JsonBuilder(obj).toString() 57 | } 58 | } -------------------------------------------------------------------------------- /src/test/groovy/com/frameworkium/core/common/reporting/jira/service/SearchSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.reporting.jira.service 2 | 3 | import com.frameworkium.core.common.reporting.jira.endpoint.JiraEndpoint 4 | import com.github.tomakehurst.wiremock.client.WireMock 5 | import groovy.json.JsonBuilder 6 | import spock.lang.Shared 7 | import spock.lang.Specification 8 | 9 | import static com.github.tomakehurst.wiremock.client.WireMock.* 10 | import static com.github.tomakehurst.wiremock.common.Metadata.metadata 11 | 12 | class SearchSpec extends Specification { 13 | String stubId = UUID.randomUUID().toString() // to uniquely identify stub for cleanup later 14 | @Shared 15 | WireMock wireMock = new WireMock("localhost", 8080) 16 | 17 | def setupSpec() { 18 | System.properties["jiraURL"] = "http://localhost:8080" 19 | System.properties["jiraUsername"] = "username" 20 | System.properties["jiraPassword"] = "password" 21 | } 22 | 23 | def cleanup() { 24 | wireMock.removeStubsByMetadataPattern(matchingJsonPath(/$.id/, equalTo(stubId))) 25 | } 26 | 27 | def "Getting keys from JQL JIRA search"() { 28 | given: 29 | String searchTerm = "ISearchForThis" 30 | def searchResult = createMockedSearchResultResponse() 31 | wireMock.register(get(urlPathEqualTo(JiraEndpoint.SEARCH.getUrl())) 32 | .withMetadata(metadata().attr("id", stubId)) 33 | .withQueryParam("jql", equalTo(searchTerm)) 34 | .withQueryParam("startAt", equalTo("0")) 35 | .withQueryParam("maxResults", equalTo("1000")) 36 | .willReturn(aResponse().withBody(searchResult) 37 | .withStatus(200)) 38 | ) 39 | when: 40 | def searchService = new Search(searchTerm) 41 | then: 42 | searchService.getKeys() == ["KEY-1", "KEY-2"] 43 | } 44 | 45 | private static def createMockedSearchResultResponse() { 46 | def obj = [ 47 | "startAt" : 0, 48 | "maxResults": 1000, 49 | "total" : 2, 50 | "issues" : [ 51 | ["id" : "10001", 52 | "key": "KEY-1" 53 | ], 54 | ["id" : "10002", 55 | "key": "KEY-2" 56 | ] 57 | ] 58 | ] 59 | return new JsonBuilder(obj).toString() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/groovy/com/frameworkium/core/common/retry/RetryFlakyTestSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.common.retry 2 | 3 | import org.testng.ITestResult 4 | import spock.lang.Specification 5 | 6 | class RetryFlakyTestSpec extends Specification { 7 | 8 | def "Retry will return true if there are retries remaining"() { 9 | given: 10 | def mockResult = Mock(ITestResult) 11 | def sut = new RetryFlakyTest() 12 | expect: 13 | RetryFlakyTest.MAX_RETRY_COUNT.times { assert sut.retry(mockResult) } 14 | !sut.retry(mockResult) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/groovy/com/frameworkium/core/ui/capture/ElementHighlighterSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.capture 2 | 3 | import org.openqa.selenium.StaleElementReferenceException 4 | import org.openqa.selenium.WebDriver 5 | import org.openqa.selenium.WebElement 6 | import org.openqa.selenium.support.events.EventFiringWebDriver 7 | import spock.lang.Specification 8 | 9 | class ElementHighlighterSpec extends Specification { 10 | 11 | def mockWDWrapper = Mock(EventFiringWebDriver, constructorArgs: [Mock(WebDriver)]) 12 | def mockElement = Mock(WebElement) 13 | 14 | ElementHighlighter sut = new ElementHighlighter(mockWDWrapper) 15 | 16 | def "provided element is highlighted and same element is un-highlighted"() { 17 | given: "The Javascript we expect to run" 18 | def highlightJS = "arguments[0].style.border='3px solid red'" 19 | def unhighlightJS = "arguments[0].style.border='none'" 20 | when: "We highlight then un-highlight an element" 21 | sut.highlightElement(mockElement) 22 | sut.unhighlightPrevious() 23 | then: "The correct scripts are executed against the given element" 24 | 1 * mockWDWrapper.executeScript(highlightJS, mockElement) 25 | 1 * mockWDWrapper.executeScript(unhighlightJS, mockElement) 26 | } 27 | 28 | def "StaleElementReferenceException's are caught"() { 29 | when: "We highlight then un-highlight an element" 30 | sut.highlightElement(mockElement) 31 | sut.unhighlightPrevious() 32 | then: "StaleElementReferenceException are not thrown" 33 | 2 * mockWDWrapper.executeScript(_ as String, mockElement) >> { 34 | throw new StaleElementReferenceException("") 35 | } 36 | notThrown(StaleElementReferenceException) 37 | } 38 | } -------------------------------------------------------------------------------- /src/test/groovy/com/frameworkium/core/ui/driver/lifecycle/MultiUseDriverLifecycleSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.driver.lifecycle 2 | 3 | import com.frameworkium.core.ui.driver.Driver 4 | import org.openqa.selenium.WebDriver 5 | import org.openqa.selenium.support.events.EventFiringWebDriver 6 | import spock.lang.Specification 7 | import spock.lang.Unroll 8 | 9 | @Unroll 10 | class MultiUseDriverLifecycleSpec extends Specification { 11 | 12 | def webDriverStub = Stub(WebDriver) 13 | def EFWebDriverMock = 14 | Mock(constructorArgs: [webDriverStub], EventFiringWebDriver) { 15 | getWrappedDriver() >> webDriverStub 16 | } 17 | def driverMock = Mock(Driver) { 18 | getWebDriver() >> EFWebDriverMock 19 | } 20 | def driverSupplier = { driverMock } 21 | 22 | def "following expected lifecycle yields correct driver with MultiUseDriverLifecycle(#poolSize)"() { 23 | given: 24 | def sut = new MultiUseDriverLifecycle(poolSize) 25 | when: 26 | sut.initDriverPool(driverSupplier) 27 | sut.initBrowserBeforeTest(driverSupplier) 28 | assert sut.getWebDriver() == webDriverStub 29 | sut.tearDownDriver() 30 | sut.tearDownDriverPool() 31 | then: 32 | EFWebDriverMock.manage() >> Stub(WebDriver.Options) 33 | poolSize * EFWebDriverMock.quit() 34 | noExceptionThrown() 35 | where: 36 | poolSize << [1, 5] 37 | } 38 | 39 | def "if a driver fails to tearDown exception will be thrown"() { 40 | given: 41 | def sut = new MultiUseDriverLifecycle(1) 42 | sut.initDriverPool(driverSupplier) 43 | sut.initBrowserBeforeTest(driverSupplier) 44 | when: 45 | sut.tearDownDriver() 46 | then: 47 | 1 * EFWebDriverMock.manage() >> { throw new Exception("") } 48 | thrown Exception 49 | } 50 | 51 | def "throwing exception on quit does not prevent subsequent drivers quitting"() { 52 | given: 53 | def sut = new MultiUseDriverLifecycle(2) 54 | sut.initDriverPool(driverSupplier) 55 | when: 56 | sut.tearDownDriverPool() 57 | then: 58 | // throw one error then do nothing for subsequent calls 59 | 2 * EFWebDriverMock.quit() >>> { 60 | throw new Exception("some error") 61 | } >> {} 62 | } 63 | 64 | def "initDriverPool can only be called once"() { 65 | given: 66 | def sut = new MultiUseDriverLifecycle(1) 67 | sut.initDriverPool(driverSupplier) 68 | when: 69 | sut.initDriverPool(driverSupplier) 70 | then: 71 | thrown IllegalStateException 72 | } 73 | 74 | def "initDriverPool can only be called again after tearDownDriverPool"() { 75 | given: 76 | def sut = new MultiUseDriverLifecycle(1) 77 | sut.initDriverPool(driverSupplier) 78 | sut.tearDownDriverPool() 79 | when: 80 | sut.initDriverPool(driverSupplier) 81 | then: 82 | noExceptionThrown() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/groovy/com/frameworkium/core/ui/driver/lifecycle/SingleUseDriverLifecycleSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.driver.lifecycle 2 | 3 | import com.frameworkium.core.ui.driver.Driver 4 | import org.openqa.selenium.WebDriver 5 | import org.openqa.selenium.support.events.EventFiringWebDriver 6 | import spock.lang.Specification 7 | 8 | class SingleUseDriverLifecycleSpec extends Specification { 9 | 10 | def webDriverStub = Stub(WebDriver) 11 | def EFWebDriverMock = 12 | Mock(constructorArgs: [webDriverStub], EventFiringWebDriver) { 13 | getWrappedDriver() >> webDriverStub 14 | } 15 | def driverMock = Mock(Driver) { 16 | getWebDriver() >> EFWebDriverMock 17 | } 18 | def driverSupplier = { driverMock } 19 | 20 | def sut = new SingleUseDriverLifecycle() 21 | 22 | 23 | def "following expected lifecycle yields correct driver"() { 24 | when: 25 | sut.initDriverPool(driverSupplier) 26 | sut.initBrowserBeforeTest(driverSupplier) 27 | assert sut.getWebDriver() == webDriverStub 28 | sut.tearDownDriver() 29 | sut.tearDownDriverPool() 30 | then: 31 | 1 * EFWebDriverMock.quit() 32 | noExceptionThrown() 33 | } 34 | 35 | def "following subset of expected lifecycle yields correct driver for SingleUseDriverLifecycle"() { 36 | when: 37 | sut.initBrowserBeforeTest(driverSupplier) 38 | assert sut.getWebDriver() == webDriverStub 39 | sut.tearDownDriver() 40 | then: 41 | 1 * EFWebDriverMock.quit() 42 | noExceptionThrown() 43 | } 44 | 45 | def "if a driver fails to tearDown exception will be thrown"() { 46 | given: 47 | sut.initBrowserBeforeTest(driverSupplier) 48 | when: 49 | sut.tearDownDriver() 50 | then: 51 | 1 * EFWebDriverMock.quit() >> { throw new Exception("") } 52 | thrown Exception 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/groovy/com/frameworkium/core/ui/listeners/ScreenshotListenerSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.listeners 2 | 3 | import org.openqa.selenium.TakesScreenshot 4 | import spock.lang.Specification 5 | 6 | import java.nio.file.Files 7 | import java.nio.file.Paths 8 | 9 | class ScreenshotListenerSpec extends Specification { 10 | 11 | def testName = "testName" 12 | def defaultScreenshotFolder = Paths.get("screenshots") 13 | 14 | def setup() { 15 | Files.createDirectories(defaultScreenshotFolder) 16 | } 17 | 18 | def cleanup() { 19 | listAllTestScreenshots() 20 | .map({ it.toFile() }) 21 | .forEach({ it.delete() }) 22 | } 23 | 24 | def sut = new ScreenshotListener() 25 | 26 | def "takeScreenshotAndSaveLocally takes a screenshot and saves file"() { 27 | given: 28 | TakesScreenshot mockDriver = Mock() 29 | def screenshotCount = listAllTestScreenshots().count() 30 | when: 31 | sut.takeScreenshotAndSaveLocally(testName, mockDriver) 32 | then: 33 | 1 * mockDriver.getScreenshotAs(_) >> { [1, 2, 3] as byte[] } 34 | listAllTestScreenshots().count() == screenshotCount + 1 35 | } 36 | 37 | def listAllTestScreenshots() { 38 | Files.walk(defaultScreenshotFolder) 39 | .filter({ it.toString().endsWith(testName + ".png") }) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/test/groovy/com/frameworkium/core/ui/proxy/SeleniumProxyFactorySpec.groovy: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.proxy 2 | 3 | import org.openqa.selenium.Proxy 4 | import spock.lang.Specification 5 | import spock.lang.Unroll 6 | 7 | @Unroll 8 | class SeleniumProxyFactorySpec extends Specification { 9 | 10 | def "providing a property of (#proxyProp) gives proxy of type (#proxyType)"() { 11 | when: 12 | def proxy = SeleniumProxyFactory.createProxy(proxyProp) 13 | then: 14 | proxy.proxyType == proxyType 15 | where: 16 | proxyProp | proxyType 17 | "system" | Proxy.ProxyType.SYSTEM 18 | "autodetect" | Proxy.ProxyType.AUTODETECT 19 | "direct" | Proxy.ProxyType.DIRECT 20 | "http://host:1234" | Proxy.ProxyType.MANUAL 21 | } 22 | 23 | def "providing a proxy URL (#proxyProp) is set as expected (#proxyUrl)"() { 24 | when: 25 | def proxy = SeleniumProxyFactory.createManualProxy(proxyProp) 26 | then: 27 | proxy.proxyType == Proxy.ProxyType.MANUAL 28 | proxy.httpProxy == proxyUrl 29 | proxy.ftpProxy == proxyUrl 30 | proxy.sslProxy == proxyUrl 31 | where: 32 | proxyProp | proxyUrl 33 | "http://host:123" | "host:123" 34 | "ftp://host:123" | "host:123" 35 | } 36 | 37 | def "invalid proxy URL (#invalidProxyProp) throw exception"() { 38 | when: 39 | SeleniumProxyFactory.getProxyURL(invalidProxyProp) 40 | then: 41 | thrown(IllegalArgumentException) 42 | where: 43 | invalidProxyProp << ["host:123", "http://host", "", null] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/groovy/com/frameworkium/core/ui/video/VideoCaptureSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.video 2 | 3 | import org.apache.commons.io.FileUtils 4 | import spock.lang.Specification 5 | 6 | import java.nio.file.Files 7 | import java.util.concurrent.TimeoutException 8 | 9 | class VideoCaptureSpec extends Specification { 10 | 11 | def setup() { 12 | FileUtils.deleteDirectory(VideoCapture.VIDEO_FOLDER.toFile()) 13 | } 14 | 15 | def array = [0, 1, 0, 1] as byte[] 16 | def urlFetcherMock = Mock(UrlFetcher) { 17 | fetchWithRetry(_, _) >> array 18 | } 19 | def url = "http://foo/bar/%s.ext" 20 | 21 | 22 | def "fetches video for stored testname"() { 23 | given: 24 | def sut = new VideoCapture(url, urlFetcherMock) 25 | sut.saveTestSessionID("test1", "session1") 26 | when: 27 | sut.fetchAndSaveVideo("test1") 28 | then: 29 | def filepath = VideoCapture.VIDEO_FOLDER.resolve("test1-session1.ext") 30 | Files.readAllBytes(filepath) == array 31 | } 32 | 33 | def "doesn't save file on fetch timeout"() { 34 | given: 35 | def timingOutUrlFetcherMock = Mock(UrlFetcher) { 36 | fetchWithRetry(_, _) >> { throw new TimeoutException("test2") } 37 | } 38 | def sut = new VideoCapture(url, timingOutUrlFetcherMock) 39 | sut.saveTestSessionID("test2", "session2") 40 | when: 41 | sut.fetchAndSaveVideo("test2") 42 | then: 43 | Files.notExists(VideoCapture.VIDEO_FOLDER.resolve("test2-session2.ext")) 44 | } 45 | 46 | def "invalid url throws IllegalArgumentException"() { 47 | given: 48 | def sut = new VideoCapture("abc%^!", urlFetcherMock) 49 | sut.saveTestSessionID("test3", "session3") 50 | when: 51 | sut.fetchAndSaveVideo("test3") 52 | then: 53 | thrown IllegalArgumentException 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/core/api/dto/LowLevelDTO.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.api.dto; 2 | 3 | public class LowLevelDTO extends AbstractDTO { 4 | String data = "initial"; 5 | } 6 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/core/api/dto/TopLevelDTO.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.api.dto; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | public class TopLevelDTO extends AbstractDTO { 7 | LowLevelDTO lowLevelDTO = new LowLevelDTO(); 8 | List stringList = Arrays.asList("1", "a"); 9 | } 10 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/core/ui/ExtraExpectedConditionsTest.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui; 2 | 3 | import com.frameworkium.core.htmlelements.element.Link; 4 | import com.frameworkium.core.ui.element.StreamTable; 5 | import org.testng.annotations.Test; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Tests to ensure elements other than just WebElements can be passed into 12 | * {@link ExtraExpectedConditions}. 13 | * 14 | * These tests do not check the correctness of the implementation, they are here 15 | * to prevent https://github.com/Frameworkium/frameworkium-core/issues/133 16 | */ 17 | public class ExtraExpectedConditionsTest { 18 | 19 | private List links = new ArrayList<>(); 20 | private List tables = new ArrayList<>(); 21 | 22 | @Test 23 | public void testNotPresentOrInvisibleWorksWithTypifiedElements() { 24 | ExtraExpectedConditions.notPresentOrInvisible(links); 25 | } 26 | 27 | @Test 28 | public void testSizeGreaterThanWorksWithTypifiedElements() { 29 | ExtraExpectedConditions.sizeGreaterThan(links, 1); 30 | } 31 | 32 | @Test 33 | public void testSizeLessThanWorksWithTypifiedElements() { 34 | ExtraExpectedConditions.sizeLessThan(links, 3); 35 | } 36 | 37 | @Test 38 | public void testNotPresentOrInvisibleWorksWithHtmlElements() { 39 | ExtraExpectedConditions.notPresentOrInvisible(tables); 40 | } 41 | 42 | @Test 43 | public void testSizeGreaterThanWorksWithHtmlElements() { 44 | ExtraExpectedConditions.sizeGreaterThan(tables, 1); 45 | } 46 | 47 | @Test 48 | public void testSizeLessThanWorksWithHtmlElements() { 49 | ExtraExpectedConditions.sizeLessThan(tables, 3); 50 | } 51 | } -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/core/ui/pages/pageobjects/TestPage.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.core.ui.pages.pageobjects; 2 | 3 | import com.frameworkium.core.ui.pages.BasePage; 4 | import org.openqa.selenium.WebElement; 5 | import org.openqa.selenium.support.FindBy; 6 | 7 | /** 8 | * Java class to test page instantiation. 9 | */ 10 | public class TestPage extends BasePage { 11 | 12 | @FindBy(css = "#foo") 13 | private WebElement element; 14 | 15 | public boolean isWebElementNull() { 16 | return element == null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/CustomFirefoxImpl.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration; 2 | 3 | import com.frameworkium.core.ui.driver.AbstractDriver; 4 | import org.openqa.selenium.Capabilities; 5 | import org.openqa.selenium.WebDriver; 6 | import org.openqa.selenium.firefox.FirefoxDriver; 7 | import org.openqa.selenium.firefox.FirefoxOptions; 8 | 9 | /** 10 | * Used as a test of CustomImpl functionality 11 | */ 12 | public class CustomFirefoxImpl extends AbstractDriver { 13 | 14 | @Override 15 | public Capabilities getCapabilities() { 16 | return new FirefoxOptions(); 17 | } 18 | 19 | @Override 20 | public WebDriver getWebDriver(Capabilities capabilities) { 21 | return new FirefoxDriver(new FirefoxOptions(capabilities)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/angularjs/pages/DeveloperGuidePage.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.angularjs.pages; 2 | 3 | import com.frameworkium.core.htmlelements.element.Link; 4 | import com.frameworkium.core.htmlelements.element.TextInput; 5 | import com.frameworkium.core.ui.ExtraExpectedConditions; 6 | import com.frameworkium.core.ui.annotations.Visible; 7 | import com.frameworkium.core.ui.pages.BasePage; 8 | import com.frameworkium.core.ui.pages.PageFactory; 9 | import org.openqa.selenium.WebElement; 10 | import org.openqa.selenium.support.FindBy; 11 | 12 | 13 | public class DeveloperGuidePage extends BasePage { 14 | 15 | @Visible 16 | @FindBy(css = "input[name='as_q']") 17 | private TextInput searchField; 18 | 19 | @FindBy(linkText = "Bootstrap") 20 | private Link bootstrapSearchItem; 21 | 22 | @FindBy(id = "loading") 23 | private WebElement loading; 24 | 25 | public static DeveloperGuidePage open() { 26 | return PageFactory.newInstance( 27 | DeveloperGuidePage.class, "https://docs.angularjs.org/guide"); 28 | } 29 | 30 | public DeveloperGuidePage searchDeveloperGuide(String inputText) { 31 | searchField.sendKeys(inputText); 32 | return this; 33 | } 34 | 35 | public DeveloperGuidePage clickBootstrapSearchItem() { 36 | bootstrapSearchItem.click(); 37 | waitForJavascriptFrameworkToFinish(); 38 | wait.until(ExtraExpectedConditions.notPresentOrInvisible(loading)); 39 | return this; 40 | } 41 | 42 | public String getGuideTitle() { 43 | return getTitle(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/angularjs/tests/DocumentationTest.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.angularjs.tests; 2 | 3 | import com.frameworkium.core.common.retry.RetryFlakyTest; 4 | import com.frameworkium.core.ui.tests.BaseUITest; 5 | import com.frameworkium.integration.angularjs.pages.DeveloperGuidePage; 6 | import io.qameta.allure.TmsLink; 7 | import org.testng.annotations.Test; 8 | 9 | import static com.google.common.truth.Truth.assertThat; 10 | 11 | public class DocumentationTest extends BaseUITest { 12 | 13 | @Test(retryAnalyzer = RetryFlakyTest.class) 14 | @TmsLink("ANG-1") 15 | public void angular_documentation_test() { 16 | String guideTitle = DeveloperGuidePage.open() 17 | .searchDeveloperGuide("Bootstrap") 18 | .clickBootstrapSearchItem() 19 | .getGuideTitle(); 20 | 21 | assertThat(guideTitle) 22 | .endsWith("Bootstrap"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/capture/api/constant/CaptureEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.capture.api.constant; 2 | 3 | import com.frameworkium.core.api.Endpoint; 4 | 5 | /** The various Endpoints of Capture. */ 6 | public enum CaptureEndpoint implements Endpoint { 7 | 8 | BASE_URI("http://capture-6c06138r.cloudapp.net"), 9 | EXECUTIONS("/executions"), 10 | GET_EXECUTION("/executions/%s"), 11 | SUT_NAMES("/softwareUnderTest/names"), 12 | SUT_VERSIONS("/softwareUnderTest/versions/%s"), 13 | SCREENSHOT("/screenshot"); 14 | 15 | private String url; 16 | 17 | CaptureEndpoint(String url) { 18 | this.url = url; 19 | } 20 | 21 | /** 22 | * @param params Arguments referenced by the format specifiers in the url. 23 | * @return A formatted String representing the URL of the given constant. 24 | */ 25 | @Override 26 | public String getUrl(Object... params) { 27 | return String.format(url, params); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/capture/api/dto/executions/Browser.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.capture.api.dto.executions; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.frameworkium.core.api.dto.AbstractDTO; 5 | 6 | @JsonIgnoreProperties(ignoreUnknown = true) 7 | public class Browser extends AbstractDTO { 8 | 9 | public String name; 10 | public String version; 11 | 12 | public static Browser newInstance() { 13 | Browser browser = new Browser(); 14 | browser.name = "Firefox"; 15 | browser.version = "58.0"; 16 | return browser; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/capture/api/dto/executions/CreateExecution.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.capture.api.dto.executions; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | 5 | import java.net.InetAddress; 6 | import java.net.UnknownHostException; 7 | 8 | public class CreateExecution extends AbstractDTO { 9 | 10 | public String testID; 11 | public Browser browser; 12 | public SoftwareUnderTest softwareUnderTest; 13 | public String nodeAddress; 14 | 15 | /** 16 | * Using the static factory method pattern instead of using a constructor 17 | * which allows for multiple different, well named, creation methods. 18 | * 19 | * @return a populated {@link CreateExecution} with sensible defaults. 20 | */ 21 | public static CreateExecution newCreateInstance() { 22 | CreateExecution execution = new CreateExecution(); 23 | execution.testID = "DEFAULT-1"; 24 | execution.browser = Browser.newInstance(); 25 | execution.softwareUnderTest = SoftwareUnderTest.newInstance(); 26 | try { 27 | execution.nodeAddress = InetAddress.getLocalHost().getHostAddress(); 28 | } catch (UnknownHostException e) { 29 | execution.nodeAddress = null; 30 | } 31 | return execution; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/capture/api/dto/executions/ExecutionID.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.capture.api.dto.executions; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | 5 | /** Created execution message. */ 6 | public class ExecutionID extends AbstractDTO { 7 | 8 | public String executionID; 9 | } 10 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/capture/api/dto/executions/ExecutionResponse.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.capture.api.dto.executions; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | import com.frameworkium.integration.capture.api.dto.screenshots.Screenshot; 5 | 6 | import java.util.List; 7 | 8 | public class ExecutionResponse extends AbstractDTO { 9 | 10 | public String testID; 11 | public Browser browser; 12 | public SoftwareUnderTest softwareUnderTest; 13 | public String nodeAddress; 14 | public String created; 15 | public String lastUpdated; 16 | public String currentStatus; 17 | public String executionID; 18 | public List screenshots; 19 | 20 | public boolean createdFrom(CreateExecution createMessage) { 21 | return browser.equals(createMessage.browser) 22 | && softwareUnderTest.equals(createMessage.softwareUnderTest) 23 | && testID.equals(createMessage.testID) 24 | && nodeAddress.equals(createMessage.nodeAddress); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/capture/api/dto/executions/ExecutionResults.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.capture.api.dto.executions; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | 5 | import java.util.List; 6 | 7 | public class ExecutionResults extends AbstractDTO { 8 | 9 | public List results; 10 | public int total; 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/capture/api/dto/executions/SoftwareUnderTest.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.capture.api.dto.executions; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | 5 | public class SoftwareUnderTest extends AbstractDTO { 6 | 7 | public String name; 8 | public String version; 9 | 10 | public static SoftwareUnderTest newInstance() { 11 | SoftwareUnderTest sut = new SoftwareUnderTest(); 12 | sut.name = "frameworkium-core"; 13 | sut.version = "master"; 14 | return sut; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/capture/api/dto/screenshots/Command.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.capture.api.dto.screenshots; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | 5 | public class Command extends AbstractDTO { 6 | 7 | public String action; 8 | public String using; 9 | public String value; 10 | 11 | public static Command newInstance() { 12 | Command command = new Command(); 13 | command.action = "click"; 14 | command.using = "id"; 15 | command.value = "my-id"; 16 | return command; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/capture/api/dto/screenshots/CreateScreenshot.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.capture.api.dto.screenshots; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.frameworkium.core.api.dto.AbstractDTO; 5 | import org.apache.commons.io.IOUtils; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.util.Base64; 10 | 11 | @JsonInclude(JsonInclude.Include.NON_NULL) 12 | public class CreateScreenshot extends AbstractDTO { 13 | 14 | public Command command; 15 | public String url; 16 | public String executionID; 17 | public String errorMessage; 18 | public String screenshotBase64; 19 | 20 | public static CreateScreenshot newInstance(String executionID) { 21 | CreateScreenshot createScreenshot = new CreateScreenshot(); 22 | createScreenshot.executionID = executionID; 23 | createScreenshot.command = Command.newInstance(); 24 | createScreenshot.url = "http://test.url/hello?x=1&y=2"; 25 | createScreenshot.errorMessage = null; 26 | createScreenshot.screenshotBase64 = getBase64TestImage(); 27 | return createScreenshot; 28 | } 29 | 30 | private static String getBase64TestImage() { 31 | try { 32 | InputStream imageStream = CreateScreenshot.class.getClassLoader() 33 | .getResourceAsStream("capture-screenshot.png"); 34 | byte[] bytes = IOUtils.toByteArray(imageStream); 35 | return Base64.getEncoder().encodeToString(bytes); 36 | } catch (IOException e) { 37 | throw new IllegalStateException(e); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/capture/api/dto/screenshots/Screenshot.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.capture.api.dto.screenshots; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | 5 | public class Screenshot extends AbstractDTO { 6 | 7 | public Command command; 8 | public String imageURL; 9 | public String timestamp; 10 | public String url; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/capture/api/service/BaseCaptureService.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.capture.api.service; 2 | 3 | import com.frameworkium.core.api.services.BaseService; 4 | import com.frameworkium.integration.capture.api.constant.CaptureEndpoint; 5 | import io.restassured.RestAssured; 6 | import io.restassured.http.ContentType; 7 | import io.restassured.response.ValidatableResponse; 8 | import io.restassured.specification.RequestSpecification; 9 | import io.restassured.specification.ResponseSpecification; 10 | import org.apache.http.HttpStatus; 11 | 12 | /** Base Service for Capture specific services. */ 13 | public class BaseCaptureService extends BaseService { 14 | 15 | /** 16 | * @return a Rest Assured {@link RequestSpecification} with the baseUri 17 | * (and anything else required by most Capture services). 18 | */ 19 | @Override 20 | protected RequestSpecification getRequestSpec() { 21 | return RestAssured.given() 22 | .baseUri(CaptureEndpoint.BASE_URI.getUrl()) 23 | .relaxedHTTPSValidation() // trusts even invalid certs 24 | // .log().all() // uncomment to log each request 25 | .contentType(ContentType.JSON) 26 | .accept(ContentType.JSON); 27 | } 28 | 29 | /** 30 | * @return a Rest Assured {@link ResponseSpecification} with basic checks 31 | * (and anything else required by most Capture services). 32 | */ 33 | @Override 34 | protected ResponseSpecification getResponseSpec() { 35 | return RestAssured.expect().response() 36 | .statusCode(HttpStatus.SC_OK); 37 | } 38 | 39 | protected ValidatableResponse post(String url, Object body) { 40 | return getRequestSpec() 41 | .when() 42 | .body(body) 43 | .post(url) 44 | .then() 45 | .assertThat().statusCode(HttpStatus.SC_CREATED); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/capture/api/service/executions/ExecutionService.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.capture.api.service.executions; 2 | 3 | import com.frameworkium.integration.capture.api.constant.CaptureEndpoint; 4 | import com.frameworkium.integration.capture.api.dto.executions.*; 5 | import com.frameworkium.integration.capture.api.service.BaseCaptureService; 6 | import com.google.common.collect.ImmutableMap; 7 | import io.qameta.allure.Step; 8 | 9 | /** Encapsulates the Capture ExecutionResponse service */ 10 | public class ExecutionService extends BaseCaptureService { 11 | 12 | @Step("Create Capture Execution {createMessage}") 13 | public ExecutionID createExecution(CreateExecution createMessage) { 14 | return post(CaptureEndpoint.EXECUTIONS.getUrl(), createMessage) 15 | .extract() 16 | .as(ExecutionID.class); 17 | } 18 | 19 | @Step("Get Capture Executions, page={page}, pageSize={pageSize}") 20 | public ExecutionResults getExecutions(int page, int pageSize) { 21 | return get( 22 | ImmutableMap.of("page", page, "pageSize", pageSize), 23 | CaptureEndpoint.EXECUTIONS.getUrl()) 24 | .as(ExecutionResults.class); 25 | } 26 | 27 | public ExecutionResponse getExecution(String executionID) { 28 | return get(CaptureEndpoint.GET_EXECUTION.getUrl(executionID)) 29 | .as(ExecutionResponse.class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/capture/api/service/screenshots/ScreenshotService.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.capture.api.service.screenshots; 2 | 3 | import com.frameworkium.integration.capture.api.constant.CaptureEndpoint; 4 | import com.frameworkium.integration.capture.api.dto.screenshots.CreateScreenshot; 5 | import com.frameworkium.integration.capture.api.service.BaseCaptureService; 6 | 7 | /** Encapsulates the Capture ExecutionResponse service */ 8 | public class ScreenshotService extends BaseCaptureService { 9 | 10 | public void createScreenshot(CreateScreenshot createMessage) { 11 | post(CaptureEndpoint.SCREENSHOT.getUrl(), createMessage); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/frameworkium/pages/JQueryDemoPage.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.frameworkium.pages; 2 | 3 | import com.frameworkium.core.ui.ExtraExpectedConditions; 4 | import com.frameworkium.core.ui.annotations.Visible; 5 | import com.frameworkium.core.ui.pages.BasePage; 6 | import com.frameworkium.core.ui.pages.PageFactory; 7 | import org.openqa.selenium.WebElement; 8 | import org.openqa.selenium.support.FindBy; 9 | 10 | public class JQueryDemoPage extends BasePage { 11 | 12 | @Visible 13 | @FindBy(css = "#content > h1") 14 | private WebElement heading; 15 | 16 | public static JQueryDemoPage open() { 17 | return PageFactory.newInstance( 18 | JQueryDemoPage.class, 19 | "https://jqueryui.com/demos/"); 20 | } 21 | 22 | public JQueryDemoPage waitForJQuery() { 23 | // Just testing it doesn't fail 24 | wait.until(ExtraExpectedConditions.jQueryAjaxDone()); 25 | return this; 26 | } 27 | 28 | public String getHeadingText() { 29 | return heading.getText(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/frameworkium/tests/FrameworkiumBugsTest.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.frameworkium.tests; 2 | 3 | import com.frameworkium.core.common.reporting.allure.AllureLogger; 4 | import com.frameworkium.core.ui.UITestLifecycle; 5 | import com.frameworkium.core.ui.tests.BaseUITest; 6 | import com.frameworkium.integration.frameworkium.pages.JQueryDemoPage; 7 | import com.frameworkium.integration.seleniumhq.pages.SeleniumDownloadPage; 8 | import org.testng.annotations.BeforeMethod; 9 | import org.testng.annotations.Test; 10 | 11 | import static com.google.common.truth.Truth.assertThat; 12 | 13 | // tests if BeforeMethods are run despite not being in this group 14 | @Test(groups = "fw-bugs") 15 | public class FrameworkiumBugsTest extends BaseUITest { 16 | 17 | @BeforeMethod(dependsOnMethods = "configureBrowserBeforeTest") 18 | public void configureBrowserBeforeUse_allows_browser_access_in_before_method() { 19 | assertThat(UITestLifecycle.get().getWebDriver().getPageSource()).isNotEmpty(); 20 | } 21 | 22 | public void ensure_jQueryAjaxDone_does_not_fail() { 23 | String headingText = 24 | JQueryDemoPage.open() 25 | .waitForJQuery() 26 | .getHeadingText(); 27 | assertThat(headingText).isEqualTo("jQuery UI Demos"); 28 | } 29 | 30 | public void use_base_page_visibility() { 31 | SeleniumDownloadPage.open() 32 | .hideContent() 33 | .forceVisibleContent() 34 | .waitForContent(); 35 | } 36 | 37 | @Test(dependsOnMethods = {"use_base_page_visibility"}) 38 | public void ensure_BaseUITest_wait_is_updated_after_browser_reset() { 39 | // tests bug whereby BasePage.wait wasn't updated after browser reset 40 | SeleniumDownloadPage.open().waitForContent(); 41 | } 42 | 43 | public void use_various_loggers() { 44 | logger.info("Using BaseUITest logger"); 45 | AllureLogger.logToAllure("Using log to Allure!"); 46 | AllureLogger.stepStart("Starting a custom allure step."); 47 | SeleniumDownloadPage.open().log(); 48 | AllureLogger.stepFinish(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/restfulbooker/api/constant/BookerEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.restfulbooker.api.constant; 2 | 3 | import com.frameworkium.core.api.Endpoint; 4 | 5 | /** The various Endpoints of Restful Booker. */ 6 | public enum BookerEndpoint implements Endpoint { 7 | 8 | BASE_URI("https://restful-booker.herokuapp.com"), 9 | PING("/ping"), 10 | BOOKING("/booking"), 11 | BOOKING_ID("/booking/%d"), 12 | AUTH("/auth"); 13 | 14 | private String url; 15 | 16 | BookerEndpoint(String url) { 17 | this.url = url; 18 | } 19 | 20 | /** 21 | * @param params Arguments referenced by the format specifiers in the url. 22 | * @return A formatted String representing the URL of the given constant. 23 | */ 24 | @Override 25 | public String getUrl(Object... params) { 26 | return String.format(url, params); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/restfulbooker/api/dto/booking/Booking.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.restfulbooker.api.dto.booking; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | 5 | import java.util.concurrent.ThreadLocalRandom; 6 | 7 | public class Booking extends AbstractDTO { 8 | public String firstname; 9 | public String lastname; 10 | public long totalprice; 11 | public boolean depositpaid; 12 | public BookingDates bookingdates; 13 | public String additionalneeds; 14 | 15 | public static Booking newInstance() { 16 | ThreadLocalRandom random = ThreadLocalRandom.current(); 17 | int randInt = random.nextInt(); 18 | Booking booking = new Booking(); 19 | booking.firstname = "firstname" + randInt; 20 | booking.lastname = "lastname" + randInt; 21 | booking.totalprice = randInt; 22 | booking.depositpaid = random.nextBoolean(); 23 | booking.bookingdates = BookingDates.newInstance(); 24 | booking.additionalneeds = null; 25 | return booking; 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/restfulbooker/api/dto/booking/BookingDates.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.restfulbooker.api.dto.booking; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | 5 | import java.time.LocalDate; 6 | import java.time.format.DateTimeFormatter; 7 | 8 | public class BookingDates extends AbstractDTO { 9 | 10 | public static final DateTimeFormatter FORMAT = 11 | DateTimeFormatter.ISO_LOCAL_DATE; 12 | 13 | public String checkin; 14 | public String checkout; 15 | 16 | public static BookingDates newInstance() { 17 | BookingDates dates = new BookingDates(); 18 | dates.checkin = LocalDate.now().plusDays(1).format(FORMAT); 19 | dates.checkout = LocalDate.now().plusDays(10).format(FORMAT); 20 | return dates; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/restfulbooker/api/dto/booking/BookingID.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.restfulbooker.api.dto.booking; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | 5 | public class BookingID extends AbstractDTO { 6 | 7 | public int bookingid; 8 | 9 | public static BookingID of(int bookingID) { 10 | BookingID id = new BookingID(); 11 | id.bookingid = bookingID; 12 | return id; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/restfulbooker/api/dto/booking/BookingResponse.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.restfulbooker.api.dto.booking; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | 5 | public class BookingResponse extends AbstractDTO { 6 | public Booking booking; 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/restfulbooker/api/dto/booking/CreateBookingResponse.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.restfulbooker.api.dto.booking; 2 | 3 | import com.frameworkium.core.api.dto.AbstractDTO; 4 | 5 | public class CreateBookingResponse extends AbstractDTO { 6 | public Booking booking; 7 | public int bookingid; 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/restfulbooker/api/dto/booking/search/SearchParamsMapper.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.restfulbooker.api.dto.booking.search; 2 | 3 | import com.frameworkium.integration.restfulbooker.api.dto.booking.Booking; 4 | import com.google.common.collect.ImmutableMap; 5 | 6 | import java.util.Map; 7 | 8 | public class SearchParamsMapper { 9 | 10 | private SearchParamsMapper() { 11 | // hide constructor of mapper 12 | } 13 | 14 | public static Map namesOfBooking(Booking booking) { 15 | return ImmutableMap.of( 16 | "firstname", booking.firstname, 17 | "lastname", booking.lastname); 18 | } 19 | 20 | public static Map datesOfBooking(Booking booking) { 21 | return ImmutableMap.of( 22 | "checkin", booking.bookingdates.checkin, 23 | "checkout", booking.bookingdates.checkout); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/restfulbooker/api/service/AbstractBookerService.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.restfulbooker.api.service; 2 | 3 | import com.frameworkium.core.api.services.BaseService; 4 | import com.frameworkium.integration.restfulbooker.api.constant.BookerEndpoint; 5 | import io.restassured.RestAssured; 6 | import io.restassured.http.Method; 7 | import io.restassured.response.ExtractableResponse; 8 | import io.restassured.specification.RequestSpecification; 9 | import io.restassured.specification.ResponseSpecification; 10 | import org.apache.http.HttpStatus; 11 | 12 | /** Base Service for RestfulBooker specific services. */ 13 | public abstract class AbstractBookerService extends BaseService { 14 | 15 | /** 16 | * @return a Rest Assured {@link RequestSpecification} with the baseUri 17 | * (and anything else required by most Capture services). 18 | */ 19 | @Override 20 | protected RequestSpecification getRequestSpec() { 21 | return RestAssured.given() 22 | .baseUri(BookerEndpoint.BASE_URI.getUrl()) 23 | .relaxedHTTPSValidation() // trusts even invalid certs 24 | // .log().all() // uncomment to log each request 25 | .contentType("application/json") 26 | .accept("application/json"); 27 | } 28 | 29 | /** 30 | * @return a Rest Assured {@link ResponseSpecification} with basic checks 31 | * (and anything else required by most services). 32 | */ 33 | @Override 34 | protected ResponseSpecification getResponseSpec() { 35 | return RestAssured.expect().response() 36 | .statusCode(HttpStatus.SC_OK); 37 | } 38 | 39 | protected ExtractableResponse post(Object body, String url) { 40 | return request(Method.POST, body, url); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/restfulbooker/api/service/booking/BookingService.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.restfulbooker.api.service.booking; 2 | 3 | import com.frameworkium.integration.restfulbooker.api.constant.BookerEndpoint; 4 | import com.frameworkium.integration.restfulbooker.api.dto.booking.*; 5 | import com.frameworkium.integration.restfulbooker.api.service.AbstractBookerService; 6 | import com.google.common.collect.ImmutableMap; 7 | import io.restassured.http.Method; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | public class BookingService extends AbstractBookerService { 13 | 14 | public List listBookings() { 15 | return get(BookerEndpoint.BOOKING.getUrl()) 16 | .jsonPath().getList(".", BookingID.class); 17 | } 18 | 19 | public Booking getBooking(int id) { 20 | return get(BookerEndpoint.BOOKING_ID.getUrl(id)) 21 | .as(Booking.class); 22 | } 23 | 24 | public CreateBookingResponse createBooking(Booking booking) { 25 | return post(booking, BookerEndpoint.BOOKING.getUrl()) 26 | .as(CreateBookingResponse.class); 27 | } 28 | 29 | public List search(Map searchParams) { 30 | return request(Method.GET, searchParams, BookerEndpoint.BOOKING.getUrl()) 31 | .jsonPath().getList(".", BookingID.class); 32 | } 33 | 34 | public String createAuthToken(String username, String password) { 35 | return post( 36 | ImmutableMap.of("username", username, "password", password), 37 | BookerEndpoint.AUTH.getUrl()) 38 | .jsonPath().get("token"); 39 | } 40 | 41 | public void delete(int bookingID, String token) { 42 | getRequestSpec() 43 | .cookie("token", token) 44 | .delete(BookerEndpoint.BOOKING_ID.getUrl(bookingID)) 45 | // API does not match documentation 46 | // .then() 47 | // .statusCode(HttpStatus.SC_NO_CONTENT) 48 | ; 49 | } 50 | 51 | public boolean doesBookingExist(int bookingID) { 52 | final int statusCode = getRequestSpec() 53 | .get(BookerEndpoint.BOOKING_ID.getUrl(bookingID)) 54 | .then() 55 | .extract() 56 | .statusCode(); 57 | if (statusCode == 200) { 58 | return true; 59 | } else if (statusCode == 404) { 60 | return false; 61 | } else { 62 | throw new IllegalStateException("Unexpected return code"); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/restfulbooker/api/service/ping/PingService.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.restfulbooker.api.service.ping; 2 | 3 | import com.frameworkium.integration.restfulbooker.api.constant.BookerEndpoint; 4 | import com.frameworkium.integration.restfulbooker.api.service.AbstractBookerService; 5 | import io.restassured.RestAssured; 6 | import io.restassured.specification.ResponseSpecification; 7 | import org.apache.http.HttpStatus; 8 | 9 | public class PingService extends AbstractBookerService { 10 | 11 | public String ping() { 12 | return get(BookerEndpoint.PING.getUrl()) 13 | .body().asString(); 14 | } 15 | 16 | /** 17 | * Used in template method {@link AbstractBookerService#get(String)} 18 | */ 19 | @Override 20 | protected ResponseSpecification getResponseSpec() { 21 | return RestAssured.expect().response() 22 | .statusCode(HttpStatus.SC_CREATED); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/restfulbooker/api/tests/BookerTest.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.restfulbooker.api.tests; 2 | 3 | import com.frameworkium.core.api.tests.BaseAPITest; 4 | import com.frameworkium.core.common.retry.RetryFlakyTest; 5 | import com.frameworkium.integration.restfulbooker.api.dto.booking.*; 6 | import com.frameworkium.integration.restfulbooker.api.service.booking.BookingService; 7 | import com.frameworkium.integration.restfulbooker.api.service.ping.PingService; 8 | import org.testng.annotations.BeforeClass; 9 | import org.testng.annotations.Test; 10 | 11 | import static com.google.common.truth.Truth.assertThat; 12 | 13 | // app resets every 10m, so could happen in the middle of this test 14 | @Test(retryAnalyzer = RetryFlakyTest.class) 15 | public class BookerTest extends BaseAPITest { 16 | 17 | @BeforeClass 18 | public void ensure_site_is_up_by_using_ping_service() { 19 | assertThat(new PingService().ping()) 20 | .isEqualTo("Created"); 21 | } 22 | 23 | public void create_new_booking() { 24 | // given some booking data 25 | BookingService service = new BookingService(); 26 | Booking booking = Booking.newInstance(); 27 | 28 | // when creating the booking 29 | CreateBookingResponse bookingResponse = service.createBooking(booking); 30 | 31 | // the booking returned matches the input and is persisted 32 | assertThat(bookingResponse.booking) 33 | .isEqualTo(booking); 34 | assertThat(service.getBooking(bookingResponse.bookingid)) 35 | .isEqualTo(booking); 36 | } 37 | 38 | public void auth_token_matches_expected_pattern() { 39 | String token = new BookingService().createAuthToken( 40 | "admin", "password123"); 41 | assertThat(token).matches("[a-z0-9]{15}"); 42 | } 43 | 44 | public void delete_newly_created_booking() { 45 | // given an existing booking 46 | BookingService service = new BookingService(); 47 | int bookingID = service.createBooking(Booking.newInstance()).bookingid; 48 | // and an auth token 49 | String authToken = new BookingService().createAuthToken( 50 | "admin", "password123"); 51 | // when deleting 52 | service.delete(bookingID, authToken); 53 | 54 | // then the booking no longer exists 55 | assertThat(service.doesBookingExist(bookingID)).isFalse(); 56 | assertThat(service.listBookings()) 57 | .doesNotContain(BookingID.of(bookingID)); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/restfulbooker/api/tests/SearchBookerTest.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.restfulbooker.api.tests; 2 | 3 | import com.frameworkium.core.api.tests.BaseAPITest; 4 | import com.frameworkium.core.common.retry.RetryFlakyTest; 5 | import com.frameworkium.integration.restfulbooker.api.dto.booking.Booking; 6 | import com.frameworkium.integration.restfulbooker.api.dto.booking.BookingID; 7 | import com.frameworkium.integration.restfulbooker.api.dto.booking.search.SearchParamsMapper; 8 | import com.frameworkium.integration.restfulbooker.api.service.booking.BookingService; 9 | import com.frameworkium.integration.restfulbooker.api.service.ping.PingService; 10 | import org.testng.SkipException; 11 | import org.testng.annotations.BeforeClass; 12 | import org.testng.annotations.Test; 13 | 14 | import java.util.List; 15 | 16 | import static com.google.common.truth.Truth.assertThat; 17 | 18 | // app resets every 10m, so could happen in the middle of this test 19 | @Test(retryAnalyzer = RetryFlakyTest.class) 20 | public class SearchBookerTest extends BaseAPITest { 21 | 22 | @BeforeClass 23 | public void ensure_site_is_up_by_using_ping_service() { 24 | assertThat(new PingService().ping()) 25 | .isEqualTo("Created"); 26 | } 27 | 28 | public void search_for_existing_records_by_name() { 29 | BookingService service = new BookingService(); 30 | BookingID existingID = service.listBookings().get(1); 31 | Booking booking = service.getBooking(existingID.bookingid); 32 | 33 | List bookingIDs = service.search( 34 | SearchParamsMapper.namesOfBooking(booking)); 35 | 36 | assertThat(bookingIDs).contains(existingID); 37 | } 38 | 39 | public void search_for_existing_records_by_date() { 40 | BookingService service = new BookingService(); 41 | BookingID existingID = service.listBookings().get(3); 42 | Booking booking = service.getBooking(existingID.bookingid); 43 | 44 | List bookingIDs = service.search( 45 | SearchParamsMapper.datesOfBooking(booking)); 46 | 47 | // TODO: move to dedicated test 48 | throw new SkipException("Known bug in service, dates not inclusive"); 49 | // assertThat(bookingIDs).contains(existingID); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/seleniumhq/components/HeaderComponent.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.seleniumhq.components; 2 | 3 | import com.frameworkium.core.htmlelements.element.HtmlElement; 4 | import com.frameworkium.core.htmlelements.element.Link; 5 | import com.frameworkium.core.ui.UITestLifecycle; 6 | import com.frameworkium.core.ui.pages.PageFactory; 7 | import com.frameworkium.integration.seleniumhq.pages.SeleniumDownloadPage; 8 | import org.openqa.selenium.support.FindBy; 9 | import org.openqa.selenium.support.ui.ExpectedConditions; 10 | 11 | 12 | @FindBy(className = "navbar") 13 | public class HeaderComponent extends HtmlElement { 14 | 15 | @FindBy(css = "#main_navbar [href='/downloads']") 16 | private Link downloadLink; 17 | 18 | public SeleniumDownloadPage clickDownloadLink() { 19 | UITestLifecycle.get().getWait().until(ExpectedConditions.elementToBeClickable(downloadLink)); 20 | downloadLink.click(); 21 | return PageFactory.newInstance(SeleniumDownloadPage.class); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/seleniumhq/pages/HomePage.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.seleniumhq.pages; 2 | 3 | import com.frameworkium.core.ui.annotations.Visible; 4 | import com.frameworkium.core.ui.pages.BasePage; 5 | import com.frameworkium.core.ui.pages.PageFactory; 6 | import com.frameworkium.integration.seleniumhq.components.HeaderComponent; 7 | import org.openqa.selenium.WebElement; 8 | import org.openqa.selenium.support.CacheLookup; 9 | import org.openqa.selenium.support.FindBy; 10 | 11 | public class HomePage extends BasePage { 12 | 13 | @CacheLookup 14 | @Visible 15 | private HeaderComponent header; 16 | 17 | @FindBy(css = "button[data-target='#main_navbar']") 18 | private WebElement menuLink; 19 | 20 | public static HomePage open() { 21 | return PageFactory.newInstance( 22 | HomePage.class, 23 | "https://selenium.dev/"); 24 | } 25 | 26 | public HeaderComponent getHeader() { 27 | // when using Selenium grid, the browser size is smaller and the header is hidden behind this link 28 | if (menuLink.isDisplayed()) { 29 | menuLink.click(); 30 | } 31 | return header; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/seleniumhq/pages/SeleniumDownloadPage.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.seleniumhq.pages; 2 | 3 | import com.frameworkium.core.htmlelements.element.Link; 4 | import com.frameworkium.core.ui.annotations.Visible; 5 | import com.frameworkium.core.ui.pages.BasePage; 6 | import com.frameworkium.core.ui.pages.PageFactory; 7 | import com.frameworkium.integration.seleniumhq.components.HeaderComponent; 8 | import org.openqa.selenium.support.FindBy; 9 | import org.openqa.selenium.support.ui.ExpectedConditions; 10 | 11 | 12 | import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOf; 13 | 14 | public class SeleniumDownloadPage extends BasePage { 15 | 16 | @Visible 17 | private HeaderComponent header; 18 | 19 | @Visible 20 | @FindBy(css = "body .td-main div:nth-child(3) > div:nth-child(2) a") 21 | private Link latestDownloadLink; 22 | 23 | public static SeleniumDownloadPage open() { 24 | return PageFactory.newInstance( 25 | SeleniumDownloadPage.class, 26 | "https://selenium.dev/downloads/"); 27 | } 28 | 29 | public String getLatestVersion() { 30 | return latestDownloadLink.getText(); 31 | } 32 | 33 | public SeleniumDownloadPage hideContent() { 34 | executeJS("arguments[0].style.visibility='hidden';", latestDownloadLink); 35 | wait.until(ExpectedConditions.not(visibilityOf(latestDownloadLink))); 36 | return this; 37 | } 38 | 39 | public SeleniumDownloadPage forceVisibleContent() { 40 | // ensure force visible works 41 | forceVisible(latestDownloadLink); 42 | return this; 43 | } 44 | 45 | public void waitForContent() { 46 | wait.until(visibilityOf(latestDownloadLink)); 47 | } 48 | 49 | public void log() { 50 | logger.trace("Using the BasePage logger"); 51 | logger.info("Using the BasePage logger"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/seleniumhq/tests/SeleniumTest.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.seleniumhq.tests; 2 | 3 | import com.frameworkium.core.ui.tests.BaseUITest; 4 | import com.frameworkium.integration.seleniumhq.pages.HomePage; 5 | 6 | import static com.google.common.truth.Truth.assertThat; 7 | 8 | public class SeleniumTest extends BaseUITest { 9 | 10 | public final void component_example_test() { 11 | String latestVersion = HomePage.open() 12 | .getHeader() 13 | .clickDownloadLink() 14 | .getLatestVersion(); 15 | 16 | assertThat(latestVersion).matches("\\d\\.\\d+\\.\\d+"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/theinternet/pages/CheckboxesPage.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.theinternet.pages; 2 | 3 | import com.frameworkium.core.htmlelements.element.CheckBox; 4 | import com.frameworkium.core.ui.annotations.Visible; 5 | import com.frameworkium.core.ui.pages.BasePage; 6 | import io.qameta.allure.Step; 7 | import org.openqa.selenium.support.FindBy; 8 | 9 | 10 | import java.util.List; 11 | import java.util.stream.Stream; 12 | 13 | public class CheckboxesPage extends BasePage { 14 | 15 | @Visible(checkAtMost = 1) 16 | @FindBy(css = "form input[type='checkbox']") 17 | private List allCheckboxes; 18 | 19 | @Step("Set all the checkboxes to true") 20 | public CheckboxesPage checkAllCheckboxes() { 21 | allCheckboxes.forEach(CheckBox::select); 22 | return this; 23 | } 24 | 25 | @Step("Return the checked status of all the checkboxes") 26 | public Stream getAllCheckboxCheckedStatus() { 27 | return allCheckboxes.stream() 28 | .map(CheckBox::isSelected); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/theinternet/pages/DynamicLoadingExamplePage.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.theinternet.pages; 2 | 3 | import com.frameworkium.core.htmlelements.annotations.Timeout; 4 | import com.frameworkium.core.htmlelements.element.Button; 5 | import com.frameworkium.core.htmlelements.element.HtmlElement; 6 | import com.frameworkium.core.ui.annotations.Invisible; 7 | import com.frameworkium.core.ui.annotations.Visible; 8 | import com.frameworkium.core.ui.pages.BasePage; 9 | import com.frameworkium.core.ui.pages.PageFactory; 10 | import io.qameta.allure.Step; 11 | import org.openqa.selenium.support.FindBy; 12 | 13 | import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOf; 14 | 15 | public class DynamicLoadingExamplePage extends BasePage { 16 | 17 | @Visible 18 | @FindBy(css = "#start button") 19 | private Button startButton; 20 | 21 | @Invisible 22 | @Timeout(0) // prevents page load taking 5s due to implicit timeout 23 | @FindBy(id = "finish") 24 | private HtmlElement dynamicElement; 25 | 26 | public static DynamicLoadingExamplePage openExampleTwo() { 27 | return PageFactory.newInstance( 28 | DynamicLoadingExamplePage.class, 29 | "https://the-internet.herokuapp.com/dynamic_loading/2"); 30 | } 31 | 32 | @Step("Click Start") 33 | public DynamicLoadingExamplePage clickStart() { 34 | startButton.click(); 35 | return this; 36 | } 37 | 38 | public String getElementText() { 39 | wait.until(visibilityOf(dynamicElement)); 40 | return dynamicElement.getText(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/theinternet/pages/HoversPage.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.theinternet.pages; 2 | 3 | import com.frameworkium.core.ui.annotations.Invisible; 4 | import com.frameworkium.core.ui.annotations.Visible; 5 | import com.frameworkium.core.ui.pages.BasePage; 6 | import io.qameta.allure.Step; 7 | import org.openqa.selenium.WebElement; 8 | import org.openqa.selenium.interactions.Actions; 9 | import org.openqa.selenium.support.FindBy; 10 | 11 | public class HoversPage extends BasePage { 12 | 13 | @Visible 14 | @FindBy(css = "div.figure:nth-of-type(1)") 15 | private WebElement firstFigure; 16 | 17 | @Invisible 18 | @FindBy(css = "div.figure:nth-of-type(1) div.figcaption") 19 | private WebElement firstFigureCaption; 20 | 21 | @Step("Get 1st figure caption") 22 | public String getFirstFigureCaption() { 23 | 24 | // Move mouse over the first figure to make caption visible 25 | (new Actions(driver)).moveToElement(firstFigure).perform(); 26 | 27 | // Return text from the now-visible caption 28 | return firstFigureCaption.getText(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/theinternet/pages/JavaScriptAlertsPage.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.theinternet.pages; 2 | 3 | import com.frameworkium.core.htmlelements.element.Button; 4 | import com.frameworkium.core.ui.annotations.Visible; 5 | import com.frameworkium.core.ui.pages.BasePage; 6 | import io.qameta.allure.Step; 7 | import org.openqa.selenium.WebElement; 8 | import org.openqa.selenium.support.FindBy; 9 | 10 | 11 | import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOf; 12 | 13 | public class JavaScriptAlertsPage extends BasePage { 14 | 15 | @Visible 16 | @FindBy(css = "button[onclick='jsAlert()']") 17 | private Button jsAlertButton; 18 | 19 | @FindBy(css = "p#result") 20 | private WebElement resultArea; 21 | 22 | @Step("Click alert") 23 | public JavaScriptAlertsPage clickAlertButtonAndAccept() { 24 | jsAlertButton.click(); 25 | driver.switchTo().alert().accept(); 26 | wait.until(visibilityOf(resultArea)); 27 | return this; 28 | } 29 | 30 | @Step("Click prompt") 31 | public String getResultText() { 32 | return resultArea.getText(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/theinternet/pages/KeyPressesPage.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.theinternet.pages; 2 | 3 | import com.frameworkium.core.ui.annotations.Visible; 4 | import com.frameworkium.core.ui.pages.BasePage; 5 | import io.qameta.allure.Step; 6 | import org.openqa.selenium.Keys; 7 | import org.openqa.selenium.WebElement; 8 | import org.openqa.selenium.interactions.Actions; 9 | import org.openqa.selenium.support.FindBy; 10 | 11 | public class KeyPressesPage extends BasePage { 12 | 13 | @Visible 14 | @FindBy(css = "div.example") 15 | private WebElement container; 16 | 17 | @FindBy(css = "p#result") 18 | private WebElement result; 19 | 20 | @Step("Enter a key press {key}") 21 | public KeyPressesPage enterKeyPress(Keys key) { 22 | (new Actions(driver)).sendKeys(key).perform(); 23 | return this; 24 | } 25 | 26 | @Step("Get result text") 27 | public String getResultText() { 28 | return result.getText(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/theinternet/pages/SortableDataTablesPage.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.theinternet.pages; 2 | 3 | import com.frameworkium.core.ui.annotations.Visible; 4 | import com.frameworkium.core.ui.element.OptimisedStreamTable; 5 | import com.frameworkium.core.ui.pages.BasePage; 6 | import com.frameworkium.core.ui.pages.PageFactory; 7 | import org.openqa.selenium.WebElement; 8 | import org.openqa.selenium.support.CacheLookup; 9 | import org.openqa.selenium.support.FindBy; 10 | 11 | import java.util.stream.Stream; 12 | 13 | public class SortableDataTablesPage extends BasePage { 14 | 15 | @Visible 16 | @CacheLookup 17 | @FindBy(id = "table1") 18 | private OptimisedStreamTable table1; 19 | 20 | @CacheLookup 21 | @FindBy(id = "table2") 22 | private OptimisedStreamTable table2; 23 | 24 | public static SortableDataTablesPage open() { 25 | return PageFactory.newInstance( 26 | SortableDataTablesPage.class, 27 | "https://the-internet.herokuapp.com/tables"); 28 | } 29 | 30 | public Stream getTable1ColumnContents(String colHeader) { 31 | return table1.getColumn(colHeader).map(WebElement::getText); 32 | } 33 | 34 | public Stream getTable2ColumnContents(String colHeader) { 35 | return table2.getColumn(colHeader).map(WebElement::getText); 36 | } 37 | 38 | public SortableDataTablesPage sortTable2ByColumnName(String colHeader) { 39 | table2.getHeading(colHeader).ifPresent(WebElement::click); 40 | return this; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/wikipedia/pages/EnglishCountiesPage.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.wikipedia.pages; 2 | 3 | import com.frameworkium.core.ui.annotations.Visible; 4 | import com.frameworkium.core.ui.element.OptimisedStreamTable; 5 | import com.frameworkium.core.ui.pages.BasePage; 6 | import com.frameworkium.core.ui.pages.PageFactory; 7 | 8 | import org.openqa.selenium.NotFoundException; 9 | import org.openqa.selenium.WebElement; 10 | import org.openqa.selenium.support.CacheLookup; 11 | import org.openqa.selenium.support.FindBy; 12 | 13 | import java.util.function.Predicate; 14 | import java.util.stream.Stream; 15 | 16 | /** 17 | * This page uses OptimisedStreamTable, this is slower than using Lists of 18 | * WebElements for columns, especially when running over a grid due to the far 19 | * greater number of page lookups required. This is even worse for StreamTable, 20 | * but StreamTable copes with a wider variety of Tables. 21 | * 22 | *

This is a trade-off between readability, maintainability and performance. 23 | * 24 | *

This approach is great if the table and tests are likely to change often. 25 | */ 26 | public class EnglishCountiesPage extends BasePage { 27 | 28 | @Visible 29 | @CacheLookup 30 | @FindBy(css = "table.wikitable") // luckily there's only one 31 | private OptimisedStreamTable listTable; 32 | 33 | public static EnglishCountiesPage open() { 34 | return PageFactory.newInstance(EnglishCountiesPage.class, 35 | "https://en.wikipedia.org/wiki/List_of_ceremonial_counties_of_England"); 36 | } 37 | 38 | public int populationOf(String countyName) { 39 | Predicate headerLookUp = e -> e.getText().trim().startsWith("County"); 40 | Predicate lookUpCellMatcher = e -> e.getText().trim().equals(countyName); 41 | Predicate targetColHeaderLookup = e -> e.getText().trim().startsWith("Population"); 42 | String population = listTable 43 | .getCellsByLookup(headerLookUp, lookUpCellMatcher, targetColHeaderLookup) 44 | .findFirst() 45 | .orElseThrow(NotFoundException::new) 46 | .getText() 47 | .replaceAll(",", ""); 48 | return Integer.parseInt(population); 49 | } 50 | 51 | public Stream densities() { 52 | return listTable 53 | // hard-coded index because headers are now row-span=2 54 | .getColumn(6) 55 | .map(WebElement::getText) 56 | .map(density -> density.replaceAll(",", "")) 57 | .map(Integer::parseInt); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/wikipedia/pages/EnglishCountiesUsingListsPage.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.wikipedia.pages; 2 | 3 | import com.frameworkium.core.ui.annotations.Visible; 4 | import com.frameworkium.core.ui.pages.BasePage; 5 | import com.frameworkium.core.ui.pages.PageFactory; 6 | import org.openqa.selenium.NoSuchElementException; 7 | import org.openqa.selenium.WebElement; 8 | import org.openqa.selenium.support.CacheLookup; 9 | import org.openqa.selenium.support.FindBy; 10 | 11 | import java.util.List; 12 | import java.util.Objects; 13 | import java.util.stream.IntStream; 14 | import java.util.stream.Stream; 15 | 16 | /** 17 | * This page uses {@link List}s of {@link WebElement}s for the columns we know 18 | * that we need. This test is faster than using StreamTable, especially when 19 | * running over a grid due to the far fewer page lookups required. 20 | * 21 | *

This is a trade-off between readability, maintainability and performance. 22 | * 23 | *

This approach is great if the table and tests are unlikely to change often. 24 | */ 25 | public class EnglishCountiesUsingListsPage extends BasePage { 26 | 27 | @Visible(checkAtMost = 1) 28 | @CacheLookup 29 | @FindBy(css = "table.wikitable > tbody > tr > td:nth-child(1)") 30 | private List countyColumn; 31 | 32 | @CacheLookup 33 | @FindBy(css = "table.wikitable > tbody > tr > td:nth-child(2)") 34 | private List populationColumn; 35 | 36 | @CacheLookup 37 | @FindBy(css = "table.wikitable > tbody > tr > td:nth-child(7)") 38 | private List densityColumn; 39 | 40 | public static EnglishCountiesUsingListsPage open() { 41 | return PageFactory.newInstance(EnglishCountiesUsingListsPage.class, 42 | "https://en.wikipedia.org/wiki/List_of_ceremonial_counties_of_England"); 43 | } 44 | 45 | public int populationOf(String countyName) { 46 | String population = 47 | populationColumn.get(findCountyIndex(countyName)) 48 | .getText() 49 | .replaceAll(",", ""); 50 | return Integer.parseInt(population); 51 | } 52 | 53 | private int findCountyIndex(String countyName) { 54 | return IntStream.range(0, countyColumn.size()) 55 | .filter(i -> Objects.equals(countyColumn.get(i).getText(), countyName)) 56 | .findFirst() 57 | .orElseThrow(() -> new NoSuchElementException( 58 | "County name '" + countyName + "' not found")); 59 | } 60 | 61 | public Stream densities() { 62 | return densityColumn.stream() 63 | .map(WebElement::getText) 64 | .map(density -> density.replaceAll(",", "")) 65 | .map(Integer::parseInt); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/wikipedia/tests/EnglishCountiesTest.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.wikipedia.tests; 2 | 3 | import com.frameworkium.core.ui.tests.BaseUITest; 4 | import com.frameworkium.integration.wikipedia.pages.EnglishCountiesPage; 5 | import com.frameworkium.integration.wikipedia.pages.EnglishCountiesUsingListsPage; 6 | import org.testng.annotations.Test; 7 | 8 | import static com.google.common.truth.Truth.assertThat; 9 | 10 | /** 11 | * This test demonstrates the different between using StreamTable and Lists 12 | * of WebElements. 13 | *

The trade-off is between readability, maintainability and performance. 14 | *

The List option is slightly longer and slightly more difficult to maintain, 15 | * especially if your table is changing shape (but not header text), however it 16 | * is significantly faster. 17 | */ 18 | @Test 19 | public class EnglishCountiesTest extends BaseUITest { 20 | 21 | public void exploring_english_counties_data_with_stream_table() { 22 | EnglishCountiesPage page = EnglishCountiesPage.open(); 23 | 24 | assertThat(page.populationOf("Cornwall")) 25 | .isAtLeast(550_000); 26 | // at least two counties have population densities of more than 3000 27 | assertThat(page.densities() 28 | .filter(density -> density > 3000) 29 | .limit(2) 30 | .count()) 31 | .isEqualTo(2L); 32 | } 33 | 34 | 35 | public void exploring_english_counties_data_with_lists() { 36 | EnglishCountiesUsingListsPage page = EnglishCountiesUsingListsPage.open(); 37 | 38 | assertThat(page.populationOf("Cornwall")) 39 | .isAtLeast(550_000); 40 | // at least two counties have population densities of more than 3000 41 | assertThat(page.densities() 42 | .filter(density -> density > 3000) 43 | .limit(2) 44 | .count()) 45 | .isEqualTo(2L); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/com/frameworkium/integration/wiremock/WireMockJiraTeardown.java: -------------------------------------------------------------------------------- 1 | package com.frameworkium.integration.wiremock; 2 | 3 | import com.frameworkium.core.api.tests.BaseAPITest; 4 | import com.github.tomakehurst.wiremock.client.WireMock; 5 | import org.testng.annotations.Test; 6 | 7 | @Test 8 | /* 9 | * This is called in post-integration-test phase to reset wiremock after an integration-test phase in anticipation 10 | * for any further test phases that may set their own wiremock mappings 11 | */ 12 | public class WireMockJiraTeardown extends BaseAPITest { 13 | 14 | public void mockJiraTeardown() { 15 | final WireMock wireMock = new WireMock(); 16 | WireMock.reset(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/resources/capture-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frameworkium/frameworkium-core/815f590a38a994cfe891fd6d22f9a3187678aae6/src/test/resources/capture-screenshot.png --------------------------------------------------------------------------------