├── .github └── workflows │ ├── build.yaml │ └── publish-release.yaml ├── .gitignore ├── Jenkinsfile ├── README.adoc ├── atlas-appium ├── build.gradle.kts └── src │ ├── main │ └── java │ │ └── io │ │ └── qameta │ │ └── atlas │ │ └── appium │ │ ├── AppiumDriverConfiguration.java │ │ ├── AtlasMobileElement.java │ │ ├── Indent.java │ │ ├── Screen.java │ │ ├── annotations │ │ ├── AndroidFindBy.java │ │ ├── AppiumDriverProvider.java │ │ └── IOSFindBy.java │ │ ├── context │ │ └── AppiumDriverContext.java │ │ └── extension │ │ ├── AppiumDriverProviderExtension.java │ │ ├── AppiumFindByExtension.java │ │ ├── LongPressExtension.java │ │ ├── SwipeDownOnExtension.java │ │ └── SwipeUpOnExtension.java │ └── test │ └── java │ └── io │ └── qameta │ └── atlas │ └── appium │ ├── AppiumFindByRetrierTest.java │ ├── AppiumFindByTest.java │ ├── AtlasMobileElementMethodsTest.java │ ├── LongPressExtensionTest.java │ └── testdata │ └── ObjectFactory.java ├── atlas-core ├── build.gradle.kts └── src │ ├── main │ └── java │ │ └── io │ │ └── qameta │ │ └── atlas │ │ └── core │ │ ├── Atlas.java │ │ ├── AtlasException.java │ │ ├── api │ │ ├── Context.java │ │ ├── Extension.java │ │ ├── Listener.java │ │ ├── MethodExtension.java │ │ ├── MethodInvoker.java │ │ ├── Retry.java │ │ ├── Target.java │ │ └── Timeout.java │ │ ├── context │ │ ├── RetryerContext.java │ │ └── TargetContext.java │ │ ├── internal │ │ ├── AtlasMethodHandler.java │ │ ├── Configuration.java │ │ ├── DefaultMethodExtension.java │ │ ├── DefaultRetryer.java │ │ ├── EmptyRetryer.java │ │ ├── ListenerNotifier.java │ │ ├── Retryer.java │ │ └── TargetMethodInvoker.java │ │ ├── target │ │ ├── HardcodedTarget.java │ │ └── LazyTarget.java │ │ └── util │ │ ├── MethodInfo.java │ │ └── ReflectionUtils.java │ └── test │ └── java │ └── io │ └── qameta │ └── atlas │ └── core │ ├── DefaultMethodTest.java │ ├── ListenerTest.java │ ├── TargetMethodTest.java │ └── testdata │ └── CustomException.java ├── atlas-webdriver ├── build.gradle.kts └── src │ ├── main │ └── java │ │ └── io │ │ └── qameta │ │ └── atlas │ │ └── webdriver │ │ ├── AtlasWebElement.java │ │ ├── ElementsCollection.java │ │ ├── WebDriverConfiguration.java │ │ ├── WebPage.java │ │ ├── WebSite.java │ │ ├── context │ │ └── WebDriverContext.java │ │ ├── exception │ │ └── WaitUntilException.java │ │ ├── extension │ │ ├── DriverProvider.java │ │ ├── DriverProviderExtension.java │ │ ├── ExecuteJScriptMethodExtension.java │ │ ├── ExtractMethodExtension.java │ │ ├── FilterCollectionExtension.java │ │ ├── FindBy.java │ │ ├── FindByCollectionExtension.java │ │ ├── FindByExtension.java │ │ ├── Name.java │ │ ├── Page.java │ │ ├── PageExtension.java │ │ ├── Param.java │ │ ├── Path.java │ │ ├── Query.java │ │ ├── QueryMap.java │ │ ├── Selector.java │ │ ├── ShouldMethodExtension.java │ │ ├── ToStringMethodExtension.java │ │ ├── WaitUntilMethodExtension.java │ │ └── WrappedElementMethodExtension.java │ │ └── util │ │ └── MethodInfoUtils.java │ └── test │ └── java │ └── io │ └── qameta │ └── atlas │ └── webdriver │ ├── AtlasWebElementMethodsTest.java │ ├── ElementsCollectionTest.java │ ├── ExecuteJScriptMethodExtensionTest.java │ ├── ExtractCollectionExtensionTest.java │ ├── FilterCollectionExtensionTest.java │ ├── FindByParameterizedTest.java │ ├── FindByRetrierTest.java │ ├── FindByWithNameTest.java │ ├── FindByWithToStringExtensionTest.java │ ├── NestedElementMethodTest.java │ ├── PageExtensionTest.java │ ├── ShouldExtensionTest.java │ ├── ToStringExtensionTest.java │ ├── WaitUntilExtensionTest.java │ ├── WrappedElementMethodExtensionTest.java │ └── testdata │ └── ObjectFactory.java ├── build.gradle.kts ├── gradle.properties ├── gradle ├── bintray.gradle ├── maven.gradle ├── release.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── samples └── github │ ├── build.gradle │ └── src │ ├── main │ └── java │ │ └── io │ │ └── qameta │ │ └── atlas │ │ └── github │ │ ├── mobile │ │ ├── config │ │ │ └── MobileConfig.java │ │ └── page │ │ │ ├── ArticleScreen.java │ │ │ ├── MainScreen.java │ │ │ └── SearchScreen.java │ │ └── web │ │ ├── element │ │ ├── Header.java │ │ └── RepositoryCard.java │ │ ├── layout │ │ └── WithHeader.java │ │ ├── page │ │ ├── ContributorsPage.java │ │ ├── MainPage.java │ │ ├── ProjectPage.java │ │ └── SearchPage.java │ │ └── site │ │ └── GitHubSite.java │ └── test │ ├── java │ └── io │ │ └── qameta │ │ └── atlas │ │ └── github │ │ ├── FindRepositoryByNameTest.java │ │ ├── WebSiteTest.java │ │ ├── WikipediaTest.java │ │ └── rules │ │ └── UnZipResource.java │ └── resources │ ├── mobile-applications.zip │ └── mobile.properties └── settings.gradle /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - '*' 7 | push: 8 | branches: 9 | - 'main' 10 | 11 | jobs: 12 | build: 13 | name: "Build" 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/setup-java@v3 18 | with: 19 | distribution: 'zulu' 20 | java-version: '8.0.x' 21 | - run: ./gradlew build 22 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-java@v3 13 | with: 14 | distribution: 'zulu' 15 | java-version: '8.0.x' 16 | - name: Set up GPG 17 | run: echo -n "${GPG_PRIVATE_KEY}" | base64 --decode > ${GITHUB_WORKSPACE}/${GPG_KEY_ID}.gpg 18 | env: 19 | GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} 20 | GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} 21 | - name: "Gradle Build" 22 | run: ./gradlew build -Pversion=${GITHUB_REF:10} 23 | - name: "Gradle Publish" 24 | run: | 25 | ./gradlew publishToSonatype -Pversion=${GITHUB_REF:10} \ 26 | -Psigning.keyId=${GPG_KEY_ID} \ 27 | -Psigning.password=${GPG_PASSPHRASE} \ 28 | -Psigning.secretKeyRingFile=${GITHUB_WORKSPACE}/${GPG_KEY_ID}.gpg 29 | env: 30 | ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.OSSRH_USERNAME }} 31 | ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.OSSRH_PASSWORD }} 32 | GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} 33 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Idea 2 | .idea 3 | *.iml 4 | out 5 | 6 | # Gradle 7 | .gradle 8 | build -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { label 'java' } 3 | parameters { 4 | booleanParam(name: 'RELEASE', defaultValue: false, description: 'Perform release?') 5 | string(name: 'RELEASE_VERSION', defaultValue: '', description: 'Release version') 6 | string(name: 'NEXT_VERSION', defaultValue: '', description: 'Next version (without SNAPSHOT)') 7 | } 8 | stages { 9 | stage('Build') { 10 | steps { 11 | sh './gradlew build' 12 | } 13 | } 14 | stage('Release') { 15 | when { expression { return params.RELEASE } } 16 | steps { 17 | withCredentials([usernamePassword(credentialsId: 'qameta-ci_bintray', 18 | usernameVariable: 'BINTRAY_USER', passwordVariable: 'BINTRAY_API_KEY')]) { 19 | sshagent(['qameta-ci_ssh']) { 20 | sh 'git checkout master && git pull origin master' 21 | sh "./gradlew release -Prelease.useAutomaticVersion=true " + 22 | "-Prelease.releaseVersion=${RELEASE_VERSION} " + 23 | "-Prelease.newVersion=${NEXT_VERSION}-SNAPSHOT" 24 | } 25 | } 26 | } 27 | } 28 | } 29 | post { 30 | always { 31 | deleteDir() 32 | } 33 | failure { 34 | slackSend message: "${env.JOB_NAME} - #${env.BUILD_NUMBER} failed (<${env.BUILD_URL}|Open>)", 35 | color: 'danger', teamDomain: 'qameta', channel: 'atlas', tokenCredentialId: 'slack-token' 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /atlas-appium/build.gradle.kts: -------------------------------------------------------------------------------- 1 | description = "Atlas Appium" 2 | 3 | dependencies { 4 | api(project(":atlas-core")) 5 | api(project(":atlas-webdriver")) 6 | 7 | implementation("io.appium:java-client") 8 | implementation("org.seleniumhq.selenium:selenium-java") 9 | implementation("ru.yandex.qatools.matchers:webdriver-matchers") 10 | implementation("org.awaitility:awaitility") 11 | 12 | testImplementation("org.apache.commons:commons-lang3") 13 | testImplementation("org.mockito:mockito-core") 14 | testImplementation("org.assertj:assertj-core") 15 | testImplementation("junit:junit") 16 | } 17 | -------------------------------------------------------------------------------- /atlas-appium/src/main/java/io/qameta/atlas/appium/AppiumDriverConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium; 2 | 3 | import io.appium.java_client.AppiumDriver; 4 | import io.qameta.atlas.appium.context.AppiumDriverContext; 5 | import io.qameta.atlas.appium.extension.AppiumDriverProviderExtension; 6 | import io.qameta.atlas.appium.extension.AppiumFindByExtension; 7 | import io.qameta.atlas.appium.extension.LongPressExtension; 8 | import io.qameta.atlas.appium.extension.SwipeDownOnExtension; 9 | import io.qameta.atlas.appium.extension.SwipeUpOnExtension; 10 | import io.qameta.atlas.core.context.RetryerContext; 11 | import io.qameta.atlas.core.internal.Configuration; 12 | import io.qameta.atlas.core.internal.DefaultMethodExtension; 13 | import io.qameta.atlas.core.internal.EmptyRetryer; 14 | import io.qameta.atlas.webdriver.extension.ExtractMethodExtension; 15 | import io.qameta.atlas.webdriver.extension.FilterCollectionExtension; 16 | import io.qameta.atlas.webdriver.extension.FindByCollectionExtension; 17 | import io.qameta.atlas.webdriver.extension.ShouldMethodExtension; 18 | import io.qameta.atlas.webdriver.extension.ToStringMethodExtension; 19 | import io.qameta.atlas.webdriver.extension.WaitUntilMethodExtension; 20 | import io.qameta.atlas.webdriver.extension.WrappedElementMethodExtension; 21 | 22 | 23 | /** 24 | * Appium configuration. 25 | */ 26 | @SuppressWarnings("checkstyle:ClassDataAbstractionCoupling") 27 | public class AppiumDriverConfiguration extends Configuration { 28 | 29 | public AppiumDriverConfiguration(final AppiumDriver appiumDriver) { 30 | registerContext(new AppiumDriverContext(appiumDriver)); 31 | registerContext(new RetryerContext(new EmptyRetryer())); 32 | registerExtension(new AppiumDriverProviderExtension()); 33 | registerExtension(new DefaultMethodExtension()); 34 | registerExtension(new AppiumFindByExtension()); 35 | registerExtension(new ToStringMethodExtension()); 36 | registerExtension(new LongPressExtension()); 37 | registerExtension(new SwipeDownOnExtension()); 38 | registerExtension(new ShouldMethodExtension()); 39 | registerExtension(new SwipeUpOnExtension()); 40 | registerExtension(new WaitUntilMethodExtension()); 41 | registerExtension(new WrappedElementMethodExtension()); 42 | registerExtension(new FilterCollectionExtension()); 43 | registerExtension(new FindByCollectionExtension()); 44 | registerExtension(new ExtractMethodExtension()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /atlas-appium/src/main/java/io/qameta/atlas/appium/AtlasMobileElement.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium; 2 | 3 | 4 | import io.qameta.atlas.webdriver.AtlasWebElement; 5 | import org.openqa.selenium.WebElement; 6 | import org.openqa.selenium.WrapsElement; 7 | 8 | 9 | /** 10 | * @author Artem Sokovets. 11 | */ 12 | public interface AtlasMobileElement extends AtlasWebElement { 13 | 14 | void longPress(); 15 | 16 | /** 17 | * Swipe (Scroll) down to element. 18 | * @return {@link AtlasMobileElement} 19 | */ 20 | AtlasMobileElement swipeDownOn(); 21 | 22 | /** 23 | * Swipe (Scroll) up to element. 24 | * @return {@link AtlasMobileElement} 25 | */ 26 | AtlasMobileElement swipeUpOn(); 27 | 28 | /** 29 | * The same as {@link WrapsElement#getWrappedElement()}. 30 | */ 31 | @Override 32 | WebElement getWrappedElement(); 33 | } 34 | -------------------------------------------------------------------------------- /atlas-appium/src/main/java/io/qameta/atlas/appium/Indent.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium; 2 | 3 | /** 4 | * Indent for swipe(scroll). 5 | */ 6 | public enum Indent { 7 | BOTTOM(0.80), TOP(0.20), LEFT(0.30), RIGHT(0.70); 8 | 9 | private final double value; 10 | 11 | Indent(final double value) { 12 | this.value = value; 13 | } 14 | 15 | public double getValue() { 16 | return value; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /atlas-appium/src/main/java/io/qameta/atlas/appium/Screen.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium; 2 | 3 | import io.appium.java_client.AppiumDriver; 4 | import io.qameta.atlas.appium.annotations.AppiumDriverProvider; 5 | import org.openqa.selenium.SearchContext; 6 | import org.openqa.selenium.WrapsDriver; 7 | 8 | /** 9 | * Mobile Page. 10 | */ 11 | public interface Screen extends WrapsDriver, SearchContext { 12 | 13 | @Override 14 | @AppiumDriverProvider 15 | AppiumDriver getWrappedDriver(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /atlas-appium/src/main/java/io/qameta/atlas/appium/annotations/AndroidFindBy.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium.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 | * AndroidFindBy extension marker. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.METHOD) 13 | public @interface AndroidFindBy { 14 | String id() default ""; 15 | String xpath() default ""; 16 | } 17 | -------------------------------------------------------------------------------- /atlas-appium/src/main/java/io/qameta/atlas/appium/annotations/AppiumDriverProvider.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium.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 | * AppiumDriver provider marker. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.METHOD) 13 | public @interface AppiumDriverProvider { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /atlas-appium/src/main/java/io/qameta/atlas/appium/annotations/IOSFindBy.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium.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 | * IOSFindBy extension marker. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.METHOD) 13 | public @interface IOSFindBy { 14 | String id() default ""; 15 | String xpath() default ""; 16 | } 17 | -------------------------------------------------------------------------------- /atlas-appium/src/main/java/io/qameta/atlas/appium/context/AppiumDriverContext.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium.context; 2 | 3 | import io.appium.java_client.AppiumDriver; 4 | import io.qameta.atlas.core.api.Context; 5 | 6 | /** 7 | * AppiumDriverContext io.qameta.atlas.context. 8 | * Used to save AppiumDriver instance to {@link io.qameta.atlas.core.internal.Configuration}. 9 | */ 10 | public class AppiumDriverContext implements Context { 11 | 12 | private final AppiumDriver appiumDriver; 13 | 14 | public AppiumDriverContext(final AppiumDriver mobileDriver) { 15 | this.appiumDriver = mobileDriver; 16 | } 17 | 18 | @Override 19 | public AppiumDriver getValue() { 20 | return appiumDriver; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /atlas-appium/src/main/java/io/qameta/atlas/appium/extension/AppiumDriverProviderExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium.extension; 2 | 3 | import io.appium.java_client.AppiumDriver; 4 | import io.qameta.atlas.appium.annotations.AppiumDriverProvider; 5 | import io.qameta.atlas.core.api.MethodExtension; 6 | import io.qameta.atlas.appium.context.AppiumDriverContext; 7 | import io.qameta.atlas.core.internal.Configuration; 8 | import io.qameta.atlas.core.util.MethodInfo; 9 | 10 | import java.lang.reflect.Method; 11 | 12 | /** 13 | * Appium Driver provider extension. 14 | */ 15 | public class AppiumDriverProviderExtension implements MethodExtension { 16 | 17 | @Override 18 | public boolean test(final Method method) { 19 | return method.isAnnotationPresent(AppiumDriverProvider.class); 20 | } 21 | 22 | @Override 23 | public AppiumDriver invoke(final Object proxy, final MethodInfo methodInfo, final Configuration config) { 24 | return config.getContext(AppiumDriverContext.class).get().getValue(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /atlas-appium/src/main/java/io/qameta/atlas/appium/extension/AppiumFindByExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium.extension; 2 | 3 | import io.appium.java_client.AppiumDriver; 4 | import io.appium.java_client.android.AndroidDriver; 5 | import io.appium.java_client.ios.IOSDriver; 6 | import io.qameta.atlas.appium.annotations.AndroidFindBy; 7 | import io.qameta.atlas.appium.annotations.IOSFindBy; 8 | import io.qameta.atlas.appium.context.AppiumDriverContext; 9 | import io.qameta.atlas.core.Atlas; 10 | import io.qameta.atlas.core.AtlasException; 11 | import io.qameta.atlas.core.api.MethodExtension; 12 | import io.qameta.atlas.core.api.Target; 13 | import io.qameta.atlas.core.internal.Configuration; 14 | import io.qameta.atlas.core.target.LazyTarget; 15 | import io.qameta.atlas.core.util.MethodInfo; 16 | import io.qameta.atlas.webdriver.extension.Name; 17 | import org.openqa.selenium.By; 18 | import org.openqa.selenium.SearchContext; 19 | import org.openqa.selenium.WebElement; 20 | 21 | import java.lang.reflect.Method; 22 | import java.util.Map; 23 | import java.util.Optional; 24 | import java.util.stream.Stream; 25 | 26 | import static io.qameta.atlas.appium.extension.AppiumFindByExtension.TypeLocator.ID; 27 | import static io.qameta.atlas.appium.extension.AppiumFindByExtension.TypeLocator.XPATH; 28 | import static io.qameta.atlas.webdriver.util.MethodInfoUtils.getParamValues; 29 | import static io.qameta.atlas.webdriver.util.MethodInfoUtils.processParamTemplate; 30 | 31 | /** 32 | * FindBy for both platform (Android and IOS). 33 | */ 34 | public class AppiumFindByExtension implements MethodExtension { 35 | 36 | @Override 37 | public boolean test(final Method method) { 38 | return method.isAnnotationPresent(IOSFindBy.class) 39 | || method.isAnnotationPresent(AndroidFindBy.class) 40 | && WebElement.class.isAssignableFrom(method.getReturnType()); 41 | } 42 | 43 | @Override 44 | public Object invoke(final Object proxy, final MethodInfo methodInfo, final Configuration configuration) { 45 | assert proxy instanceof SearchContext; 46 | 47 | final Method method = methodInfo.getMethod(); 48 | final Map parameters = getParamValues(method, methodInfo.getArgs()); 49 | final AppiumDriver driver = configuration.getContext(AppiumDriverContext.class) 50 | .orElseThrow(() -> new AtlasException("AppiumDriver is missing")).getValue(); 51 | 52 | final By locator; 53 | if (driver instanceof AndroidDriver) { 54 | final String xpath = processParamTemplate(method.getAnnotation(AndroidFindBy.class).xpath(), parameters); 55 | final String id = processParamTemplate(method.getAnnotation(AndroidFindBy.class).id(), parameters); 56 | locator = getByLocator(new LocatorWrapper(XPATH, xpath), new LocatorWrapper(ID, id)); 57 | 58 | } else if (driver instanceof IOSDriver) { 59 | final String xpath = processParamTemplate(method.getAnnotation(IOSFindBy.class).xpath(), parameters); 60 | final String id = processParamTemplate(method.getAnnotation(IOSFindBy.class).id(), parameters); 61 | locator = getByLocator(new LocatorWrapper(XPATH, xpath), new LocatorWrapper(ID, id)); 62 | 63 | } else { 64 | throw new AtlasException("Can not identified driver"); 65 | } 66 | 67 | final SearchContext searchContext = (SearchContext) proxy; 68 | final Configuration childConfiguration = configuration.child(); 69 | final String name = Optional.ofNullable(method.getAnnotation(Name.class)) 70 | .map(Name::value) 71 | .map(template -> processParamTemplate(template, parameters)) 72 | .orElse(method.getName()); 73 | final Target target = new LazyTarget(name, () -> searchContext.findElement(locator)); 74 | return new Atlas(childConfiguration).create(target, method.getReturnType()); 75 | } 76 | 77 | private By getByLocator(final LocatorWrapper... locatorWrappers) { 78 | return Stream.of(locatorWrappers) 79 | .filter(LocatorWrapper::isNotEmptyLocator) 80 | .map(LocatorWrapper::toBy) 81 | .findFirst() 82 | .orElseThrow(() -> new AtlasException("No valid locators found")); 83 | } 84 | 85 | /** 86 | * Type of locator. 87 | */ 88 | enum TypeLocator { 89 | ID, XPATH 90 | } 91 | 92 | /** 93 | * Locator's wrapper. 94 | */ 95 | private static final class LocatorWrapper { 96 | private final TypeLocator type; 97 | private final String locator; 98 | 99 | LocatorWrapper(final TypeLocator type, final String locator) { 100 | this.type = type; 101 | this.locator = locator; 102 | } 103 | 104 | private boolean isNotEmptyLocator() { 105 | return !this.locator.isEmpty(); 106 | } 107 | 108 | private By toBy() { 109 | if (XPATH == type) { 110 | return By.xpath(locator); 111 | } else if (ID == type) { 112 | return By.id(locator); 113 | } else { 114 | throw new UnsupportedOperationException("Unsupported locator type"); 115 | } 116 | } 117 | } 118 | } 119 | 120 | 121 | -------------------------------------------------------------------------------- /atlas-appium/src/main/java/io/qameta/atlas/appium/extension/LongPressExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium.extension; 2 | 3 | import io.appium.java_client.AppiumDriver; 4 | import io.appium.java_client.PerformsTouchActions; 5 | import io.appium.java_client.TouchAction; 6 | import io.appium.java_client.touch.offset.PointOption; 7 | import io.qameta.atlas.core.AtlasException; 8 | import io.qameta.atlas.core.api.MethodExtension; 9 | import io.qameta.atlas.appium.context.AppiumDriverContext; 10 | import io.qameta.atlas.core.internal.Configuration; 11 | import io.qameta.atlas.core.util.MethodInfo; 12 | import org.openqa.selenium.Dimension; 13 | import org.openqa.selenium.Point; 14 | import org.openqa.selenium.WebElement; 15 | 16 | import java.lang.reflect.Method; 17 | import java.time.Duration; 18 | import java.time.temporal.ChronoUnit; 19 | 20 | import static io.appium.java_client.touch.LongPressOptions.longPressOptions; 21 | 22 | /** 23 | * LongPress. 24 | */ 25 | public class LongPressExtension implements MethodExtension { 26 | 27 | private static final Duration LONG_TAP_DURATION = Duration.of(1000, ChronoUnit.MILLIS); 28 | 29 | @Override 30 | public boolean test(final Method method) { 31 | return "longPress".equals(method.getName()); 32 | } 33 | 34 | @Override 35 | public Object invoke(final Object proxy, final MethodInfo methodInfo, final Configuration configuration) { 36 | final AppiumDriver driver = configuration.getContext(AppiumDriverContext.class) 37 | .orElseThrow(() -> new AtlasException("AppiumDriver is missing")).getValue(); 38 | 39 | final TouchAction action = new TouchAction((PerformsTouchActions) driver); 40 | final Point location = ((WebElement) proxy).getLocation(); 41 | final Dimension size = ((WebElement) proxy).getSize(); 42 | final int x = location.getX() + size.width / 2; 43 | final int y = location.getY() + size.height / 2; 44 | action.longPress(longPressOptions().withDuration(LONG_TAP_DURATION) 45 | .withPosition(new PointOption().withCoordinates(x, y))).perform(); 46 | return proxy; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /atlas-appium/src/main/java/io/qameta/atlas/appium/extension/SwipeDownOnExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium.extension; 2 | 3 | import io.appium.java_client.AppiumDriver; 4 | import io.appium.java_client.PerformsTouchActions; 5 | import io.appium.java_client.TouchAction; 6 | import io.appium.java_client.touch.offset.PointOption; 7 | import io.qameta.atlas.core.AtlasException; 8 | import io.qameta.atlas.core.api.MethodExtension; 9 | import io.qameta.atlas.appium.context.AppiumDriverContext; 10 | import io.qameta.atlas.core.internal.Configuration; 11 | import io.qameta.atlas.core.util.MethodInfo; 12 | import org.openqa.selenium.Dimension; 13 | 14 | import java.lang.reflect.Method; 15 | 16 | import static io.qameta.atlas.appium.Indent.BOTTOM; 17 | import static io.qameta.atlas.appium.Indent.TOP; 18 | import static java.util.concurrent.TimeUnit.SECONDS; 19 | import static org.awaitility.Awaitility.given; 20 | import static ru.yandex.qatools.matchers.webdriver.DisplayedMatcher.displayed; 21 | 22 | /** 23 | * Swipe (Scroll) down to element. 24 | */ 25 | public class SwipeDownOnExtension implements MethodExtension { 26 | 27 | 28 | @Override 29 | public boolean test(final Method method) { 30 | return "swipeDownOn".equals(method.getName()); 31 | } 32 | 33 | @Override 34 | public Object invoke(final Object proxy, final MethodInfo methodInfo, final Configuration configuration) { 35 | final AppiumDriver driver = configuration.getContext(AppiumDriverContext.class) 36 | .orElseThrow(() -> new AtlasException("AppiumDriver is missing")).getValue(); 37 | 38 | given().atMost(120, SECONDS).pollInterval(1, SECONDS).ignoreExceptions().until(() -> { 39 | if (!displayed().matches(proxy)) { 40 | final Dimension size = driver.manage().window().getSize(); 41 | final int startX = size.width / 2; 42 | final int startY = (int) (size.height * BOTTOM.getValue()); 43 | final int endX = size.width / 2; 44 | final int endY = (int) (size.height * TOP.getValue()); 45 | 46 | final TouchAction touchAction = new TouchAction((PerformsTouchActions) driver); 47 | touchAction.longPress(new PointOption().withCoordinates(startX, startY)) 48 | .moveTo(new PointOption().withCoordinates(endX, endY)).release().perform(); 49 | } 50 | return displayed().matches(proxy); 51 | }); 52 | return proxy; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /atlas-appium/src/main/java/io/qameta/atlas/appium/extension/SwipeUpOnExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium.extension; 2 | 3 | import io.appium.java_client.AppiumDriver; 4 | import io.appium.java_client.PerformsTouchActions; 5 | import io.appium.java_client.TouchAction; 6 | import io.appium.java_client.touch.offset.PointOption; 7 | import io.qameta.atlas.appium.context.AppiumDriverContext; 8 | import io.qameta.atlas.core.AtlasException; 9 | import io.qameta.atlas.core.api.MethodExtension; 10 | import io.qameta.atlas.core.internal.Configuration; 11 | import io.qameta.atlas.core.util.MethodInfo; 12 | import org.openqa.selenium.Dimension; 13 | 14 | import java.lang.reflect.Method; 15 | 16 | import static io.qameta.atlas.appium.Indent.BOTTOM; 17 | import static io.qameta.atlas.appium.Indent.TOP; 18 | import static java.util.concurrent.TimeUnit.SECONDS; 19 | import static org.awaitility.Awaitility.given; 20 | import static ru.yandex.qatools.matchers.webdriver.DisplayedMatcher.displayed; 21 | 22 | /** 23 | * Swipe (Scroll) up to element. 24 | */ 25 | public class SwipeUpOnExtension implements MethodExtension { 26 | 27 | 28 | @Override 29 | public boolean test(final Method method) { 30 | return "swipeUpOn".equals(method.getName()); 31 | } 32 | 33 | @Override 34 | public Object invoke(final Object proxy, final MethodInfo methodInfo, final Configuration configuration) { 35 | final AppiumDriver driver = configuration.getContext(AppiumDriverContext.class) 36 | .orElseThrow(() -> new AtlasException("AppiumDriver is missing")).getValue(); 37 | 38 | given().atMost(120, SECONDS).pollInterval(1, SECONDS).ignoreExceptions().until(() -> { 39 | if (!displayed().matches(proxy)) { 40 | final Dimension size = driver.manage().window().getSize(); 41 | final int startX = size.width / 2; 42 | final int startY = (int) (size.height * TOP.getValue()); 43 | final int endX = size.width / 2; 44 | final int endY = (int) (size.height * BOTTOM.getValue()); 45 | 46 | final TouchAction touchAction = new TouchAction((PerformsTouchActions) driver); 47 | touchAction.longPress(new PointOption().withCoordinates(startX, startY)) 48 | .moveTo(new PointOption().withCoordinates(endX, endY)).release().perform(); 49 | } 50 | return displayed().matches(proxy); 51 | }); 52 | return proxy; 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /atlas-appium/src/test/java/io/qameta/atlas/appium/AppiumFindByRetrierTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium; 2 | 3 | import io.qameta.atlas.appium.annotations.AndroidFindBy; 4 | import io.qameta.atlas.core.Atlas; 5 | import io.qameta.atlas.core.api.Retry; 6 | import org.junit.Test; 7 | import org.openqa.selenium.By; 8 | import org.openqa.selenium.NotFoundException; 9 | import org.openqa.selenium.WebElement; 10 | 11 | import static io.qameta.atlas.appium.testdata.ObjectFactory.mockAndroidDriver; 12 | import static io.qameta.atlas.appium.testdata.ObjectFactory.mockWebElement; 13 | import static org.mockito.Mockito.when; 14 | 15 | /** 16 | * Check retries functional for AtlasMobileElement. 17 | */ 18 | public class AppiumFindByRetrierTest { 19 | 20 | @Test(expected = NotFoundException.class) 21 | public void retryChildFind() { 22 | final WebElement parentOrigin = mockWebElement(); 23 | final WebElement childOrigin = mockWebElement(); 24 | 25 | when(parentOrigin.findElement(By.xpath("//div"))).thenThrow(new NotFoundException()); 26 | when(childOrigin.findElement(By.xpath("//"))).thenThrow(new NotFoundException()); 27 | 28 | final Atlas atlas = new Atlas(new AppiumDriverConfiguration(mockAndroidDriver())); 29 | final ParentElement parent = atlas.create(parentOrigin, ParentElement.class); 30 | 31 | parent.child().isDisplayed(); 32 | } 33 | 34 | /** 35 | * Parent mobile element. 36 | */ 37 | private interface ParentElement extends AtlasMobileElement { 38 | @Retry(timeout = 1000L) 39 | @AndroidFindBy(xpath = "//div") 40 | NestedElement child(); 41 | } 42 | 43 | /** 44 | * Child mobile element. 45 | */ 46 | private interface NestedElement extends AtlasMobileElement { 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /atlas-appium/src/test/java/io/qameta/atlas/appium/AppiumFindByTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium; 2 | 3 | import io.qameta.atlas.appium.annotations.AndroidFindBy; 4 | import io.qameta.atlas.appium.extension.AppiumFindByExtension; 5 | import io.qameta.atlas.core.Atlas; 6 | import io.qameta.atlas.webdriver.AtlasWebElement; 7 | import io.qameta.atlas.webdriver.extension.Name; 8 | import io.qameta.atlas.webdriver.extension.Param; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.openqa.selenium.By; 12 | import org.openqa.selenium.WebElement; 13 | 14 | import static io.qameta.atlas.appium.testdata.ObjectFactory.mockAndroidDriver; 15 | import static io.qameta.atlas.appium.testdata.ObjectFactory.mockWebElement; 16 | import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; 17 | import static org.junit.Assert.assertEquals; 18 | import static org.mockito.ArgumentMatchers.any; 19 | import static org.mockito.Mockito.times; 20 | import static org.mockito.Mockito.verify; 21 | import static org.mockito.Mockito.when; 22 | 23 | public class AppiumFindByTest { 24 | 25 | private WebElement parent; 26 | private Atlas atlas; 27 | 28 | @Before 29 | public void setUp() { 30 | parent = mockWebElement(); 31 | atlas = new Atlas(new AppiumDriverConfiguration(mockAndroidDriver())) 32 | .extension(new AppiumFindByExtension()); 33 | } 34 | 35 | @Test 36 | public void shouldParameterizedFindBy() { 37 | when(parent.findElement(any(By.class))).thenReturn(mockWebElement()); 38 | 39 | String param = randomAlphanumeric(10); 40 | 41 | ParentElement atlasMobileElement = atlas.create(parent, ParentElement.class); 42 | atlasMobileElement.childElement(param).isDisplayed(); 43 | 44 | verify(parent, times(1)).findElement(By.xpath(String.format("//div[%s]", param))); 45 | } 46 | 47 | @Test 48 | public void shouldParameterizedName() { 49 | when(parent.findElement(any(By.class))).thenReturn(mockWebElement()); 50 | 51 | String param = randomAlphanumeric(10); 52 | 53 | ParentElement atlasMobileElement = atlas.create(parent, ParentElement.class); 54 | 55 | assertEquals(String.format("element %s", param), atlasMobileElement.childElement(param).toString()); 56 | } 57 | 58 | private interface ParentElement extends AtlasWebElement { 59 | @Name("element {{ value }}") 60 | @AndroidFindBy(xpath = "//div[{{ value }}]") 61 | AtlasWebElement childElement(@Param("value") String value); 62 | } 63 | 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /atlas-appium/src/test/java/io/qameta/atlas/appium/AtlasMobileElementMethodsTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.core.internal.DefaultRetryer; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.openqa.selenium.By; 8 | import org.openqa.selenium.WebElement; 9 | 10 | import java.util.Collections; 11 | 12 | import static io.qameta.atlas.appium.testdata.ObjectFactory.mockWebElement; 13 | import static org.mockito.Mockito.times; 14 | import static org.mockito.Mockito.verify; 15 | 16 | /** 17 | * Check methods of AtlasMobileElement, that it are invoked. 18 | */ 19 | public class AtlasMobileElementMethodsTest { 20 | 21 | private AtlasMobileElement atlasMobileElement; 22 | private WebElement originElement; 23 | 24 | @Before 25 | public void initElements() { 26 | originElement = mockWebElement(); 27 | atlasMobileElement = new Atlas() 28 | .create(originElement, AtlasMobileElement.class); 29 | } 30 | 31 | @Test 32 | public void clickMethodTest() { 33 | atlasMobileElement.click(); 34 | verify(originElement, times(1)).click(); 35 | } 36 | 37 | @Test 38 | public void sendKeysMethodTest() { 39 | atlasMobileElement.sendKeys(); 40 | verify(originElement, times(1)).sendKeys(); 41 | } 42 | 43 | @Test 44 | public void isEnabledMethodTest() { 45 | atlasMobileElement.isEnabled(); 46 | verify(originElement, times(1)).isEnabled(); 47 | } 48 | 49 | @Test 50 | public void getTextMethodTest() { 51 | atlasMobileElement.getText(); 52 | verify(originElement, times(1)).getText(); 53 | } 54 | 55 | @Test 56 | public void findElementsMethodTest() { 57 | atlasMobileElement.findElements(By.xpath("")); 58 | verify(originElement, times(1)).findElements(By.xpath("")); 59 | } 60 | 61 | @Test 62 | public void isDisplayedMethodTest() { 63 | atlasMobileElement.isDisplayed(); 64 | verify(originElement, times(1)).isDisplayed(); 65 | } 66 | 67 | @Test 68 | public void findElement() { 69 | atlasMobileElement.findElement(By.xpath("//")); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /atlas-appium/src/test/java/io/qameta/atlas/appium/LongPressExtensionTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium; 2 | 3 | import io.qameta.atlas.appium.extension.LongPressExtension; 4 | import io.qameta.atlas.core.Atlas; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.openqa.selenium.Dimension; 8 | import org.openqa.selenium.Point; 9 | import org.openqa.selenium.WebElement; 10 | 11 | import static io.qameta.atlas.appium.testdata.ObjectFactory.mockAndroidDriver; 12 | import static io.qameta.atlas.appium.testdata.ObjectFactory.mockWebElement; 13 | import static org.mockito.Mockito.mock; 14 | import static org.mockito.Mockito.when; 15 | 16 | /** 17 | * Check long press method of AtlasMobileElement, that it are invoked. 18 | */ 19 | public class LongPressExtensionTest { 20 | 21 | private AtlasMobileElement mobileElement; 22 | private WebElement parentElement = mockWebElement(); 23 | 24 | @Before 25 | public void createAtlasElementWithExtension() { 26 | mobileElement = new Atlas(new AppiumDriverConfiguration(mockAndroidDriver())) 27 | .extension(new LongPressExtension()) 28 | .create(parentElement, AtlasMobileElement.class); 29 | } 30 | 31 | 32 | @Test 33 | public void shouldInvokeLongPressExtensionMethodName() { 34 | when(parentElement.getLocation()).thenReturn(mock(Point.class)); 35 | when(parentElement.getSize()).thenReturn(mock(Dimension.class)); 36 | mobileElement.longPress(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /atlas-appium/src/test/java/io/qameta/atlas/appium/testdata/ObjectFactory.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.appium.testdata; 2 | 3 | import io.appium.java_client.AppiumDriver; 4 | import io.appium.java_client.android.AndroidDriver; 5 | import io.appium.java_client.ios.IOSDriver; 6 | import io.qameta.atlas.appium.AtlasMobileElement; 7 | import io.qameta.atlas.webdriver.AtlasWebElement; 8 | import org.openqa.selenium.WebElement; 9 | import org.openqa.selenium.WrapsDriver; 10 | 11 | import static org.mockito.Mockito.mock; 12 | import static org.mockito.Mockito.withSettings; 13 | 14 | /** 15 | * Factory of mocks for AppiumDriver, MobileElement, AtlasMobileElement. 16 | */ 17 | public final class ObjectFactory { 18 | 19 | private ObjectFactory() { 20 | } 21 | 22 | public static AppiumDriver mockAppiumDriver() { 23 | return mock(AppiumDriver.class, withSettings().extraInterfaces(WrapsDriver.class)); 24 | } 25 | 26 | public static AppiumDriver mockAndroidDriver() { 27 | return mock(AndroidDriver.class, withSettings().extraInterfaces(WrapsDriver.class)); 28 | } 29 | 30 | 31 | public static IOSDriver mockIOSDriver() { 32 | return mock(IOSDriver.class, withSettings().extraInterfaces(WrapsDriver.class)); 33 | } 34 | 35 | public static WebElement mockWebElement() { 36 | return mock(WebElement.class); 37 | } 38 | 39 | public static AtlasMobileElement mockAtlasMobileElement() { 40 | return mock(AtlasMobileElement.class, withSettings().extraInterfaces(AtlasWebElement.class)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /atlas-core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | description = "Atlas Core" 2 | 3 | dependencies { 4 | implementation("org.apache.commons:commons-lang3") 5 | implementation("org.slf4j:slf4j-api") 6 | 7 | testImplementation("org.mockito:mockito-core") 8 | testImplementation("org.assertj:assertj-core") 9 | testImplementation("junit:junit") 10 | } 11 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/Atlas.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core; 2 | 3 | import io.qameta.atlas.core.api.Context; 4 | import io.qameta.atlas.core.api.Listener; 5 | import io.qameta.atlas.core.api.MethodExtension; 6 | import io.qameta.atlas.core.api.MethodInvoker; 7 | import io.qameta.atlas.core.api.Target; 8 | import io.qameta.atlas.core.context.TargetContext; 9 | import io.qameta.atlas.core.internal.*; 10 | import io.qameta.atlas.core.target.HardcodedTarget; 11 | 12 | import java.lang.reflect.Method; 13 | import java.lang.reflect.Proxy; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | import static io.qameta.atlas.core.util.ReflectionUtils.getMethods; 19 | 20 | /** 21 | * @author Artem Eroshenko. 22 | */ 23 | public class Atlas { 24 | 25 | private final Configuration configuration; 26 | 27 | public Atlas() { 28 | this(new Configuration()); 29 | } 30 | 31 | public Atlas(final Configuration configuration) { 32 | this.configuration = configuration; 33 | } 34 | 35 | public Atlas listener(final Listener listener) { 36 | this.configuration.registerExtension(listener); 37 | return this; 38 | } 39 | 40 | public Atlas extension(final MethodExtension methodExtension) { 41 | this.configuration.registerExtension(methodExtension); 42 | return this; 43 | } 44 | 45 | public Atlas context(final Context context) { 46 | this.configuration.registerContext(context); 47 | return this; 48 | } 49 | 50 | @SuppressWarnings("unchecked") 51 | public T create(final Target target, final Class type) { 52 | final Map invokers = new HashMap<>(); 53 | final List methods = getMethods(type, Object.class); 54 | this.context(new TargetContext(target)); 55 | 56 | methods.forEach(method -> { 57 | final MethodInvoker invoker = configuration.getExtensions(MethodExtension.class).stream() 58 | .filter(extension -> extension.test(method)).map(MethodInvoker.class::cast).findFirst() 59 | .orElse(new TargetMethodInvoker()); 60 | invokers.put(method, invoker); 61 | }); 62 | 63 | final ListenerNotifier notifier = new ListenerNotifier(); 64 | configuration.getExtensions(Listener.class).forEach(notifier::addListeners); 65 | 66 | return (T) Proxy.newProxyInstance( 67 | type.getClassLoader(), 68 | new Class[]{type}, 69 | new AtlasMethodHandler(configuration, notifier, invokers) 70 | ); 71 | } 72 | 73 | public T create(final String name, final Object target, final Class type) { 74 | return create(new HardcodedTarget(name, target), type); 75 | } 76 | 77 | public T create(final Object target, final Class type) { 78 | return create(type.getSimpleName(), target, type); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/AtlasException.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core; 2 | 3 | /** 4 | * @author ero 5 | */ 6 | public class AtlasException extends RuntimeException { 7 | 8 | public AtlasException(final String message) { 9 | super(message); 10 | } 11 | 12 | public AtlasException(final String message, final Exception exception) { 13 | super(message, exception); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/api/Context.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.api; 2 | 3 | /** 4 | * Main context. 5 | * @param returned value. 6 | */ 7 | @FunctionalInterface 8 | public interface Context extends Extension { 9 | 10 | /** 11 | * Returns the context value. 12 | * 13 | * @return the context value. 14 | */ 15 | T getValue(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/api/Extension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.api; 2 | 3 | /** 4 | * @author Artem Eroshenko. 5 | */ 6 | public interface Extension { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/api/Listener.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.api; 2 | 3 | import io.qameta.atlas.core.internal.Configuration; 4 | import io.qameta.atlas.core.util.MethodInfo; 5 | 6 | /** 7 | * Listener. 8 | */ 9 | public interface Listener extends Extension { 10 | 11 | void beforeMethodCall(MethodInfo methodInfo, Configuration configuration); 12 | 13 | void afterMethodCall(MethodInfo methodInfo, Configuration configuration); 14 | 15 | void onMethodReturn(MethodInfo methodInfo, Configuration configuration, Object returned); 16 | 17 | void onMethodFailure(MethodInfo methodInfo, Configuration configuration, Throwable throwable); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/api/MethodExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.api; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.function.Predicate; 5 | 6 | /** 7 | * Basic extension point. 8 | */ 9 | public interface MethodExtension extends Predicate, MethodInvoker, Extension { 10 | } 11 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/api/MethodInvoker.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.api; 2 | 3 | import io.qameta.atlas.core.internal.Configuration; 4 | import io.qameta.atlas.core.util.MethodInfo; 5 | 6 | /** 7 | * Method invoker. 8 | */ 9 | @FunctionalInterface 10 | public interface MethodInvoker { 11 | 12 | Object invoke(Object proxy, MethodInfo methodInfo, Configuration config) throws Throwable; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/api/Retry.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.api; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | 7 | /** 8 | * Retry configuration. 9 | */ 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @java.lang.annotation.Target(ElementType.METHOD) 12 | public @interface Retry { 13 | 14 | long timeout() default 5000L; 15 | 16 | long polling() default 1000L; 17 | 18 | Class[] ignoring() default {Throwable.class}; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/api/Target.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.api; 2 | 3 | /** 4 | * @author Artem Eroshenko. 5 | */ 6 | public interface Target { 7 | 8 | String name(); 9 | 10 | Object instance(); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/api/Timeout.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.api; 2 | 3 | import java.lang.annotation.Target; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | /** 9 | * @author eroshenkoam (Artem Eroshenko). 10 | */ 11 | @Target(ElementType.PARAMETER) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Timeout { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/context/RetryerContext.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.context; 2 | 3 | import io.qameta.atlas.core.api.Context; 4 | import io.qameta.atlas.core.internal.Retryer; 5 | 6 | /** 7 | * Retry context. 8 | */ 9 | public class RetryerContext implements Context { 10 | 11 | private final Retryer retry; 12 | 13 | public RetryerContext(final Retryer retry) { 14 | this.retry = retry; 15 | } 16 | 17 | @Override 18 | public Retryer getValue() { 19 | return retry; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/context/TargetContext.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.context; 2 | 3 | import io.qameta.atlas.core.api.Context; 4 | import io.qameta.atlas.core.api.Target; 5 | 6 | /** 7 | * Target context. 8 | */ 9 | public class TargetContext implements Context { 10 | 11 | private final Target target; 12 | 13 | public TargetContext(final Target target) { 14 | this.target = target; 15 | } 16 | 17 | @Override 18 | public Target getValue() { 19 | return target; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/internal/AtlasMethodHandler.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.internal; 2 | 3 | import io.qameta.atlas.core.api.MethodInvoker; 4 | import io.qameta.atlas.core.api.Retry; 5 | import io.qameta.atlas.core.api.Timeout; 6 | import io.qameta.atlas.core.context.RetryerContext; 7 | import io.qameta.atlas.core.util.MethodInfo; 8 | 9 | import java.lang.reflect.InvocationHandler; 10 | import java.lang.reflect.Method; 11 | import java.util.Map; 12 | import java.util.Optional; 13 | 14 | /** 15 | * Atlas method handler. 16 | */ 17 | public class AtlasMethodHandler implements InvocationHandler { 18 | 19 | private final ListenerNotifier notifier; 20 | 21 | private final Configuration configuration; 22 | 23 | private final Map handlers; 24 | 25 | public AtlasMethodHandler(final Configuration configuration, 26 | final ListenerNotifier listenerNotifier, 27 | final Map handlers) { 28 | this.configuration = configuration; 29 | this.notifier = listenerNotifier; 30 | this.handlers = handlers; 31 | } 32 | 33 | @Override 34 | public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { 35 | final MethodInfo methodInfo = new MethodInfo(method, args); 36 | 37 | notifier.beforeMethodCall(methodInfo, configuration); 38 | try { 39 | final MethodInvoker handler = handlers.get(method); 40 | final Object result = invokeWithRetry(handler, proxy, methodInfo); 41 | notifier.onMethodReturn(methodInfo, configuration, result); 42 | return result; 43 | } catch (Throwable e) { 44 | notifier.onMethodFailure(methodInfo, configuration, e); 45 | throw e; 46 | } finally { 47 | notifier.afterMethodCall(methodInfo, configuration); 48 | } 49 | } 50 | 51 | private Object invokeWithRetry(final MethodInvoker invoker, 52 | final Object proxy, 53 | final MethodInfo methodInfo) throws Throwable { 54 | final Retryer retryer = Optional.ofNullable(methodInfo.getMethod().getAnnotation(Retry.class)) 55 | .map(DefaultRetryer::new) 56 | .map(Retryer.class::cast) 57 | .orElseGet(() -> configuration.getContext(RetryerContext.class) 58 | .orElseGet(() -> new RetryerContext(new EmptyRetryer())).getValue()); 59 | methodInfo.getParameter(Integer.class, Timeout.class).ifPresent(retryer::timeoutInSeconds); 60 | Throwable lastException; 61 | final long start = System.currentTimeMillis(); 62 | do { 63 | try { 64 | return invoker.invoke(proxy, methodInfo, configuration); 65 | } catch (Throwable e) { 66 | lastException = e; 67 | } 68 | } while (retryer.shouldRetry(start, lastException)); 69 | throw lastException; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/internal/Configuration.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.internal; 2 | 3 | import io.qameta.atlas.core.AtlasException; 4 | import io.qameta.atlas.core.api.Context; 5 | import io.qameta.atlas.core.api.Extension; 6 | 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Optional; 11 | import java.util.stream.Collectors; 12 | 13 | /** 14 | * Atlas configuration. 15 | */ 16 | public class Configuration { 17 | 18 | private final Map, Extension> extensions; 19 | 20 | public Configuration() { 21 | this.extensions = new HashMap<>(); 22 | } 23 | 24 | public void registerExtension(final Extension extension) { 25 | this.extensions.put(extension.getClass(), extension); 26 | } 27 | 28 | public void registerContext(final Context context) { 29 | this.extensions.put(context.getClass(), context); 30 | } 31 | 32 | public List getExtensions(final Class extensionType) { 33 | return extensions.values().stream() 34 | .filter(extensionType::isInstance) 35 | .map(extensionType::cast) 36 | .collect(Collectors.toList()); 37 | } 38 | 39 | public Optional getContext(final Class contextType) { 40 | return extensions.values().stream() 41 | .filter(contextType::isInstance) 42 | .map(contextType::cast) 43 | .findFirst(); 44 | } 45 | 46 | public T requireContext(final Class contextType) { 47 | return getContext(contextType) 48 | .orElseThrow(() -> new AtlasException("Context not found by type " + contextType)); 49 | } 50 | 51 | public Configuration child() { 52 | final Configuration configuration = new Configuration(); 53 | this.getExtensions(Extension.class).forEach(configuration::registerExtension); 54 | return configuration; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/internal/DefaultMethodExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.internal; 2 | 3 | import io.qameta.atlas.core.api.MethodExtension; 4 | import io.qameta.atlas.core.util.MethodInfo; 5 | import org.apache.commons.lang3.JavaVersion; 6 | 7 | import java.lang.invoke.MethodHandle; 8 | import java.lang.invoke.MethodHandles; 9 | import java.lang.invoke.MethodType; 10 | import java.lang.reflect.Constructor; 11 | import java.lang.reflect.Method; 12 | 13 | import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost; 14 | 15 | /** 16 | * Default method extension. 17 | */ 18 | public class DefaultMethodExtension implements MethodExtension { 19 | 20 | @Override 21 | public boolean test(final Method method) { 22 | return method.isDefault(); 23 | } 24 | 25 | @Override 26 | public Object invoke(final Object proxy, final MethodInfo methodInfo, final Configuration config) throws Throwable { 27 | final Class declaringClass = methodInfo.getMethod().getDeclaringClass(); 28 | 29 | if (isJavaVersionAtMost(JavaVersion.JAVA_1_8)) { 30 | final Constructor constructor = MethodHandles.Lookup.class 31 | .getDeclaredConstructor(Class.class, int.class); 32 | constructor.setAccessible(true); 33 | return constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE) 34 | .unreflectSpecial(methodInfo.getMethod(), declaringClass) 35 | .bindTo(proxy) 36 | .invokeWithArguments(methodInfo.getArgs()); 37 | } 38 | 39 | final MethodHandle methodHandle = MethodHandles.lookup().findSpecial( 40 | declaringClass, 41 | methodInfo.getMethod().getName(), 42 | MethodType.methodType( 43 | methodInfo.getMethod().getReturnType(), 44 | methodInfo.getMethod().getParameterTypes() 45 | ), 46 | declaringClass 47 | ); 48 | 49 | return methodHandle 50 | .bindTo(proxy) 51 | .invokeWithArguments(methodInfo.getArgs()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/internal/DefaultRetryer.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.internal; 2 | 3 | import io.qameta.atlas.core.api.Retry; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * Retryer. 12 | */ 13 | @SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName") 14 | public class DefaultRetryer implements Retryer { 15 | 16 | private final List> ignoring; 17 | 18 | private Long timeout; 19 | 20 | private Long polling; 21 | 22 | public DefaultRetryer(final Retry annotation) { 23 | this(annotation.timeout(), annotation.polling(), Arrays.asList(annotation.ignoring())); 24 | } 25 | 26 | public DefaultRetryer(final Long timeout, final Long polling, final List> ignoring) { 27 | this.ignoring = new ArrayList<>(ignoring); 28 | this.timeout = timeout; 29 | this.polling = polling; 30 | } 31 | 32 | public void ignore(final Class throwable) { 33 | this.ignoring.add(throwable); 34 | } 35 | 36 | public void timeoutInMillis(final Long millis) { 37 | this.timeout = millis; 38 | } 39 | 40 | @Override 41 | public void timeoutInSeconds(final int seconds) { 42 | this.timeout = TimeUnit.SECONDS.toMillis(seconds); 43 | } 44 | 45 | public void polling(final Long polling) { 46 | this.polling = polling; 47 | } 48 | 49 | @Override 50 | public boolean shouldRetry(final long start, final Throwable e) { 51 | return shouldRetry(start, timeout, polling, ignoring, e); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/internal/EmptyRetryer.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.internal; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | /** 8 | * Retryer with default values. 9 | */ 10 | public class EmptyRetryer implements Retryer { 11 | 12 | private final Long polling; 13 | private final List> ignoring; 14 | private Long timeout; 15 | 16 | public EmptyRetryer() { 17 | this.timeout = 5000L; 18 | this.polling = 1000L; 19 | this.ignoring = Collections.singletonList(Throwable.class); 20 | } 21 | 22 | @Override 23 | public boolean shouldRetry(final long start, final Throwable e) { 24 | return shouldRetry(start, timeout, polling, ignoring, e); 25 | } 26 | 27 | @Override 28 | public void timeoutInSeconds(final int seconds) { 29 | this.timeout = TimeUnit.SECONDS.toMillis(seconds); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/internal/ListenerNotifier.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.internal; 2 | 3 | import io.qameta.atlas.core.api.Listener; 4 | import io.qameta.atlas.core.util.MethodInfo; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | /** 13 | * Listener notifier. 14 | */ 15 | public class ListenerNotifier implements Listener { 16 | 17 | private static final Logger LOGGER = LoggerFactory.getLogger(ListenerNotifier.class); 18 | 19 | private final List listeners = new ArrayList<>(); 20 | 21 | public void addListeners(final Listener... listeners) { 22 | this.listeners.addAll(Arrays.asList(listeners)); 23 | } 24 | 25 | @Override 26 | public void beforeMethodCall(final MethodInfo methodInfo, final Configuration configuration) { 27 | for (Listener listener : listeners) { 28 | try { 29 | listener.beforeMethodCall(methodInfo, configuration); 30 | } catch (Exception e) { 31 | LOGGER.error("Error during listener {} beforeMethodCall", listener, e); 32 | } 33 | } 34 | } 35 | 36 | @Override 37 | public void afterMethodCall(final MethodInfo methodInfo, final Configuration configuration) { 38 | for (Listener listener : listeners) { 39 | try { 40 | listener.afterMethodCall(methodInfo, configuration); 41 | } catch (Exception e) { 42 | LOGGER.error("Error during listener {} afterMethodCall", listener, e); 43 | } 44 | } 45 | } 46 | 47 | @Override 48 | public void onMethodReturn(final MethodInfo methodInfo, final Configuration configuration, final Object returned) { 49 | for (Listener listener : listeners) { 50 | try { 51 | listener.onMethodReturn(methodInfo, configuration, returned); 52 | } catch (Exception e) { 53 | LOGGER.error("Error during listener {} onMethodReturn", listener, e); 54 | } 55 | } 56 | } 57 | 58 | @Override 59 | public void onMethodFailure(final MethodInfo methodInfo, final Configuration configuration, 60 | final Throwable throwable) { 61 | for (Listener listener : listeners) { 62 | try { 63 | listener.onMethodFailure(methodInfo, configuration, throwable); 64 | } catch (Exception e) { 65 | LOGGER.error("Error during listener {} onMethodFailure", listener, e); 66 | } 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/internal/Retryer.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.internal; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Retryer. 7 | */ 8 | public interface Retryer { 9 | 10 | boolean shouldRetry(long start, Throwable e) throws Throwable; 11 | 12 | default boolean shouldRetry(final Long start, final Long timeout, final Long polling, 13 | final List> ignoring, final Throwable e) { 14 | final long current = System.currentTimeMillis(); 15 | if (!(ignoring.stream().anyMatch(clazz -> clazz.isInstance(e)) && start + timeout < current)) { 16 | try { 17 | Thread.sleep(polling); 18 | return true; 19 | } catch (InterruptedException i) { 20 | Thread.currentThread().interrupt(); 21 | } 22 | } 23 | return false; 24 | } 25 | 26 | void timeoutInSeconds(int seconds); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/internal/TargetMethodInvoker.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.internal; 2 | 3 | import io.qameta.atlas.core.api.MethodInvoker; 4 | import io.qameta.atlas.core.context.TargetContext; 5 | import io.qameta.atlas.core.util.MethodInfo; 6 | import io.qameta.atlas.core.util.ReflectionUtils; 7 | 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.lang.reflect.Method; 10 | import java.util.Arrays; 11 | 12 | /** 13 | * Target method invoker. 14 | */ 15 | public class TargetMethodInvoker implements MethodInvoker { 16 | 17 | @Override 18 | public Object invoke(final Object proxy, final MethodInfo methodInfo, final Configuration config) throws Throwable { 19 | final Object target = config.requireContext(TargetContext.class).getValue().instance(); 20 | final Method targetMethod = ReflectionUtils.getMatchingMethod( 21 | target.getClass(), methodInfo.getMethod().getName(), getParametersTypes(methodInfo.getArgs())); 22 | try { 23 | targetMethod.setAccessible(true); 24 | return targetMethod.invoke(target, methodInfo.getArgs()); 25 | } catch (InvocationTargetException e) { 26 | throw e.getTargetException(); 27 | } 28 | } 29 | 30 | private Class[] getParametersTypes(final Object... args) { 31 | if (args == null) { 32 | return new Class[]{}; 33 | } 34 | return Arrays.stream(args) 35 | .map(Object::getClass).toArray(Class[]::new); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/target/HardcodedTarget.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.target; 2 | 3 | import io.qameta.atlas.core.api.Target; 4 | 5 | /** 6 | * @author Artem Eroshenko. 7 | */ 8 | @SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName") 9 | public class HardcodedTarget implements Target { 10 | 11 | private final String name; 12 | 13 | private final Object instance; 14 | 15 | public HardcodedTarget(final String name, final Object instance) { 16 | this.instance = instance; 17 | this.name = name; 18 | } 19 | 20 | @Override 21 | public String name() { 22 | return name; 23 | } 24 | 25 | @Override 26 | public Object instance() { 27 | return instance; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/target/LazyTarget.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.target; 2 | 3 | import io.qameta.atlas.core.api.Target; 4 | import java.util.function.Supplier; 5 | 6 | /** 7 | * @author Artem Eroshenko. 8 | */ 9 | @SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName") 10 | public class LazyTarget implements Target { 11 | 12 | private final String name; 13 | 14 | private final Supplier supplier; 15 | 16 | public LazyTarget(final String name, final Supplier supplier) { 17 | this.supplier = supplier; 18 | this.name = name; 19 | } 20 | 21 | @Override 22 | public String name() { 23 | return name; 24 | } 25 | 26 | @Override 27 | public Object instance() { 28 | return supplier.get(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/util/MethodInfo.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.util; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Method; 5 | import java.lang.reflect.Parameter; 6 | import java.util.Optional; 7 | 8 | /** 9 | * Method info auxiliary class. 10 | */ 11 | public class MethodInfo { 12 | 13 | private final Method method; 14 | private final Object[] args; 15 | 16 | public MethodInfo(final Method method, final Object... args) { 17 | this.method = method; 18 | this.args = cloneArgs(args); 19 | } 20 | 21 | public Method getMethod() { 22 | return method; 23 | } 24 | 25 | public Object[] getArgs() { 26 | return cloneArgs(args); 27 | } 28 | 29 | public Optional getParameter(final Class type) { 30 | final Parameter[] parameters = method.getParameters(); 31 | T result = null; 32 | for (int i = 0; i < parameters.length; i++) { 33 | final Parameter parameter = parameters[i]; 34 | if (parameter.getType().equals(type)) { 35 | result = type.cast(args[i]); 36 | } 37 | } 38 | return Optional.ofNullable(result); 39 | } 40 | 41 | public Optional getParameter(final Class type, final Class annotation) { 42 | final Parameter[] parameters = method.getParameters(); 43 | T result = null; 44 | for (int i = 0; i < parameters.length; i++) { 45 | final Parameter parameter = parameters[i]; 46 | if (parameter.getType().equals(type) && parameter.isAnnotationPresent(annotation)) { 47 | result = type.cast(args[i]); 48 | } 49 | } 50 | return Optional.ofNullable(result); 51 | } 52 | 53 | private Object[] cloneArgs(final Object... argsToClone) { 54 | return argsToClone == null ? null : argsToClone.clone(); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /atlas-core/src/main/java/io/qameta/atlas/core/util/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.util; 2 | 3 | import io.qameta.atlas.core.AtlasException; 4 | import org.apache.commons.lang3.ClassUtils; 5 | import org.apache.commons.lang3.reflect.ConstructorUtils; 6 | 7 | import java.lang.annotation.Annotation; 8 | import java.lang.reflect.AnnotatedElement; 9 | import java.lang.reflect.Method; 10 | import java.util.*; 11 | import java.util.function.Predicate; 12 | import java.util.stream.Collectors; 13 | import java.util.stream.Stream; 14 | 15 | /** 16 | * Reflection utils. 17 | */ 18 | public final class ReflectionUtils { 19 | 20 | private ReflectionUtils() { 21 | } 22 | 23 | private static List> getAllInterfaces(final Class... classes) { 24 | final List> result = new ArrayList<>(); 25 | Arrays.stream(classes).forEach(clazz -> { 26 | result.addAll(ClassUtils.getAllInterfaces(clazz)); 27 | result.add(clazz); 28 | }); 29 | return result; 30 | } 31 | 32 | public static List getMethods(final Class... clazz) { 33 | 34 | return getAllInterfaces(clazz).stream() 35 | .flatMap(m -> Arrays.stream(m.getDeclaredMethods())) 36 | .collect(Collectors.toList()); 37 | } 38 | 39 | public static T newInstance(final Class clazz) { 40 | try { 41 | return ConstructorUtils.invokeConstructor(clazz); 42 | } catch (Exception e) { 43 | throw new AtlasException("Can't instantiate class " + clazz, e); 44 | } 45 | } 46 | 47 | /** 48 | * Get matching methods for the given class, superclass and all of interfaces implemented by the given 49 | * class and its superclasses. Note: first, find method in current class and etc. 50 | * 51 | * @param cls - current Class {@link Class} 52 | * @param methodName - method name {@link String} 53 | * @param parameterTypes - parameter types {@link Class} 54 | * @return required method {@link Method} 55 | */ 56 | public static Method getMatchingMethod(final Class cls, final String methodName, 57 | final Class... parameterTypes) { 58 | if (!Objects.nonNull(cls) && !Objects.requireNonNull(methodName).isEmpty()) { 59 | throw new AtlasException("Null class not allowed."); 60 | } 61 | final Predicate filter1 = method -> methodName.equals(method.getName()) 62 | && Objects.deepEquals(parameterTypes, method.getParameterTypes()); 63 | final Predicate filter2 = method -> methodName.equals(method.getName()) 64 | && ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true); 65 | 66 | return Stream.of( 67 | Collections.singletonList(cls), ClassUtils.getAllSuperclasses(cls), ClassUtils.getAllInterfaces(cls)) 68 | .flatMap(Collection::stream) 69 | .map(Class::getDeclaredMethods) 70 | .flatMap(Arrays::stream) 71 | .filter(filter1.or(filter2)) 72 | .findFirst() 73 | .orElseThrow(() -> new AtlasException("Can't find valid method: " + cls.getName() + "." + methodName)); 74 | } 75 | 76 | 77 | /** 78 | * Check is element have needful annotation. 79 | * 80 | * @param element - all class implemented {@link AnnotatedElement} interface. 81 | * @param type {@link Class} - type of needful annotation 82 | * @return - true or false. 83 | */ 84 | public static boolean isAnnotated(final AnnotatedElement element, final Class type) { 85 | return element.isAnnotationPresent(type); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /atlas-core/src/test/java/io/qameta/atlas/core/DefaultMethodTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core; 2 | 3 | import io.qameta.atlas.core.internal.DefaultMethodExtension; 4 | import io.qameta.atlas.core.testdata.CustomException; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | public class DefaultMethodTest { 9 | 10 | private Atlas atlas; 11 | 12 | @Before 13 | public void initAtlas() { 14 | atlas = new Atlas() 15 | .extension(new DefaultMethodExtension()); 16 | } 17 | 18 | @Test 19 | public void shouldExecuteDefaultMethod() { 20 | InterfaceWithDefaultMethod instance = atlas 21 | .create(new Object(), InterfaceWithDefaultMethod.class); 22 | instance.doSomething(); 23 | } 24 | 25 | @Test(expected = CustomException.class) 26 | public void shouldPropagateExceptionInDefaultMethod() { 27 | InterfaceWithDefaultMethodThrowable instance = atlas 28 | .create(new Object(), InterfaceWithDefaultMethodThrowable.class); 29 | instance.doSomething(); 30 | } 31 | 32 | public interface InterfaceWithDefaultMethod { 33 | 34 | default void doSomething() { 35 | } 36 | 37 | } 38 | 39 | public interface InterfaceWithDefaultMethodThrowable { 40 | 41 | default void doSomething() { 42 | throw new CustomException(); 43 | } 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /atlas-core/src/test/java/io/qameta/atlas/core/ListenerTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core; 2 | 3 | import io.qameta.atlas.core.api.Listener; 4 | import org.junit.Test; 5 | 6 | import static org.mockito.ArgumentMatchers.any; 7 | import static org.mockito.Mockito.mock; 8 | import static org.mockito.Mockito.times; 9 | import static org.mockito.Mockito.verify; 10 | 11 | public class ListenerTest { 12 | 13 | @Test 14 | public void shouldFireListenerMethods() { 15 | SimpleSayHello hello = mock(SimpleSayHello.class); 16 | Listener listener = mock(Listener.class); 17 | SimpleSayHello proxyHello = new Atlas() 18 | .listener(listener) 19 | .create(hello, SimpleSayHello.class); 20 | 21 | proxyHello.hello(); 22 | 23 | verify(listener, times(1)).beforeMethodCall(any(), any()); 24 | verify(listener, times(1)).afterMethodCall(any(), any()); 25 | verify(listener, times(1)).onMethodReturn(any(), any(), any()); 26 | verify(listener, times(0)).onMethodFailure(any(), any(), any()); 27 | 28 | } 29 | 30 | public interface SimpleSayHello { 31 | 32 | default void hello() { 33 | 34 | } 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /atlas-core/src/test/java/io/qameta/atlas/core/TargetMethodTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core; 2 | 3 | import io.qameta.atlas.core.testdata.CustomException; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import static org.mockito.Mockito.doThrow; 8 | import static org.mockito.Mockito.mock; 9 | import static org.mockito.Mockito.times; 10 | import static org.mockito.Mockito.verify; 11 | 12 | public class TargetMethodTest { 13 | 14 | private Origin origin; 15 | private SayHello sayHello; 16 | 17 | @Before 18 | public void setUp() { 19 | sayHello = mock(SayHello.class); 20 | origin = new Atlas() 21 | .create(sayHello, Origin.class); 22 | } 23 | 24 | @Test 25 | public void shouldExecuteTargetMethod() { 26 | origin.hello(); 27 | verify(sayHello, times(1)).hello(); 28 | } 29 | 30 | @Test(expected = CustomException.class) 31 | public void shouldPropagateExceptionInTargetMethod() { 32 | doThrow(CustomException.class).when(sayHello).hello(); 33 | origin.hello(); 34 | } 35 | 36 | 37 | interface Origin extends SayHello { 38 | 39 | } 40 | 41 | interface SayHello { 42 | void hello(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /atlas-core/src/test/java/io/qameta/atlas/core/testdata/CustomException.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.core.testdata; 2 | 3 | public class CustomException extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /atlas-webdriver/build.gradle.kts: -------------------------------------------------------------------------------- 1 | description = "Atlas Webdriver" 2 | 3 | dependencies { 4 | api(project(":atlas-core")) 5 | 6 | implementation("org.apache.httpcomponents:httpclient:4.5.8") 7 | implementation("org.seleniumhq.selenium:selenium-java") 8 | implementation("org.hamcrest:hamcrest-all") 9 | 10 | testImplementation("ru.yandex.qatools.matchers:webdriver-matchers") 11 | testImplementation("org.apache.commons:commons-lang3") 12 | testImplementation("org.mockito:mockito-core") 13 | testImplementation("org.assertj:assertj-core") 14 | testImplementation("junit:junit") 15 | } 16 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/AtlasWebElement.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.api.Timeout; 4 | import io.qameta.atlas.webdriver.extension.ShouldMethodExtension; 5 | import io.qameta.atlas.webdriver.extension.WaitUntilMethodExtension; 6 | import org.hamcrest.Matcher; 7 | import org.openqa.selenium.By; 8 | import org.openqa.selenium.Dimension; 9 | import org.openqa.selenium.Point; 10 | import org.openqa.selenium.Rectangle; 11 | import org.openqa.selenium.WebElement; 12 | import org.openqa.selenium.WrapsElement; 13 | import org.openqa.selenium.interactions.Coordinates; 14 | import org.openqa.selenium.interactions.Locatable; 15 | 16 | import java.util.List; 17 | 18 | /** 19 | * Atlas Web Element. 20 | * @param the type of the value being boxed 21 | */ 22 | public interface AtlasWebElement extends WrapsElement, WebElement, Locatable { 23 | 24 | /** 25 | * The same as {@link WebElement#click()}. 26 | */ 27 | @Override 28 | void click(); 29 | 30 | /** 31 | * The same as {@link WebElement#submit()}. 32 | */ 33 | @Override 34 | void submit(); 35 | 36 | /** 37 | * The same as {@link WebElement#sendKeys(CharSequence...)}. 38 | */ 39 | @Override 40 | void sendKeys(CharSequence... keysToSend); 41 | 42 | /** 43 | * The same as {@link WebElement#clear()}. 44 | */ 45 | @Override 46 | void clear(); 47 | 48 | /** 49 | * The same as {@link WebElement#getTagName()}. 50 | */ 51 | @Override 52 | String getTagName(); 53 | 54 | /** 55 | * The same as {@link WebElement#getAttribute(String)}. 56 | */ 57 | @Override 58 | String getAttribute(String name); 59 | 60 | /** 61 | * The same as {@link WebElement#isSelected()}. 62 | */ 63 | @Override 64 | boolean isSelected(); 65 | 66 | /** 67 | * The same as {@link WebElement#isEnabled()}. 68 | */ 69 | @Override 70 | boolean isEnabled(); 71 | 72 | /** 73 | * The same as {@link WebElement#getText()}. 74 | */ 75 | @Override 76 | String getText(); 77 | 78 | /** 79 | * The same as {@link WebElement#findElements(By)}. 80 | */ 81 | @Override 82 | List findElements(By by); 83 | 84 | /** 85 | * The same as {@link WebElement#findElement(By)}. 86 | */ 87 | @Override 88 | WebElement findElement(By by); 89 | 90 | /** 91 | * The same as {@link WebElement#isDisplayed()}. 92 | */ 93 | @Override 94 | boolean isDisplayed(); 95 | 96 | /** 97 | * The same as {@link WebElement#getLocation()}. 98 | */ 99 | @Override 100 | Point getLocation(); 101 | 102 | /** 103 | * The same as {@link WebElement#getSize()}. 104 | */ 105 | @Override 106 | Dimension getSize(); 107 | 108 | /** 109 | * The same as {@link WebElement#getRect()}. 110 | */ 111 | @Override 112 | Rectangle getRect(); 113 | 114 | /** 115 | * The same as {@link WebElement#getCssValue(String)}. 116 | */ 117 | @Override 118 | String getCssValue(String propertyName); 119 | 120 | /** 121 | * The same as {@link Locatable#getCoordinates()}. 122 | */ 123 | @Override 124 | Coordinates getCoordinates(); 125 | 126 | /** 127 | * This method handled by the {@link ShouldMethodExtension}. 128 | */ 129 | T should(Matcher matcher); 130 | 131 | /** 132 | * This method handled by the {@link ShouldMethodExtension}. 133 | */ 134 | T should(Matcher matcher, @Timeout Integer timeoutInSeconds); 135 | 136 | /** 137 | * This method handled by the {@link ShouldMethodExtension}. 138 | */ 139 | T should(String message, Matcher matcher); 140 | 141 | /** 142 | * This method handled by the {@link ShouldMethodExtension}. 143 | */ 144 | T should(String message, Matcher matcher, @Timeout Integer timeoutInSeconds); 145 | 146 | /** 147 | * This method handled by the {@link WaitUntilMethodExtension}. 148 | */ 149 | T waitUntil(Matcher matcher); 150 | 151 | /** 152 | * This method handled by the {@link WaitUntilMethodExtension}. 153 | */ 154 | T waitUntil(Matcher matcher, @Timeout Integer timeoutInSeconds); 155 | 156 | /** 157 | * This method handled by the {@link WaitUntilMethodExtension}. 158 | */ 159 | T waitUntil(String message, Matcher matcher); 160 | 161 | /** 162 | * This method handled by the {@link WaitUntilMethodExtension}. 163 | */ 164 | T waitUntil(String message, Matcher matcher, @Timeout Integer timeoutInSeconds); 165 | 166 | /** 167 | * The same as {@link WrapsElement#getWrappedElement()}. 168 | */ 169 | @Override 170 | WebElement getWrappedElement(); 171 | 172 | /** 173 | * Executes JavaScript in the context of the currently AtlasWebElement. 174 | */ 175 | Object executeScript(String script); 176 | 177 | } 178 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/ElementsCollection.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.api.Timeout; 4 | import io.qameta.atlas.webdriver.extension.ExtractMethodExtension; 5 | import io.qameta.atlas.webdriver.extension.ShouldMethodExtension; 6 | import io.qameta.atlas.webdriver.extension.WaitUntilMethodExtension; 7 | import org.hamcrest.Matcher; 8 | 9 | import java.util.List; 10 | import java.util.function.Function; 11 | import java.util.function.Predicate; 12 | 13 | /** 14 | * Atlas Web Element Collection. 15 | * @param the type of elements in this collection 16 | */ 17 | public interface ElementsCollection extends List { 18 | 19 | /** 20 | * This method handled by the {@link io.qameta.atlas.webdriver.extension.FilterCollectionExtension}. 21 | */ 22 | ElementsCollection filter(Predicate predicate); 23 | 24 | /** 25 | * This method handled by the {@link ExtractMethodExtension}. 26 | */ 27 | ElementsCollection extract(Function function); 28 | 29 | /** 30 | * This method handled by the {@link ShouldMethodExtension}. 31 | */ 32 | ElementsCollection should(Matcher matcher); 33 | 34 | /** 35 | * This method handled by the {@link ShouldMethodExtension}. 36 | */ 37 | ElementsCollection should(String message, Matcher matcher); 38 | 39 | /** 40 | * This method handled by the {@link WaitUntilMethodExtension}. 41 | */ 42 | ElementsCollection waitUntil(Matcher matcher); 43 | 44 | /** 45 | * This method handled by the {@link WaitUntilMethodExtension}. 46 | */ 47 | ElementsCollection waitUntil(Matcher matcher, @Timeout Integer timeoutInSeconds); 48 | 49 | /** 50 | * This method handled by the {@link WaitUntilMethodExtension}. 51 | */ 52 | ElementsCollection waitUntil(String message, Matcher matcher); 53 | 54 | /** 55 | * This method handled by the {@link WaitUntilMethodExtension}. 56 | */ 57 | ElementsCollection waitUntil(String message, Matcher matcher, @Timeout Integer timeoutInSeconds); 58 | } 59 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/WebDriverConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.context.RetryerContext; 4 | import io.qameta.atlas.core.internal.EmptyRetryer; 5 | import io.qameta.atlas.webdriver.context.WebDriverContext; 6 | import io.qameta.atlas.core.internal.Configuration; 7 | import io.qameta.atlas.core.internal.DefaultMethodExtension; 8 | import io.qameta.atlas.webdriver.extension.ExtractMethodExtension; 9 | import io.qameta.atlas.webdriver.extension.DriverProviderExtension; 10 | import io.qameta.atlas.webdriver.extension.ExecuteJScriptMethodExtension; 11 | import io.qameta.atlas.webdriver.extension.FilterCollectionExtension; 12 | import io.qameta.atlas.webdriver.extension.FindByCollectionExtension; 13 | import io.qameta.atlas.webdriver.extension.FindByExtension; 14 | import io.qameta.atlas.webdriver.extension.PageExtension; 15 | import io.qameta.atlas.webdriver.extension.ShouldMethodExtension; 16 | import io.qameta.atlas.webdriver.extension.ToStringMethodExtension; 17 | import io.qameta.atlas.webdriver.extension.WaitUntilMethodExtension; 18 | import io.qameta.atlas.webdriver.extension.WrappedElementMethodExtension; 19 | import org.openqa.selenium.WebDriver; 20 | 21 | /** 22 | * WebDriver configuration. 23 | */ 24 | @SuppressWarnings("checkstyle:ClassDataAbstractionCoupling") 25 | public class WebDriverConfiguration extends Configuration { 26 | 27 | public WebDriverConfiguration(final WebDriver webDriver) { 28 | registerContext(new WebDriverContext(webDriver)); 29 | registerContext(new RetryerContext(new EmptyRetryer())); 30 | registerExtension(new DriverProviderExtension()); 31 | registerExtension(new DefaultMethodExtension()); 32 | registerExtension(new FindByExtension()); 33 | registerExtension(new FindByCollectionExtension()); 34 | registerExtension(new ShouldMethodExtension()); 35 | registerExtension(new WaitUntilMethodExtension()); 36 | registerExtension(new WrappedElementMethodExtension()); 37 | registerExtension(new ExecuteJScriptMethodExtension()); 38 | registerExtension(new PageExtension()); 39 | registerExtension(new FilterCollectionExtension()); 40 | registerExtension(new ToStringMethodExtension()); 41 | registerExtension(new ExtractMethodExtension()); 42 | } 43 | 44 | public WebDriverConfiguration(final WebDriver webDriver, final String baseUrl) { 45 | this(webDriver); 46 | System.getProperties().setProperty("ATLAS_WEBSITE_URL", baseUrl); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/WebPage.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.webdriver.extension.DriverProvider; 4 | import org.openqa.selenium.SearchContext; 5 | import org.openqa.selenium.WebDriver; 6 | import org.openqa.selenium.WrapsDriver; 7 | 8 | /** 9 | * Web Page. 10 | */ 11 | public interface WebPage extends WrapsDriver, SearchContext { 12 | 13 | @DriverProvider 14 | @Override 15 | WebDriver getWrappedDriver(); 16 | 17 | default void open(final String url) { 18 | getWrappedDriver().get(url); 19 | } 20 | 21 | default void open() { 22 | getWrappedDriver().get(System.getProperties().getProperty("ATLAS_WEBSITE_URL")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/WebSite.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.webdriver.extension.DriverProvider; 4 | import org.openqa.selenium.SearchContext; 5 | import org.openqa.selenium.WebDriver; 6 | import org.openqa.selenium.WrapsDriver; 7 | 8 | /** 9 | * Web Site. 10 | */ 11 | public interface WebSite extends WrapsDriver, SearchContext { 12 | 13 | @DriverProvider 14 | @Override 15 | WebDriver getWrappedDriver(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/context/WebDriverContext.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.context; 2 | 3 | import io.qameta.atlas.core.api.Context; 4 | import io.qameta.atlas.core.internal.Configuration; 5 | import org.openqa.selenium.WebDriver; 6 | 7 | /** 8 | * WebDriver io.qameta.webdriver.context. 9 | * Used to save WebDriver instance to {@link Configuration}. 10 | */ 11 | public class WebDriverContext implements Context { 12 | 13 | private final WebDriver webDriver; 14 | 15 | public WebDriverContext(final WebDriver webDriver) { 16 | this.webDriver = webDriver; 17 | } 18 | 19 | @Override 20 | public WebDriver getValue() { 21 | return webDriver; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/exception/WaitUntilException.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.exception; 2 | 3 | import io.qameta.atlas.webdriver.extension.WaitUntilMethodExtension; 4 | import org.openqa.selenium.WebDriverException; 5 | 6 | /** 7 | * Exception for custom behavior of the {@link WaitUntilMethodExtension}. 8 | */ 9 | public class WaitUntilException extends WebDriverException { 10 | 11 | public WaitUntilException(final String message) { 12 | super(message); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/DriverProvider.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 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 | * WebDriver provider marker. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.METHOD) 13 | public @interface DriverProvider { 14 | } 15 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/DriverProviderExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 2 | 3 | import io.qameta.atlas.core.api.MethodExtension; 4 | import io.qameta.atlas.webdriver.context.WebDriverContext; 5 | import io.qameta.atlas.core.internal.Configuration; 6 | import io.qameta.atlas.core.util.MethodInfo; 7 | import org.openqa.selenium.WebDriver; 8 | 9 | import java.lang.reflect.Method; 10 | 11 | /** 12 | * Driver provider extension. 13 | */ 14 | public class DriverProviderExtension implements MethodExtension { 15 | 16 | @Override 17 | public boolean test(final Method method) { 18 | return method.isAnnotationPresent(DriverProvider.class); 19 | } 20 | 21 | @Override 22 | public WebDriver invoke(final Object proxy, final MethodInfo methodInfo, final Configuration config) 23 | throws Throwable { 24 | return config.getContext(WebDriverContext.class).get().getValue(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/ExecuteJScriptMethodExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 2 | 3 | import io.qameta.atlas.core.AtlasException; 4 | import io.qameta.atlas.core.api.MethodExtension; 5 | import io.qameta.atlas.core.internal.Configuration; 6 | import io.qameta.atlas.core.util.MethodInfo; 7 | import io.qameta.atlas.webdriver.context.WebDriverContext; 8 | import org.openqa.selenium.JavascriptExecutor; 9 | import org.openqa.selenium.WebDriver; 10 | 11 | import java.lang.reflect.Method; 12 | 13 | /** 14 | * JavaScript (executeScript) method extension. 15 | */ 16 | public class ExecuteJScriptMethodExtension implements MethodExtension { 17 | 18 | private static final String EXECUTE_SCRIPT = "executeScript"; 19 | 20 | @Override 21 | public boolean test(final Method method) { 22 | return EXECUTE_SCRIPT.equals(method.getName()); 23 | } 24 | 25 | @Override 26 | public Object invoke(final Object proxy, 27 | final MethodInfo methodInfo, 28 | final Configuration configuration) { 29 | final WebDriver driver = configuration.getContext(WebDriverContext.class) 30 | .orElseThrow(() -> new AtlasException("WebDriver is missing")).getValue(); 31 | final String script = (String) methodInfo.getArgs()[0]; 32 | final JavascriptExecutor js = (JavascriptExecutor) driver; 33 | 34 | js.executeScript(script, proxy); 35 | return proxy; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/ExtractMethodExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.core.api.MethodExtension; 5 | import io.qameta.atlas.core.internal.Configuration; 6 | import io.qameta.atlas.core.util.MethodInfo; 7 | import io.qameta.atlas.webdriver.ElementsCollection; 8 | 9 | import java.lang.reflect.Method; 10 | import java.util.List; 11 | import java.util.function.Function; 12 | 13 | import static java.util.stream.Collectors.toList; 14 | 15 | /** 16 | * @author artem.krosheninnikov 17 | */ 18 | public class ExtractMethodExtension implements MethodExtension { 19 | 20 | private static final String EXTRACT = "extract"; 21 | 22 | @Override 23 | public boolean test(final Method method) { 24 | return EXTRACT.equals(method.getName()) && List.class.isAssignableFrom(method.getReturnType()); 25 | } 26 | 27 | @Override 28 | @SuppressWarnings("unchecked") 29 | public Object invoke(final Object proxy, 30 | final MethodInfo methodInfo, 31 | final Configuration config) { 32 | final Function converter = (Function) methodInfo.getArgs()[0]; 33 | return new Atlas(config.child()) 34 | .create(((List) proxy).stream().map(converter).collect(toList()), ElementsCollection.class); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/FilterCollectionExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.webdriver.ElementsCollection; 5 | import io.qameta.atlas.core.api.MethodExtension; 6 | import io.qameta.atlas.core.internal.Configuration; 7 | import io.qameta.atlas.core.util.MethodInfo; 8 | 9 | import java.lang.reflect.Method; 10 | import java.util.List; 11 | import java.util.function.Predicate; 12 | 13 | import static java.util.stream.Collectors.toList; 14 | 15 | /** 16 | * Filter method extension for {@link io.qameta.atlas.webdriver.ElementsCollection}. 17 | */ 18 | public class FilterCollectionExtension implements MethodExtension { 19 | 20 | private static final String FILTER = "filter"; 21 | 22 | @Override 23 | public boolean test(final Method method) { 24 | return FILTER.equals(method.getName()) && List.class.isAssignableFrom(method.getDeclaringClass()); 25 | } 26 | 27 | @Override 28 | @SuppressWarnings("unchecked") 29 | public ElementsCollection invoke(final Object proxy, 30 | final MethodInfo methodInfo, 31 | final Configuration configuration) { 32 | final Predicate condition = (Predicate) methodInfo.getArgs()[0]; 33 | return new Atlas(configuration) 34 | .create(((List) proxy).stream().filter(condition).collect(toList()), ElementsCollection.class); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/FindBy.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 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 | * FindBy extension marker. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.METHOD) 13 | public @interface FindBy { 14 | 15 | String value(); 16 | 17 | Selector selector() default Selector.XPATH; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/FindByCollectionExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.core.api.MethodExtension; 5 | import io.qameta.atlas.core.api.Retry; 6 | import io.qameta.atlas.core.api.Target; 7 | import io.qameta.atlas.core.context.RetryerContext; 8 | import io.qameta.atlas.core.internal.Configuration; 9 | import io.qameta.atlas.core.internal.DefaultRetryer; 10 | import io.qameta.atlas.core.internal.Retryer; 11 | import io.qameta.atlas.core.target.HardcodedTarget; 12 | import io.qameta.atlas.core.target.LazyTarget; 13 | import io.qameta.atlas.core.util.MethodInfo; 14 | import org.openqa.selenium.By; 15 | import org.openqa.selenium.SearchContext; 16 | import org.openqa.selenium.WebElement; 17 | 18 | import java.lang.reflect.Method; 19 | import java.lang.reflect.ParameterizedType; 20 | import java.lang.reflect.Type; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.Optional; 24 | import java.util.stream.IntStream; 25 | 26 | import static io.qameta.atlas.webdriver.util.MethodInfoUtils.getParamValues; 27 | import static io.qameta.atlas.webdriver.util.MethodInfoUtils.processParamTemplate; 28 | import static java.util.stream.Collectors.toList; 29 | 30 | /** 31 | * Extension for methods with {@link io.qameta.atlas.webdriver.extension.FindBy} annotation 32 | * and {@link io.qameta.atlas.webdriver.ElementsCollection} return type. 33 | */ 34 | public class FindByCollectionExtension implements MethodExtension { 35 | 36 | @Override 37 | public boolean test(final Method method) { 38 | return method.isAnnotationPresent(FindBy.class) 39 | && List.class.isAssignableFrom(method.getReturnType()); 40 | } 41 | 42 | @Override 43 | public Object invoke(final Object proxy, 44 | final MethodInfo methodInfo, 45 | final Configuration configuration) { 46 | final Method method = methodInfo.getMethod(); 47 | 48 | assert proxy instanceof SearchContext; 49 | assert method.isAnnotationPresent(FindBy.class); 50 | 51 | final Map parameters = getParamValues(method, methodInfo.getArgs()); 52 | final FindBy findBy = method.getAnnotation(FindBy.class); 53 | final By by = findBy.selector().buildBy(processParamTemplate(findBy.value(), parameters)); 54 | 55 | final String name = Optional.ofNullable(method.getAnnotation(Name.class)) 56 | .map(Name::value) 57 | .map(template -> processParamTemplate(template, parameters)) 58 | .orElse(method.getName()); 59 | final SearchContext context = (SearchContext) proxy; 60 | 61 | final LazyTarget elementsTarget = new LazyTarget(name, () -> { 62 | final List originalElements = context.findElements(by); 63 | final Type methodReturnType = ((ParameterizedType) method.getGenericReturnType()) 64 | .getActualTypeArguments()[0]; 65 | 66 | return IntStream.range(0, originalElements.size()) 67 | .mapToObj(i -> { 68 | final WebElement originalElement = originalElements.get(i); 69 | final Configuration childConfiguration = configuration.child(); 70 | final Target target = new HardcodedTarget(listElementName(name, i), originalElement); 71 | return new Atlas(childConfiguration) 72 | .create(target, (Class) methodReturnType); 73 | }) 74 | .collect(toList()); 75 | }); 76 | 77 | final Configuration childConfiguration = configuration.child(); 78 | Optional.ofNullable(methodInfo.getMethod().getAnnotation(Retry.class)).ifPresent(retry -> { 79 | final Retryer retryer = new DefaultRetryer(retry); 80 | childConfiguration.registerContext(new RetryerContext(retryer)); 81 | }); 82 | 83 | return new Atlas(childConfiguration) 84 | .create(elementsTarget, method.getReturnType()); 85 | } 86 | 87 | private String listElementName(final String name, final int position) { 88 | return String.format("%s [%s]", name, position); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/FindByExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.core.api.MethodExtension; 5 | import io.qameta.atlas.core.api.Retry; 6 | import io.qameta.atlas.core.api.Target; 7 | import io.qameta.atlas.core.context.RetryerContext; 8 | import io.qameta.atlas.core.internal.Configuration; 9 | import io.qameta.atlas.core.internal.DefaultRetryer; 10 | import io.qameta.atlas.core.internal.Retryer; 11 | import io.qameta.atlas.core.target.LazyTarget; 12 | import io.qameta.atlas.core.util.MethodInfo; 13 | import org.openqa.selenium.By; 14 | import org.openqa.selenium.SearchContext; 15 | import org.openqa.selenium.WebElement; 16 | 17 | import java.lang.reflect.Method; 18 | import java.util.Map; 19 | import java.util.Optional; 20 | 21 | import static io.qameta.atlas.webdriver.util.MethodInfoUtils.getParamValues; 22 | import static io.qameta.atlas.webdriver.util.MethodInfoUtils.processParamTemplate; 23 | 24 | /** 25 | * Extension for methods with {@link io.qameta.atlas.webdriver.extension.FindBy} annotation. 26 | */ 27 | public class FindByExtension implements MethodExtension { 28 | 29 | @Override 30 | public boolean test(final Method method) { 31 | return method.isAnnotationPresent(FindBy.class) 32 | && WebElement.class.isAssignableFrom(method.getReturnType()); 33 | } 34 | 35 | @Override 36 | public Object invoke(final Object proxy, 37 | final MethodInfo methodInfo, 38 | final Configuration configuration) { 39 | final Method method = methodInfo.getMethod(); 40 | 41 | assert proxy instanceof SearchContext; 42 | assert method.isAnnotationPresent(FindBy.class); 43 | 44 | final Map parameters = getParamValues(method, methodInfo.getArgs()); 45 | final FindBy findBy = method.getAnnotation(FindBy.class); 46 | final By by = findBy.selector().buildBy(processParamTemplate(findBy.value(), parameters)); 47 | 48 | final SearchContext searchContext = (SearchContext) proxy; 49 | final String name = Optional.ofNullable(method.getAnnotation(Name.class)) 50 | .map(Name::value) 51 | .map(template -> processParamTemplate(template, parameters)) 52 | .orElse(method.getName()); 53 | 54 | final Configuration childConfiguration = configuration.child(); 55 | Optional.ofNullable(methodInfo.getMethod().getAnnotation(Retry.class)).ifPresent(retry -> { 56 | final Retryer retryer = new DefaultRetryer(retry); 57 | childConfiguration.registerContext(new RetryerContext(retryer)); 58 | }); 59 | 60 | final Target elementTarget = new LazyTarget(name, () -> searchContext.findElement(by)); 61 | return new Atlas(childConfiguration) 62 | .create(elementTarget, method.getReturnType()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/Name.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 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 | * Name of element for FindBy extension. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.METHOD) 13 | public @interface Name { 14 | 15 | String value(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/Page.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 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 | * Page marker. Use on your {@link io.qameta.atlas.webdriver.WebSite} implementation. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.METHOD) 13 | public @interface Page { 14 | 15 | String url() default "/"; 16 | } 17 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/PageExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.core.AtlasException; 5 | import io.qameta.atlas.core.api.MethodExtension; 6 | import io.qameta.atlas.core.internal.Configuration; 7 | import io.qameta.atlas.core.util.MethodInfo; 8 | import org.apache.http.client.utils.URIBuilder; 9 | import org.openqa.selenium.WrapsDriver; 10 | 11 | import java.lang.reflect.Method; 12 | import java.net.URISyntaxException; 13 | import java.util.Map; 14 | import java.util.Map.Entry; 15 | import java.util.Optional; 16 | import java.util.stream.Stream; 17 | 18 | import static io.qameta.atlas.core.util.ReflectionUtils.isAnnotated; 19 | import static io.qameta.atlas.webdriver.util.MethodInfoUtils.*; 20 | import static java.util.stream.Collectors.toMap; 21 | 22 | 23 | /** 24 | * Extension for methods with {@link Page} annotation. 25 | */ 26 | public class PageExtension implements MethodExtension { 27 | 28 | private static final String DEFAULT_PATH = "/"; 29 | 30 | @Override 31 | public boolean test(final Method method) { 32 | return isAnnotated(method, Page.class); 33 | } 34 | 35 | @Override 36 | public Object invoke(final Object proxy, 37 | final MethodInfo methodInfo, 38 | final Configuration configuration) { 39 | assert proxy instanceof WrapsDriver; 40 | 41 | final Method method = methodInfo.getMethod(); 42 | final Object[] args = methodInfo.getArgs(); 43 | 44 | final String baseURL = method.getAnnotation(Page.class).url(); 45 | final WrapsDriver wrapsDriver = (WrapsDriver) proxy; 46 | 47 | 48 | final String baseURI = Optional.ofNullable(System.getProperties().getProperty("ATLAS_WEBSITE_URL")) 49 | .orElseThrow(() -> new AtlasException("URI WebSite did'nt declared.")); 50 | 51 | final Map pathParameters = getPathSegmentValues(method, args); 52 | final String pathSegment = processTemplate(baseURL, pathParameters, "{", "}"); 53 | 54 | final Map queryParameters = Stream 55 | .of(getQueryValues(method, args), getQueryMapValues(method, args)) 56 | .flatMap(map -> map.entrySet().stream()) 57 | .collect(toMap(Entry::getKey, Entry::getValue)); 58 | final String requestURL = buildUrl(baseURI, pathSegment, queryParameters); 59 | 60 | Optional.of(requestURL) 61 | .filter(it -> !it.equals(baseURI + DEFAULT_PATH)) 62 | .ifPresent(url -> wrapsDriver.getWrappedDriver().get(requestURL)); 63 | 64 | return new Atlas(configuration).create(wrapsDriver.getWrappedDriver(), (Class) method.getReturnType()); 65 | } 66 | 67 | /** 68 | * Build full completed URL. 69 | * 70 | * @param baseURI {@link String} - the base URI of WebSite. 71 | * @param pathSegment {@link String} - the path segment. 72 | * @param queryParameters {@link Map} - the query parameters. 73 | * @return {@link String} 74 | */ 75 | private String buildUrl(final String baseURI, final String pathSegment, final Map queryParameters) { 76 | final URIBuilder urlBuilder; 77 | try { 78 | urlBuilder = new URIBuilder(baseURI); 79 | urlBuilder.setPath(pathSegment); 80 | queryParameters.forEach(urlBuilder::addParameter); 81 | } catch (URISyntaxException e) { 82 | throw new AtlasException("Can't parse base URL of your WebSite", e); 83 | } 84 | return urlBuilder.toString(); 85 | } 86 | } 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/Param.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 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 | * Params marker. 10 | */ 11 | @Target(ElementType.PARAMETER) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Param { 14 | 15 | String value(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/Path.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 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 | * Path marker. Use to replace item defined in your uri path. 10 | * E.g. if path is "/book/{hotelId}/{roomNumber}" you can do like this: 11 | * 12 | *
{@code
13 |  * @Page(url = "book/{hotelId}/{roomNumber}")
14 |  * MainPage onMainPage(@Path("hotelId") long id, @Path("roomNumber") String number);
15 |  * }
16 | */ 17 | @Target(ElementType.PARAMETER) 18 | @Retention(RetentionPolicy.RUNTIME) 19 | public @interface Path { 20 | 21 | String value(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/Query.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 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 | * Query marker. Use to mark params on your {@link io.qameta.atlas.webdriver.WebSite} implementation. 10 | */ 11 | @Target(ElementType.PARAMETER) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Query { 14 | 15 | String value(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/QueryMap.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 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 | import java.util.Map; 8 | 9 | /** 10 | * QueryMap marker. Use to mark params as {@link Map} on your {@link io.qameta.atlas.webdriver.WebSite} implementation. 11 | */ 12 | @Target(ElementType.PARAMETER) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface QueryMap { 15 | } 16 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/Selector.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 2 | 3 | import org.openqa.selenium.By; 4 | import org.openqa.selenium.support.ByIdOrName; 5 | 6 | /** 7 | * @author eroshenkoam (Artem Eroshenko). 8 | */ 9 | public enum Selector { 10 | 11 | CLASS_NAME { 12 | @Override 13 | public By buildBy(final String value) { 14 | return By.className(value); 15 | } 16 | }, 17 | CSS { 18 | @Override 19 | public By buildBy(final String value) { 20 | return By.cssSelector(value); 21 | } 22 | }, 23 | ID { 24 | @Override 25 | public By buildBy(final String value) { 26 | return By.id(value); 27 | } 28 | }, 29 | ID_OR_NAME { 30 | @Override 31 | public By buildBy(final String value) { 32 | return new ByIdOrName(value); 33 | } 34 | }, 35 | LINK_TEXT { 36 | @Override 37 | public By buildBy(final String value) { 38 | return By.linkText(value); 39 | } 40 | }, 41 | NAME { 42 | @Override 43 | public By buildBy(final String value) { 44 | return By.name(value); 45 | } 46 | }, 47 | PARTIAL_LINK_TEXT { 48 | @Override 49 | public By buildBy(final String value) { 50 | return By.partialLinkText(value); 51 | } 52 | }, 53 | TAG_NAME { 54 | @Override 55 | public By buildBy(final String value) { 56 | return By.tagName(value); 57 | } 58 | }, 59 | XPATH { 60 | @Override 61 | public By buildBy(final String value) { 62 | return By.xpath(value); 63 | } 64 | }; 65 | 66 | public abstract By buildBy(String value); 67 | 68 | } 69 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/ShouldMethodExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 2 | 3 | import io.qameta.atlas.core.api.MethodExtension; 4 | import io.qameta.atlas.core.internal.Configuration; 5 | import io.qameta.atlas.core.util.MethodInfo; 6 | import org.hamcrest.Matcher; 7 | import org.hamcrest.StringDescription; 8 | 9 | import java.lang.reflect.Method; 10 | 11 | /** 12 | * Should method extension for {@link io.qameta.atlas.webdriver.AtlasWebElement}. 13 | */ 14 | public class ShouldMethodExtension implements MethodExtension { 15 | 16 | private static final String SHOULD = "should"; 17 | 18 | @Override 19 | public boolean test(final Method method) { 20 | return SHOULD.equals(method.getName()); 21 | } 22 | 23 | @Override 24 | public Object invoke(final Object proxy, 25 | final MethodInfo methodInfo, 26 | final Configuration configuration) { 27 | final String message = methodInfo.getParameter(String.class) 28 | .orElse(""); 29 | final Matcher matcher = methodInfo.getParameter(Matcher.class) 30 | .orElseThrow(() -> new IllegalStateException("Unexpected method signature")); 31 | 32 | if (!matcher.matches(proxy)) { 33 | final StringDescription description = new StringDescription(); 34 | description.appendText(message) 35 | .appendText("\nExpected: ") 36 | .appendDescriptionOf(matcher) 37 | .appendText("\n but: "); 38 | matcher.describeMismatch(proxy, description); 39 | throw new AssertionError(description.toString()); 40 | } 41 | return proxy; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/ToStringMethodExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 2 | 3 | import io.qameta.atlas.core.api.MethodExtension; 4 | import io.qameta.atlas.core.context.TargetContext; 5 | import io.qameta.atlas.core.internal.Configuration; 6 | import io.qameta.atlas.core.util.MethodInfo; 7 | 8 | import java.lang.reflect.Method; 9 | 10 | /** 11 | * ToString method extension. 12 | */ 13 | public class ToStringMethodExtension implements MethodExtension { 14 | 15 | private static final String TO_STRING = "toString"; 16 | 17 | @Override 18 | public boolean test(final Method method) { 19 | return TO_STRING.equals(method.getName()); 20 | } 21 | 22 | @Override 23 | public Object invoke(final Object proxy, 24 | final MethodInfo methodInfo, 25 | final Configuration configuration) { 26 | return configuration.requireContext(TargetContext.class).getValue().name(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/WaitUntilMethodExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 2 | 3 | import io.qameta.atlas.core.api.MethodExtension; 4 | import io.qameta.atlas.webdriver.exception.WaitUntilException; 5 | import io.qameta.atlas.core.internal.Configuration; 6 | import io.qameta.atlas.core.util.MethodInfo; 7 | import org.hamcrest.Matcher; 8 | import org.hamcrest.StringDescription; 9 | 10 | import java.lang.reflect.Method; 11 | 12 | /** 13 | * WaitUntil method extension for {@link io.qameta.atlas.webdriver.AtlasWebElement}. 14 | */ 15 | public class WaitUntilMethodExtension implements MethodExtension { 16 | 17 | private static final String WAIT_UNTIL = "waitUntil"; 18 | 19 | @Override 20 | public boolean test(final Method method) { 21 | return WAIT_UNTIL.equals(method.getName()); 22 | } 23 | 24 | @Override 25 | public Object invoke(final Object proxy, 26 | final MethodInfo methodInfo, 27 | final Configuration configuration) { 28 | final String message = methodInfo.getParameter(String.class) 29 | .orElse(""); 30 | final Matcher matcher = methodInfo.getParameter(Matcher.class) 31 | .orElseThrow(() -> new IllegalStateException("Unexpected method signature")); 32 | 33 | if (!matcher.matches(proxy)) { 34 | final StringDescription description = new StringDescription(); 35 | description.appendText(message) 36 | .appendText("\nExpected: ") 37 | .appendDescriptionOf(matcher) 38 | .appendText("\n but: "); 39 | matcher.describeMismatch(proxy, description); 40 | throw new WaitUntilException(description.toString()); 41 | } 42 | return proxy; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/extension/WrappedElementMethodExtension.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.extension; 2 | 3 | import io.qameta.atlas.core.api.MethodExtension; 4 | import io.qameta.atlas.core.context.TargetContext; 5 | import io.qameta.atlas.core.internal.Configuration; 6 | import io.qameta.atlas.core.util.MethodInfo; 7 | 8 | import java.lang.reflect.Method; 9 | 10 | /** 11 | * GetWrappedElement method extension. 12 | */ 13 | public class WrappedElementMethodExtension implements MethodExtension { 14 | 15 | private static final String GET_WRAPPED_ELEMENT = "getWrappedElement"; 16 | 17 | @Override 18 | public boolean test(final Method method) { 19 | return GET_WRAPPED_ELEMENT.equals(method.getName()); 20 | } 21 | 22 | @Override 23 | public Object invoke(final Object proxy, 24 | final MethodInfo methodInfo, 25 | final Configuration configuration) { 26 | return configuration.requireContext(TargetContext.class).getValue().instance(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /atlas-webdriver/src/main/java/io/qameta/atlas/webdriver/util/MethodInfoUtils.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.util; 2 | 3 | import io.qameta.atlas.webdriver.extension.Param; 4 | import io.qameta.atlas.webdriver.extension.Path; 5 | import io.qameta.atlas.webdriver.extension.Query; 6 | import io.qameta.atlas.webdriver.extension.QueryMap; 7 | 8 | import java.lang.reflect.AnnotatedElement; 9 | import java.lang.reflect.Method; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.Objects; 13 | import java.util.function.IntPredicate; 14 | import java.util.stream.Collector; 15 | import java.util.stream.IntStream; 16 | 17 | import static io.qameta.atlas.core.util.ReflectionUtils.isAnnotated; 18 | import static java.util.stream.Collectors.toMap; 19 | 20 | /** 21 | * @author kurau (Yuri Kalinin) 22 | */ 23 | public final class MethodInfoUtils { 24 | 25 | private MethodInfoUtils() { 26 | } 27 | 28 | /** 29 | * Replace string template with value from {@link Param}. 30 | * 31 | * @param template {@link String} - the string template. 32 | * @param parameters {@link Map} - the method's parameters. 33 | * @return - transformed value. 34 | * 35 | * Example: 36 | *
{@code
 37 |      *  @FindBy("//div[{{ value }}]")
 38 |      *  AtlasWebElement childWithName(@Param("value") String value);
 39 |      *  }
40 | * 41 | * where "//div[{{ value }}]" - the template, value - the method's parameter. 42 | * 43 | */ 44 | public static String processParamTemplate(final String template, final Map parameters) { 45 | return processTemplate(template, parameters, "{{ ", " }}"); 46 | } 47 | 48 | 49 | /** 50 | * Replace string template with special value. 51 | * 52 | * @param template {@link String} - the string template of. 53 | * @param parameters {@link Map} - parameters. 54 | * @param prefix {@link CharSequence} - the sequence of characters to be used at the beginning 55 | * @param suffix {@link CharSequence} - the sequence of characters to be used at the end 56 | * @return - transformed value. 57 | */ 58 | public static String processTemplate(final String template, final Map parameters, 59 | final CharSequence prefix, final CharSequence suffix) { 60 | return parameters.entrySet() 61 | .stream() 62 | .reduce(template, (a, b) -> a.replace(prefix + b.getKey() + suffix, b.getValue()), (s, s2) -> s); 63 | } 64 | 65 | 66 | @SuppressWarnings({"PMD.UseVarargs", "unchecked"}) 67 | public static Map getQueryMapValues(final Method method, final Object[] args) { 68 | final IntPredicate queryPredicate = index -> isAnnotated(method.getParameters()[index], QueryMap.class); 69 | 70 | return IntStream.range(0, method.getParameterCount()) 71 | .filter(queryPredicate) 72 | .boxed() 73 | .map(index -> (HashMap) args[index]) 74 | .flatMap(x -> x.entrySet().stream()) 75 | .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); 76 | } 77 | 78 | /** 79 | * Get parameters name with annotation {@link Query} and all argument values. 80 | * 81 | * @param method {@link Method} - the method on action. 82 | * @param args - method arguments. 83 | * @return {@link Map} return ParamName as Map Key, argument value as Map Value. 84 | */ 85 | @SuppressWarnings("PMD.UseVarargs") 86 | public static Map getQueryValues(final Method method, final Object[] args) { 87 | final IntPredicate queryPredicate = index -> isAnnotated(method.getParameters()[index], Query.class); 88 | return getParameters(queryPredicate, method, 89 | toMap(index -> getQueryValue(method.getParameters()[index]), index -> Objects.toString(args[index]))); 90 | } 91 | 92 | /** 93 | * Get parameters name with annotation {@link Path} and all argument values. 94 | * 95 | * @param method {@link Method} - the method on action. 96 | * @param args - method arguments. 97 | * @return {@link Map} return ParamName as Map Key, argument value as Map Value. 98 | */ 99 | @SuppressWarnings("PMD.UseVarargs") 100 | public static Map getPathSegmentValues(final Method method, final Object[] args) { 101 | final IntPredicate pathSegmentPredicate = index -> isAnnotated(method.getParameters()[index], Path.class); 102 | return getParameters(pathSegmentPredicate, method, 103 | toMap(index -> getPathValue(method.getParameters()[index]), index -> Objects.toString(args[index]))); 104 | } 105 | 106 | /** 107 | * Get parameters name with annotation {@link Param} and all argument values. 108 | * 109 | * @param method {@link Method} - the method on action. 110 | * @param args - method arguments. 111 | * @return {@link Map} return ParamName as Map Key, argument value as Map Value. 112 | */ 113 | @SuppressWarnings("PMD.UseVarargs") 114 | public static Map getParamValues(final Method method, final Object[] args) { 115 | final IntPredicate paramPredicate = index -> isAnnotated(method.getParameters()[index], Param.class); 116 | return getParameters(paramPredicate, method, 117 | toMap(index -> getParamValue(method.getParameters()[index]), index -> Objects.toString(args[index]))); 118 | } 119 | 120 | 121 | /** 122 | * @param predicate {@link IntPredicate} - Predicate to find parameters with special annotation. 123 | * 124 | * @param method {@link Method} - action. 125 | * @param collector {@link Collector} - Map collector. 126 | * @return {@link Map} return ParamName as Map Key, argument value as Map Value 127 | */ 128 | private static Map getParameters(final IntPredicate predicate, final Method method, 129 | final Collector> collector) { 130 | return IntStream.range(0, method.getParameterCount()) 131 | .filter(predicate) 132 | .boxed() 133 | .collect(collector); 134 | } 135 | 136 | private static String getParamValue(final AnnotatedElement element) { 137 | return element.getAnnotation(Param.class).value(); 138 | } 139 | 140 | private static String getQueryValue(final AnnotatedElement element) { 141 | return element.getAnnotation(Query.class).value(); 142 | } 143 | 144 | private static String getPathValue(final AnnotatedElement element) { 145 | return element.getAnnotation(Path.class).value(); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/AtlasWebElementMethodsTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.openqa.selenium.By; 7 | import org.openqa.selenium.WebElement; 8 | import org.openqa.selenium.interactions.Locatable; 9 | 10 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockWebElement; 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | import static org.mockito.Mockito.times; 13 | import static org.mockito.Mockito.verify; 14 | 15 | public class AtlasWebElementMethodsTest { 16 | 17 | private AtlasWebElement atlasWebElement; 18 | 19 | private WebElement originWebElement; 20 | 21 | @Before 22 | public void initElements() { 23 | originWebElement = mockWebElement(); 24 | atlasWebElement = new Atlas() 25 | .create(originWebElement, AtlasWebElement.class); 26 | } 27 | 28 | @Test 29 | public void clickMethodTest() { 30 | atlasWebElement.click(); 31 | verify(originWebElement, times(1)).click(); 32 | } 33 | 34 | @Test 35 | public void submitMethodTest() { 36 | atlasWebElement.submit(); 37 | verify(originWebElement, times(1)).submit(); 38 | } 39 | 40 | @Test 41 | public void sendKeysMethodTest() { 42 | atlasWebElement.sendKeys(); 43 | verify(originWebElement, times(1)).sendKeys(); 44 | } 45 | 46 | @Test 47 | public void clearMethodTest() { 48 | atlasWebElement.clear(); 49 | verify(originWebElement, times(1)).clear(); 50 | } 51 | 52 | @Test 53 | public void getTagNameMethodTest() { 54 | atlasWebElement.getTagName(); 55 | verify(originWebElement, times(1)).getTagName(); 56 | } 57 | 58 | @Test 59 | public void getAttributeMethodTest() { 60 | atlasWebElement.getAttribute(""); 61 | verify(originWebElement, times(1)).getAttribute(""); 62 | } 63 | 64 | @Test 65 | public void isSelectedMethodTest() { 66 | atlasWebElement.isSelected(); 67 | verify(originWebElement, times(1)).isSelected(); 68 | } 69 | 70 | @Test 71 | public void isEnabledMethodTest() { 72 | atlasWebElement.isEnabled(); 73 | verify(originWebElement, times(1)).isEnabled(); 74 | } 75 | 76 | @Test 77 | public void getTextMethodTest() { 78 | atlasWebElement.getText(); 79 | verify(originWebElement, times(1)).getText(); 80 | } 81 | 82 | @Test 83 | public void findElementsMethodTest() { 84 | atlasWebElement.findElements(By.xpath("")); 85 | verify(originWebElement, times(1)).findElements(By.xpath("")); 86 | } 87 | 88 | @Test 89 | public void findElementMethodTest() { 90 | atlasWebElement.findElement(By.xpath("")); 91 | verify(originWebElement, times(1)).findElement(By.xpath("")); 92 | } 93 | 94 | @Test 95 | public void isDisplayedMethodTest() { 96 | atlasWebElement.isDisplayed(); 97 | verify(originWebElement, times(1)).isDisplayed(); 98 | } 99 | 100 | @Test 101 | public void getLocationMethodTest() { 102 | atlasWebElement.getLocation(); 103 | verify(originWebElement, times(1)).getLocation(); 104 | } 105 | 106 | @Test 107 | public void getSizeMethodTest() { 108 | atlasWebElement.getSize(); 109 | verify(originWebElement, times(1)).getSize(); 110 | } 111 | 112 | @Test 113 | public void getRectMethodTest() { 114 | atlasWebElement.getRect(); 115 | verify(originWebElement, times(1)).getRect(); 116 | } 117 | 118 | @Test 119 | public void getCssValueMethodTest() { 120 | atlasWebElement.getCssValue(""); 121 | verify(originWebElement, times(1)).getCssValue(""); 122 | } 123 | 124 | @Test 125 | public void getCoordinatesMethodTest() { 126 | atlasWebElement.getCoordinates(); 127 | verify((Locatable) originWebElement, times(1)).getCoordinates(); 128 | } 129 | 130 | 131 | @Test 132 | public void toStringMethodTest() { 133 | assertThat(atlasWebElement.toString()).isEqualTo(originWebElement.toString()); 134 | } 135 | 136 | @Test 137 | public void hashCodeMethodTest() { 138 | assertThat(atlasWebElement.hashCode()).isEqualTo(originWebElement.hashCode()); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/ElementsCollectionTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.webdriver.extension.FindBy; 5 | import io.qameta.atlas.webdriver.extension.FindByCollectionExtension; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.mockito.stubbing.Answer; 9 | import org.openqa.selenium.By; 10 | import org.openqa.selenium.WebDriver; 11 | import org.openqa.selenium.WebElement; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Collections; 15 | import java.util.List; 16 | import java.util.concurrent.atomic.AtomicInteger; 17 | 18 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockAtlasWebElement; 19 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockWebElement; 20 | import static org.assertj.core.api.Assertions.assertThat; 21 | import static org.hamcrest.collection.IsCollectionWithSize.hasSize; 22 | import static org.mockito.Mockito.mock; 23 | import static org.mockito.Mockito.when; 24 | 25 | public class ElementsCollectionTest { 26 | 27 | private static final String SELECTOR = "//div"; 28 | private static final int DEFAULT_SIZE = 1; 29 | 30 | private WebElement parent; 31 | private ElementsCollection collection; 32 | 33 | @Before 34 | public void setUp() { 35 | parent = mockWebElement(); 36 | collection = mock(ElementsCollection.class); 37 | } 38 | 39 | @Test 40 | public void shouldFindElementsCollection() { 41 | when(parent.findElements(By.xpath(SELECTOR))).thenReturn(collection); 42 | when(collection.size()).thenReturn(DEFAULT_SIZE); 43 | 44 | ParentElement parentElement = new Atlas() 45 | .extension(new FindByCollectionExtension()) 46 | .create(parent, ParentElement.class); 47 | 48 | assertThat(parentElement.collection().size()).isEqualTo(DEFAULT_SIZE); 49 | } 50 | 51 | @Test(expected = NullPointerException.class) 52 | public void shouldNotInteractWithoutResult() { 53 | when(parent.findElements(By.xpath(SELECTOR))).thenReturn(null); 54 | when(collection.size()).thenReturn(DEFAULT_SIZE); 55 | 56 | ParentElement parentElement = new Atlas() 57 | .extension(new FindByCollectionExtension()) 58 | .create(parent, ParentElement.class); 59 | 60 | assertThat(parentElement.collection().size()).isEqualTo(DEFAULT_SIZE); 61 | } 62 | 63 | @Test 64 | public void shouldFindNestedElementFromCollection() { 65 | AtlasWebElement listElement = mockAtlasWebElement(); 66 | AtlasWebElement block = mockAtlasWebElement(); 67 | 68 | when(parent.findElements(By.xpath(SELECTOR))).thenReturn(collection); 69 | when(collection.size()).thenReturn(DEFAULT_SIZE); 70 | when(collection.get(0)).thenReturn(listElement); 71 | 72 | when(listElement.findElement(By.xpath(SELECTOR))).thenReturn(block); 73 | when(block.isDisplayed()).thenReturn(true); 74 | 75 | ParentElement parentElement = new Atlas(new WebDriverConfiguration(mock(WebDriver.class))) 76 | .create(parent, ParentElement.class); 77 | 78 | ListElement element = parentElement.collection().get(0); 79 | assertThat(element.block().isDisplayed()).isEqualTo(true); 80 | } 81 | 82 | @Test 83 | public void shouldRefreshCollectionWhenWaiting() { 84 | WebElement listElement = mockWebElement(); 85 | final AtomicInteger count = new AtomicInteger(); 86 | when(parent.findElements(By.xpath(SELECTOR))).then((Answer>) (invocation) -> { 87 | if (count.incrementAndGet() > 3) { 88 | return Collections.singletonList(listElement); 89 | } 90 | return new ArrayList<>(); 91 | }); 92 | ParentElement parentElement = new Atlas(new WebDriverConfiguration(mock(WebDriver.class))) 93 | .create(parent, ParentElement.class); 94 | parentElement.collection() 95 | .should(hasSize(1)); 96 | } 97 | 98 | interface ParentElement extends AtlasWebElement { 99 | 100 | @FindBy(SELECTOR) 101 | ElementsCollection collection(); 102 | 103 | } 104 | 105 | interface ListElement extends AtlasWebElement { 106 | 107 | @FindBy(SELECTOR) 108 | AtlasWebElement block(); 109 | 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/ExecuteJScriptMethodExtensionTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.openqa.selenium.JavascriptExecutor; 7 | import org.openqa.selenium.WebDriver; 8 | import org.openqa.selenium.WebElement; 9 | import org.openqa.selenium.remote.RemoteWebDriver; 10 | 11 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockWebElement; 12 | import static org.mockito.Mockito.*; 13 | 14 | public class ExecuteJScriptMethodExtensionTest { 15 | 16 | private AtlasWebElement atlasWebElement; 17 | private WebDriver driver; 18 | 19 | @Before 20 | public void initElements() { 21 | driver = mock(RemoteWebDriver.class, withSettings().extraInterfaces(WebDriver.class)); 22 | WebElement originWebElement = mockWebElement(); 23 | atlasWebElement = new Atlas(new WebDriverConfiguration(driver)) 24 | .create(originWebElement, AtlasWebElement.class); 25 | } 26 | 27 | @Test 28 | public void getWrappedElementMethodTest() { 29 | JavascriptExecutor js = (JavascriptExecutor) driver; 30 | String bodyScript = "arguments[0].click();"; 31 | atlasWebElement.executeScript(bodyScript); 32 | verify(js, times(1)).executeScript(bodyScript, atlasWebElement); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/ExtractCollectionExtensionTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.webdriver.extension.ExtractMethodExtension; 5 | import io.qameta.atlas.webdriver.extension.FindBy; 6 | import io.qameta.atlas.webdriver.extension.FindByCollectionExtension; 7 | import org.junit.Test; 8 | import org.openqa.selenium.By; 9 | import org.openqa.selenium.WebElement; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockAtlasWebElement; 15 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockWebElement; 16 | import static java.util.Arrays.asList; 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | import static org.mockito.Mockito.*; 19 | 20 | /** 21 | * @author artem.krosheninnikov 22 | */ 23 | public class ExtractCollectionExtensionTest { 24 | 25 | private static final String SELECTOR = "//div"; 26 | private static final int DEFAULT_SIZE = 1; 27 | 28 | 29 | private ElementsCollection collection; 30 | private AtlasWebElement element = mockAtlasWebElement(); 31 | 32 | @Test 33 | public void shouldExtractElementsCollectionToList() { 34 | when(element.getText()).thenReturn("mytext"); 35 | collection = createElements(element); 36 | 37 | assertThat(collection.extract(AtlasWebElement::getText)).contains(element.getText()); 38 | } 39 | 40 | @Test 41 | public void shouldExtractTwoTimesInARow() { 42 | WebElement parent = mockWebElement(); 43 | ElementsCollection collection = mock(ElementsCollection.class); 44 | StatusItem status = mock(StatusItem.class, withSettings()); 45 | AtlasWebElement innerElement = mockAtlasWebElement(); 46 | 47 | when(parent.getText()).thenReturn("text"); 48 | when(parent.findElements(By.xpath(SELECTOR))).thenReturn(collection); 49 | when(collection.size()).thenReturn(DEFAULT_SIZE); 50 | when(collection.get(0)).thenReturn(status); 51 | when(status.title()).thenReturn(innerElement); 52 | when(innerElement.getText()).thenReturn("innerText"); 53 | 54 | ParentElement parentElement = new Atlas() 55 | .extension(new ExtractMethodExtension()) 56 | .extension(new FindByCollectionExtension()) 57 | .create(parent, ParentElement.class); 58 | 59 | assertThat(parentElement.items().extract(StatusItem::title).extract(AtlasWebElement::getText)).contains("innerText"); 60 | } 61 | 62 | @SuppressWarnings("unchecked") 63 | private static ElementsCollection createElements(AtlasWebElement... elements) { 64 | List target = new ArrayList(asList(elements)); 65 | return new Atlas() 66 | .extension(new ExtractMethodExtension()) 67 | .create(target, ElementsCollection.class); 68 | } 69 | 70 | interface StatusItem extends AtlasWebElement { 71 | 72 | @FindBy(SELECTOR) 73 | AtlasWebElement title(); 74 | 75 | } 76 | 77 | interface ParentElement extends AtlasWebElement { 78 | 79 | @FindBy(SELECTOR) 80 | ElementsCollection items(); 81 | } 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/FilterCollectionExtensionTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.webdriver.extension.FilterCollectionExtension; 5 | import org.junit.Test; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockAtlasWebElement; 11 | import static java.util.Arrays.asList; 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | import static org.hamcrest.collection.IsCollectionWithSize.hasSize; 14 | import static org.mockito.Mockito.when; 15 | 16 | /** 17 | * @author kurau (Yuri Kalinin) 18 | */ 19 | public class FilterCollectionExtensionTest { 20 | 21 | private static final boolean IS_DISPLAYED = true; 22 | private static final boolean NOT_DISPLAYED = false; 23 | 24 | private static final String TEXT = "text"; 25 | private static final String WRONG_TEXT = "wrong"; 26 | 27 | private ElementsCollection collection; 28 | private AtlasWebElement element = mockAtlasWebElement(); 29 | 30 | @Test 31 | public void shouldReturnNewCollectionAfterFilter() { 32 | when(element.isDisplayed()).thenReturn(IS_DISPLAYED); 33 | collection = createElementsCollection(element); 34 | 35 | assertThat("Should return only displayed element", collection.filter(AtlasWebElement::isDisplayed), hasSize(1)); 36 | } 37 | 38 | @Test 39 | public void shouldFilterElementsTwice() { 40 | when(element.isDisplayed()).thenReturn(IS_DISPLAYED); 41 | when(element.getText()).thenReturn(TEXT); 42 | collection = createElementsCollection(element); 43 | 44 | assertThat("Should filter the elements twice", collection.filter(AtlasWebElement::isDisplayed) 45 | .filter(e -> e.getText().contains(WRONG_TEXT)), hasSize(0)); 46 | } 47 | 48 | @Test 49 | public void shouldReturnEmptyCollectionIf() { 50 | when(element.isDisplayed()).thenReturn(NOT_DISPLAYED); 51 | collection = createElementsCollection(element); 52 | 53 | assertThat("Should return empty list", collection.filter(AtlasWebElement::isDisplayed), hasSize(0)); 54 | } 55 | 56 | @SuppressWarnings("unchecked") 57 | private static ElementsCollection createElementsCollection(AtlasWebElement... elements) { 58 | List target = new ArrayList(asList(elements)); 59 | return new Atlas() 60 | .extension(new FilterCollectionExtension()) 61 | .create(target, ElementsCollection.class); 62 | } 63 | } -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/FindByParameterizedTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.webdriver.extension.FindBy; 5 | import io.qameta.atlas.webdriver.extension.FindByCollectionExtension; 6 | import io.qameta.atlas.webdriver.extension.FindByExtension; 7 | import io.qameta.atlas.webdriver.extension.Param; 8 | import org.apache.commons.lang3.RandomStringUtils; 9 | import org.junit.Test; 10 | import org.openqa.selenium.By; 11 | import org.openqa.selenium.WebElement; 12 | 13 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockWebElement; 14 | import static java.util.Arrays.asList; 15 | import static org.mockito.ArgumentMatchers.any; 16 | import static org.mockito.Mockito.times; 17 | import static org.mockito.Mockito.verify; 18 | import static org.mockito.Mockito.when; 19 | 20 | /** 21 | * @author kurau (Yuri Kalinin) 22 | */ 23 | public class FindByParameterizedTest { 24 | 25 | private WebElement parent = mockWebElement(); 26 | 27 | private Atlas atlas; 28 | 29 | @Test 30 | public void shouldParameterizedFindBy() { 31 | when(parent.findElement(any(By.class))).thenReturn(mockWebElement()); 32 | atlas = new Atlas().extension(new FindByExtension()); 33 | 34 | String param = RandomStringUtils.randomAlphanumeric(10); 35 | 36 | ParentElement atlasWebElement = atlas.create(parent, ParentElement.class); 37 | atlasWebElement.childWithName(param).isDisplayed(); 38 | 39 | verify(parent, times(1)).findElement(By.xpath(String.format("//div[%s]", param))); 40 | } 41 | 42 | @Test 43 | public void shouldParameterizedFindByCollection() { 44 | when(parent.findElements(any(By.class))).thenReturn(asList(mockWebElement())); 45 | atlas = new Atlas().extension(new FindByCollectionExtension()); 46 | 47 | String param = RandomStringUtils.randomAlphanumeric(10); 48 | 49 | ParentElement atlasWebElement = atlas.create(parent, ParentElement.class); 50 | atlasWebElement.elements(param).size(); 51 | 52 | verify(parent, times(1)).findElements(By.xpath(String.format("//td[%s]", param))); 53 | } 54 | 55 | interface ParentElement extends AtlasWebElement { 56 | 57 | @FindBy("//div[{{ value }}]") 58 | AtlasWebElement childWithName(@Param("value") String value); 59 | 60 | @FindBy("//td[{{ value }}]") 61 | ElementsCollection elements(@Param("value") String value); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/FindByRetrierTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.core.api.Retry; 5 | import io.qameta.atlas.core.context.RetryerContext; 6 | import io.qameta.atlas.core.internal.DefaultRetryer; 7 | import io.qameta.atlas.core.internal.EmptyRetryer; 8 | import io.qameta.atlas.webdriver.extension.FindBy; 9 | import org.junit.Test; 10 | import org.openqa.selenium.By; 11 | import org.openqa.selenium.NotFoundException; 12 | import org.openqa.selenium.WebElement; 13 | 14 | import java.util.Collections; 15 | 16 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockWebDriver; 17 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockWebElement; 18 | import static org.mockito.Mockito.when; 19 | 20 | public class FindByRetrierTest { 21 | 22 | @Test(expected = NotFoundException.class) 23 | public void retryChildFind() { 24 | WebElement parentOrigin = mockWebElement(); 25 | WebElement childOrigin = mockWebElement(); 26 | 27 | when(parentOrigin.findElement(By.xpath("//div"))).thenThrow(new NotFoundException()); 28 | when(childOrigin.isDisplayed()).thenThrow(new NotFoundException()); 29 | 30 | Atlas atlas = new Atlas(new WebDriverConfiguration(mockWebDriver())); 31 | ParentElement parent = atlas.create(parentOrigin, ParentElement.class); 32 | 33 | parent.child().isDisplayed(); 34 | } 35 | 36 | 37 | 38 | @Test(expected = NotFoundException.class) 39 | public void shouldSetGlobalRetryThroughDefaultRetryer() { 40 | WebElement parentOrigin = mockWebElement(); 41 | when(parentOrigin.isDisplayed()).thenThrow(new NotFoundException()); 42 | 43 | Atlas atlas = new Atlas(new WebDriverConfiguration(mockWebDriver())) 44 | .context(new RetryerContext( 45 | new DefaultRetryer(3000L, 1000L, Collections.singletonList(Throwable.class)))); 46 | ParentElement parent = atlas.create(parentOrigin, ParentElement.class); 47 | parent.isDisplayed(); 48 | } 49 | 50 | @Test(expected = NotFoundException.class) 51 | public void shouldSetGlobalRetryThroughEmptyRetryer() { 52 | WebElement parentOrigin = mockWebElement(); 53 | when(parentOrigin.isDisplayed()).thenThrow(new NotFoundException()); 54 | 55 | Atlas atlas = new Atlas(new WebDriverConfiguration(mockWebDriver())) 56 | .context(new RetryerContext(new EmptyRetryer())); 57 | ParentElement parent = atlas.create(parentOrigin, ParentElement.class); 58 | parent.isDisplayed(); 59 | } 60 | 61 | interface ParentElement extends AtlasWebElement { 62 | @Retry(timeout = 8000) 63 | @FindBy("//div") 64 | NestedElement child(); 65 | } 66 | 67 | interface NestedElement extends AtlasWebElement { 68 | boolean isDisplayed(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/FindByWithNameTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.webdriver.extension.FindBy; 5 | import io.qameta.atlas.webdriver.extension.FindByCollectionExtension; 6 | import io.qameta.atlas.webdriver.extension.FindByExtension; 7 | import io.qameta.atlas.webdriver.extension.Name; 8 | import io.qameta.atlas.webdriver.extension.Param; 9 | import io.qameta.atlas.webdriver.extension.ToStringMethodExtension; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.openqa.selenium.By; 13 | import org.openqa.selenium.WebElement; 14 | 15 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.*; 16 | import static java.util.Collections.singletonList; 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | import static org.mockito.Mockito.when; 19 | 20 | public class FindByWithNameTest { 21 | 22 | private static final String PARAM = "id"; 23 | private static final String PARAM_VALUE = "val"; 24 | private static final String SELECTOR = "//*[@id=\"{{ " + PARAM + " }}\"]"; 25 | private static final String FINAL_SELECTOR = "//*[@id=\"" + PARAM_VALUE + "\"]"; 26 | private static final String ELEMENT_NAME = "element {{ " + PARAM + " }}"; 27 | private static final String FINAL_ELEMENT_NAME = "element " + PARAM_VALUE; 28 | 29 | private WebElement parent; 30 | 31 | private Atlas atlas; 32 | 33 | @Before 34 | public void createParent() { 35 | parent = mockWebElement(); 36 | atlas = new Atlas() 37 | .extension(new ToStringMethodExtension()) 38 | .extension(new FindByExtension()) 39 | .extension(new FindByCollectionExtension()); 40 | } 41 | 42 | @Test 43 | public void shouldUseTemplateInNameAnnotationForElement() { 44 | ParentElement atlasWebElement = atlas.create(parent, ParentElement.class); 45 | assertThat(atlasWebElement.childWithName(PARAM_VALUE).toString()).isEqualTo(FINAL_ELEMENT_NAME); 46 | } 47 | 48 | @Test 49 | public void shouldUseTemplateInNameAnnotationForElementsCollection() { 50 | ParentElement atlasWebElement = atlas.create(parent, ParentElement.class); 51 | assertThat(atlasWebElement.childListWithName(PARAM_VALUE).toString()).isEqualTo(FINAL_ELEMENT_NAME); 52 | } 53 | 54 | @Test 55 | public void shouldUseTemplateInNameAnnotationForCollectionElement() { 56 | ParentElement atlasWebElement = atlas.create(parent, ParentElement.class); 57 | when(parent.findElements(By.xpath(FINAL_SELECTOR))).thenReturn(singletonList(mockAtlasWebElement())); 58 | 59 | int first = 0; 60 | assertThat(atlasWebElement.childListWithName(PARAM_VALUE).get(first).toString()) 61 | .isEqualTo(listElementName(FINAL_ELEMENT_NAME, first)); 62 | } 63 | 64 | interface ParentElement extends AtlasWebElement { 65 | 66 | @Name(ELEMENT_NAME) 67 | @FindBy(SELECTOR) 68 | AtlasWebElement childWithName(@Param(PARAM) String id); 69 | 70 | @Name(ELEMENT_NAME) 71 | @FindBy(SELECTOR) 72 | ElementsCollection childListWithName(@Param(PARAM) String id); 73 | 74 | } 75 | 76 | private String listElementName(final String name, final int position) { 77 | return String.format("%s [%s]", name, position); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/FindByWithToStringExtensionTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.webdriver.extension.FindBy; 5 | import io.qameta.atlas.webdriver.extension.FindByExtension; 6 | import io.qameta.atlas.webdriver.extension.Name; 7 | import io.qameta.atlas.webdriver.extension.ToStringMethodExtension; 8 | import org.junit.Before; 9 | import org.junit.Ignore; 10 | import org.junit.Test; 11 | import org.openqa.selenium.By; 12 | import org.openqa.selenium.WebElement; 13 | 14 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.*; 15 | import static java.util.Arrays.asList; 16 | import static org.assertj.core.api.Assertions.assertThat; 17 | import static org.mockito.Mockito.when; 18 | 19 | /** 20 | * @author kurau (Yuri Kalinin) 21 | */ 22 | public class FindByWithToStringExtensionTest { 23 | 24 | private static final String SELECTOR = "//div"; 25 | private static final String ELEMENT_NAME = "my name"; 26 | private static final String METHOD_NAME = "childWithoutName"; 27 | private static final String METHOD_LIST_NAME = "childList"; 28 | 29 | private WebElement parent; 30 | 31 | private Atlas atlas; 32 | 33 | @Before 34 | public void createParent() { 35 | parent = mockWebElement(); 36 | atlas = new Atlas() 37 | .extension(new ToStringMethodExtension()) 38 | .extension(new FindByExtension()); 39 | } 40 | 41 | @Test 42 | public void shouldUseNameAnnotationInFindByExtension() { 43 | ParentElement atlasWebElement = atlas.create(parent, ParentElement.class); 44 | assertThat(atlasWebElement.childWithName().toString()).isEqualTo(ELEMENT_NAME); 45 | } 46 | 47 | @Test 48 | public void shouldUseMethodNameInFindByExtension() { 49 | ParentElement atlasWebElement = atlas.create(parent, ParentElement.class); 50 | assertThat(atlasWebElement.childWithoutName().toString()).isEqualTo(METHOD_NAME); 51 | } 52 | 53 | @Test 54 | @Ignore("Fix element name in collection") 55 | public void shouldUseMethodNameInFindByCollectionExtension() { 56 | ParentElement atlasWebElement = atlas.create(parent, ParentElement.class); 57 | assertThat(atlasWebElement.childList().toString()).isEqualTo(METHOD_LIST_NAME); 58 | } 59 | 60 | @Test 61 | @Ignore("Fix element name in collection") 62 | public void shouldUseNameAnnotationInFindByCollectionExtension() { 63 | ParentElement atlasWebElement = atlas.create(parent, ParentElement.class); 64 | assertThat(atlasWebElement.childListWithName().toString()).isEqualTo(ELEMENT_NAME); 65 | } 66 | 67 | @Test 68 | @Ignore("Fix element name in collection") 69 | public void shouldAppendIndexToElementNameFromCollection() { 70 | ParentElement atlasWebElement = atlas.create(parent, ParentElement.class); 71 | when(parent.findElements(By.xpath(SELECTOR))).thenReturn(asList(mockAtlasWebElement())); 72 | 73 | int first = 0; 74 | assertThat(atlasWebElement.childListWithName().get(first).toString()) 75 | .isEqualTo(listElementName(ELEMENT_NAME, first)); 76 | } 77 | 78 | @Test 79 | @Ignore("Fix element name in collection") 80 | public void shouldAppendIndexToMethodNameFromCollection() { 81 | ParentElement atlasWebElement = atlas.create(parent, ParentElement.class); 82 | when(parent.findElements(By.xpath(SELECTOR))).thenReturn(asList(mockAtlasWebElement())); 83 | 84 | int first = 0; 85 | assertThat(atlasWebElement.childList().get(first).toString()) 86 | .isEqualTo(listElementName(METHOD_LIST_NAME, first)); 87 | } 88 | 89 | interface ParentElement extends AtlasWebElement { 90 | 91 | @Name(ELEMENT_NAME) 92 | @FindBy(SELECTOR) 93 | ChildElement childWithName(); 94 | 95 | @FindBy(SELECTOR) 96 | ChildElement childWithoutName(); 97 | 98 | @Name(ELEMENT_NAME) 99 | @FindBy(SELECTOR) 100 | ElementsCollection childListWithName(); 101 | 102 | @FindBy(SELECTOR) 103 | ElementsCollection childList(); 104 | 105 | } 106 | 107 | interface ChildElement extends AtlasWebElement { 108 | 109 | } 110 | 111 | private String listElementName(String name, int index) { 112 | return String.format("%s [%s]", name, index); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/NestedElementMethodTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.webdriver.extension.FindBy; 5 | import io.qameta.atlas.webdriver.extension.FindByExtension; 6 | import io.qameta.atlas.webdriver.extension.ToStringMethodExtension; 7 | import org.junit.Test; 8 | import org.openqa.selenium.By; 9 | import org.openqa.selenium.WebElement; 10 | 11 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockWebElement; 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | import static org.mockito.Mockito.verify; 14 | import static org.mockito.Mockito.when; 15 | 16 | public class NestedElementMethodTest { 17 | 18 | private static final String CHILD_SELECTOR = "//div"; 19 | private static final String LEAF_SELECTOR = "//div"; 20 | 21 | @Test 22 | public void shouldFindNestedElement() { 23 | WebElement parent = mockWebElement(); 24 | 25 | WebElement child = mockWebElement(); 26 | when(parent.findElement(By.xpath(CHILD_SELECTOR))).thenReturn(child); 27 | 28 | WebElement leaf = mockWebElement(); 29 | when(child.findElement(By.xpath(LEAF_SELECTOR))).thenReturn(leaf); 30 | 31 | ParentElement parentElement = new Atlas() 32 | .extension(new FindByExtension()) 33 | .extension(new ToStringMethodExtension()) 34 | .create(parent, ParentElement.class); 35 | 36 | assertThat(parentElement.child()).isNotNull(); 37 | 38 | parentElement.child().isDisplayed(); 39 | verify(child).isDisplayed(); 40 | 41 | assertThat(parentElement.child().leaf()).isNotNull(); 42 | 43 | parentElement.child().leaf().isDisplayed(); 44 | verify(leaf).isDisplayed(); 45 | } 46 | 47 | interface ParentElement extends AtlasWebElement { 48 | @FindBy(CHILD_SELECTOR) 49 | ChildElement child(); 50 | } 51 | 52 | interface ChildElement extends AtlasWebElement { 53 | @FindBy(LEAF_SELECTOR) 54 | LeafElement leaf(); 55 | } 56 | 57 | interface LeafElement extends AtlasWebElement { 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/PageExtensionTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.core.AtlasException; 5 | import io.qameta.atlas.webdriver.extension.Page; 6 | import io.qameta.atlas.webdriver.extension.Path; 7 | import io.qameta.atlas.webdriver.extension.Query; 8 | import io.qameta.atlas.webdriver.extension.QueryMap; 9 | import org.junit.After; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.mockito.ArgumentCaptor; 13 | import org.openqa.selenium.WebDriver; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockAtlasWebElement; 19 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockWebDriver; 20 | import static org.hamcrest.CoreMatchers.allOf; 21 | import static org.hamcrest.CoreMatchers.containsString; 22 | import static org.hamcrest.MatcherAssert.assertThat; 23 | import static org.junit.Assert.assertEquals; 24 | import static org.mockito.Mockito.*; 25 | 26 | public class PageExtensionTest { 27 | 28 | private WebDriver driver; 29 | 30 | private String atlas = "atlas"; 31 | 32 | @Before 33 | public void setUp() { 34 | this.driver = mockWebDriver(); 35 | } 36 | 37 | @After 38 | public void stopDriver() { 39 | this.driver.quit(); 40 | } 41 | 42 | 43 | @Test 44 | public void shouldHandleSingleQueryParam() { 45 | String exceptedURI = "https://github.com/search?abs=%D0%BA%D0%B8%D1%80%D0%B8%D0%BB%D0%BB%D0%B8%D1%86%D0%B0"; 46 | TestSite onSite = 47 | new Atlas(new WebDriverConfiguration(driver, "https://github.com")) 48 | .create(driver, TestSite.class); 49 | 50 | onSite.onMainPage("кириллица").atlasWebElement.click(); 51 | verify(driver, times(1)).get(exceptedURI); 52 | } 53 | 54 | @Test 55 | public void shouldHandleDefaultPathOfPage() { 56 | when(driver.getTitle()).thenReturn(atlas); 57 | TestSiteWithDefaultPage onSite = 58 | new Atlas(new WebDriverConfiguration(driver, "https://github.com")) 59 | .create(driver, TestSiteWithDefaultPage.class); 60 | 61 | String title = onSite.onMainPage().getWrappedDriver().getTitle(); 62 | assertEquals(atlas, title); 63 | } 64 | 65 | @Test 66 | public void shouldHandleDefaultPathWithPathSegment() { 67 | TestSiteWithDefaultPageAndQuery onSite = 68 | new Atlas(new WebDriverConfiguration(driver, "https://github.com")) 69 | .create(driver, TestSiteWithDefaultPageAndQuery.class); 70 | 71 | Map queryMap = new HashMap<>(); 72 | queryMap.put("first", "value-1"); 73 | queryMap.put("second", "value-2"); 74 | 75 | onSite.onMainPage(atlas, queryMap).atlasWebElement 76 | .sendKeys("Use driver.get(...) and then sendKeys"); 77 | 78 | verify(driver, times(1)) 79 | .get("https://github.com/?q=atlas&first=value-1&second=value-2"); 80 | } 81 | 82 | 83 | @Test 84 | public void shouldHandleQueryParams() { 85 | ArgumentCaptor stringCaptor = ArgumentCaptor.forClass(String.class); 86 | TestSite onSite = 87 | new Atlas(new WebDriverConfiguration(driver, "https://github.com")) 88 | .create(driver, TestSite.class); 89 | Map queryMap = new HashMap<>(); 90 | queryMap.put("first", "value-1"); 91 | queryMap.put("second", "value-2"); 92 | onSite.onMainPage("100", queryMap).atlasWebElement.sendKeys("Use driver.get(...) and then sendKeys"); 93 | verify(driver, times(1)).get(stringCaptor.capture()); 94 | 95 | String capturedStringArg = stringCaptor.getValue(); 96 | assertThat(capturedStringArg, allOf(containsString("total?"), 97 | containsString("q=100"), 98 | containsString("first=value-1"), 99 | containsString("second=value-2"), 100 | containsString("?"))); 101 | assertEquals(3, capturedStringArg.split("&").length); 102 | } 103 | 104 | 105 | @Test(expected = AtlasException.class) 106 | public void siteWithOutAnyBaseURIShouldThrowException() { 107 | System.getProperties().clear(); 108 | TestSiteWithOutAnyURI siteWithOutUrl = 109 | new Atlas(new WebDriverConfiguration(mockWebDriver())) 110 | .create(driver, TestSiteWithOutAnyURI.class); 111 | siteWithOutUrl.onMainPage("null"); 112 | } 113 | 114 | @Test 115 | public void setBaseURIMethodShouldHaveWebSite() { 116 | TestSiteWithOutAnyURI siteWithOutUrl = 117 | new Atlas(new WebDriverConfiguration(driver, "https://github.com")) 118 | .create(driver, TestSiteWithOutAnyURI.class); 119 | 120 | siteWithOutUrl.onMainPage("zero").atlasWebElement.click(); 121 | verify(driver, times(1)).get("https://github.com/search?a=zero"); 122 | } 123 | 124 | @Test 125 | public void shouldHandlePathParams() { 126 | TestSiteWithPathParams siteWithPath = 127 | new Atlas(new WebDriverConfiguration(driver, "https://github.com")) 128 | .create(driver, TestSiteWithPathParams.class); 129 | Map queryMap = new HashMap<>(); 130 | queryMap.put("first", "value-1"); 131 | 132 | Map queryMap1 = new HashMap<>(); 133 | queryMap1.put("second", "value-2"); 134 | 135 | siteWithPath.onMainPage(1100109, "Atlas", 100, queryMap, queryMap1).atlasWebElement.click(); 136 | verify(driver, times(1)).get("https://github.com/users/1100109/Atlas?q=100&first=value-1&second=value-2"); 137 | } 138 | 139 | 140 | @Test 141 | public void shouldHandleUrlWithUserAndPassword() { 142 | TestSiteWithOutAnyURI siteWithUserAndPass = 143 | new Atlas(new WebDriverConfiguration(driver, "http://username:password@example.com/")) 144 | .create(driver, TestSiteWithOutAnyURI.class); 145 | 146 | siteWithUserAndPass.onMainPage("zero").atlasWebElement.click(); 147 | verify(driver, times(1)).get("http://username:password@example.com/search?a=zero"); 148 | } 149 | 150 | 151 | @Test 152 | public void shouldHandleUrlWithPort() { 153 | TestSiteWithOutAnyURI siteWithUserAndPass = 154 | new Atlas(new WebDriverConfiguration(driver, "http://example.com:8443/")) 155 | .create(driver, TestSiteWithOutAnyURI.class); 156 | 157 | siteWithUserAndPass.onMainPage("zero").atlasWebElement.click(); 158 | verify(driver, times(1)).get("http://example.com:8443/search?a=zero"); 159 | } 160 | 161 | 162 | public interface TestSiteWithDefaultPageAndQuery extends WebSite { 163 | @Page 164 | MainPage onMainPage(@Query("q") String value, @QueryMap Map queryMap); 165 | } 166 | 167 | 168 | public interface TestSite extends WebSite { 169 | @Page(url = "total") 170 | MainPage onMainPage(@Query("q") String value, @QueryMap Map queryMap); 171 | 172 | @Page(url = "search") 173 | MainPage onMainPage(@Query("abs") String value); 174 | } 175 | 176 | public interface TestSiteWithPathParams extends WebSite { 177 | @Page(url = "users/{id}/{project}") 178 | MainPage onMainPage(@Path("id") long customerId, @Path("project") String name, @Query("q") Integer value, @QueryMap Map queryMap, @QueryMap Map queryMap1); 179 | } 180 | 181 | public interface TestSiteWithOutAnyURI extends WebSite { 182 | @Page(url = "search") 183 | MainPage onMainPage(@Query("a") String value); 184 | } 185 | 186 | 187 | public interface TestSiteWithDefaultPage extends WebSite { 188 | @Page 189 | MainPage onMainPage(); 190 | } 191 | 192 | public interface MainPage extends WebPage { 193 | AtlasWebElement atlasWebElement = mockAtlasWebElement(); 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/ShouldExtensionTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.webdriver.extension.ShouldMethodExtension; 5 | import org.apache.commons.lang3.RandomStringUtils; 6 | import org.hamcrest.Matcher; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.openqa.selenium.WebElement; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockWebElement; 15 | import static java.util.Arrays.asList; 16 | import static org.hamcrest.Matchers.hasItem; 17 | import static org.hamcrest.Matchers.hasSize; 18 | import static org.mockito.Mockito.when; 19 | import static ru.yandex.qatools.matchers.webdriver.DisplayedMatcher.displayed; 20 | 21 | /** 22 | * @author kurau (Yuri Kalinin) 23 | */ 24 | public class ShouldExtensionTest { 25 | 26 | private static final Matcher DISPLAYED_MATCHER = displayed(); 27 | 28 | private static final boolean IS_DISPLAYED = true; 29 | private static final boolean NOT_DISPLAYED = false; 30 | private static final int TIMEOUT = 5; 31 | 32 | private WebElement baseElement = mockWebElement(); 33 | private AtlasWebElement atlasWebElement; 34 | private ElementsCollection collection; 35 | 36 | 37 | @Before 38 | public void createAtlasElementWithExtension() { 39 | atlasWebElement = new Atlas() 40 | .extension(new ShouldMethodExtension()) 41 | .create(baseElement, AtlasWebElement.class); 42 | } 43 | 44 | @Test 45 | public void shouldPassOneArgumentShouldMethod() { 46 | when(baseElement.isDisplayed()).thenReturn(IS_DISPLAYED); 47 | atlasWebElement.should(DISPLAYED_MATCHER); 48 | } 49 | 50 | @Test 51 | public void shouldPassOneArgumentShouldMethodWithTimeOut() { 52 | when(baseElement.isDisplayed()).thenReturn(IS_DISPLAYED); 53 | atlasWebElement.should(DISPLAYED_MATCHER, TIMEOUT); 54 | } 55 | 56 | @Test 57 | public void shouldPassTwoArgumentShouldMethod() { 58 | String message = RandomStringUtils.randomAlphanumeric(10); 59 | when(baseElement.isDisplayed()).thenReturn(IS_DISPLAYED); 60 | atlasWebElement.should(message, DISPLAYED_MATCHER); 61 | } 62 | 63 | @Test 64 | public void shouldPassTwoArgumentShouldMethodWithTimeOut() { 65 | String message = RandomStringUtils.randomAlphanumeric(10); 66 | when(baseElement.isDisplayed()).thenReturn(IS_DISPLAYED); 67 | atlasWebElement.should(message, DISPLAYED_MATCHER, TIMEOUT); 68 | } 69 | 70 | @Test(expected = AssertionError.class) 71 | public void shouldThrowAssertionErrorInOneArgumentShouldMethod() { 72 | when(baseElement.isDisplayed()).thenReturn(NOT_DISPLAYED); 73 | atlasWebElement.should(DISPLAYED_MATCHER); 74 | } 75 | 76 | @Test(expected = AssertionError.class) 77 | public void shouldThrowAssertionErrorInTwoArgumentShouldMethod() { 78 | String message = RandomStringUtils.randomAlphanumeric(10); 79 | 80 | when(baseElement.isDisplayed()).thenReturn(NOT_DISPLAYED); 81 | atlasWebElement.should(message, DISPLAYED_MATCHER); 82 | } 83 | 84 | @Test 85 | public void shouldUseMethodForCollection() { 86 | collection = createElementsCollection(atlasWebElement); 87 | collection.should(hasSize(1)); 88 | } 89 | 90 | @Test(expected = AssertionError.class) 91 | public void shouldThrowAssertionErrorForCollection() { 92 | collection = createElementsCollection(atlasWebElement); 93 | collection.should(hasSize(0)); 94 | } 95 | 96 | @Test 97 | public void shouldUseMethodForCollectionElements() { 98 | when(atlasWebElement.isDisplayed()).thenReturn(IS_DISPLAYED); 99 | collection = createElementsCollection(atlasWebElement); 100 | collection.should(hasItem(DISPLAYED_MATCHER)); 101 | } 102 | 103 | @Test(expected = AssertionError.class) 104 | public void shouldThrowAssertionErrorForCollectionElements() { 105 | when(atlasWebElement.isDisplayed()).thenReturn(NOT_DISPLAYED); 106 | collection = createElementsCollection(atlasWebElement); 107 | collection.should(hasItem(DISPLAYED_MATCHER)); 108 | } 109 | 110 | @SuppressWarnings("unchecked") 111 | private static ElementsCollection createElementsCollection(AtlasWebElement... elements) { 112 | List target = new ArrayList(); 113 | target.addAll(asList(elements)); 114 | return new Atlas() 115 | .extension(new ShouldMethodExtension()) 116 | .create(target, ElementsCollection.class); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/ToStringExtensionTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.webdriver.extension.ToStringMethodExtension; 5 | import org.apache.commons.lang3.RandomStringUtils; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.openqa.selenium.WebElement; 9 | 10 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockWebElement; 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | import static org.mockito.Mockito.when; 13 | 14 | /** 15 | * @author kurau (Yuri Kalinin) 16 | */ 17 | public class ToStringExtensionTest { 18 | 19 | private String message; 20 | private WebElement parent; 21 | 22 | @Before 23 | public void createParent() { 24 | message = RandomStringUtils.randomAlphanumeric(10); 25 | parent = mockWebElement(); 26 | } 27 | 28 | @Test 29 | public void shouldUseDefaultToStringMethodWithoutExtension() { 30 | AtlasWebElement atlasWebElement = new Atlas() 31 | .create(parent, AtlasWebElement.class); 32 | when(parent.toString()).thenReturn(message); 33 | 34 | assertThat(atlasWebElement.toString()).isEqualTo(message); 35 | } 36 | 37 | @Test 38 | public void shouldUseToStringExtensionMethodName() { 39 | AtlasWebElement atlasWebElement = new Atlas() 40 | .extension(new ToStringMethodExtension()) 41 | .create(message, parent, AtlasWebElement.class); 42 | 43 | assertThat(atlasWebElement.toString()).isEqualTo(message); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/WaitUntilExtensionTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | import io.qameta.atlas.core.Atlas; 4 | import io.qameta.atlas.webdriver.exception.WaitUntilException; 5 | import io.qameta.atlas.webdriver.extension.WaitUntilMethodExtension; 6 | import org.apache.commons.lang3.RandomStringUtils; 7 | import org.hamcrest.Matcher; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.openqa.selenium.WebElement; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockWebElement; 16 | import static java.util.Arrays.asList; 17 | import static org.hamcrest.Matchers.hasItem; 18 | import static org.hamcrest.Matchers.hasSize; 19 | import static org.mockito.Mockito.when; 20 | import static ru.yandex.qatools.matchers.webdriver.DisplayedMatcher.displayed; 21 | 22 | /** 23 | * @author kurau (Yuri Kalinin) 24 | */ 25 | public class WaitUntilExtensionTest { 26 | 27 | private static final Matcher DISPLAYED_MATCHER = displayed(); 28 | 29 | private static final boolean IS_DISPLAYED = true; 30 | private static final boolean NOT_DISPLAYED = false; 31 | 32 | private WebElement baseElement = mockWebElement(); 33 | private AtlasWebElement atlasWebElement; 34 | private ElementsCollection collection; 35 | 36 | @Before 37 | public void createAtlasElementWithExtension() { 38 | atlasWebElement = new Atlas() 39 | .extension(new WaitUntilMethodExtension()) 40 | .create(baseElement, AtlasWebElement.class); 41 | } 42 | 43 | @Test 44 | public void shouldPassOneArgumentWaitUntilMethod() { 45 | when(baseElement.isDisplayed()).thenReturn(IS_DISPLAYED); 46 | atlasWebElement.waitUntil(DISPLAYED_MATCHER); 47 | } 48 | 49 | @Test 50 | public void shouldPassTwoArgumentWaitUntilMethod() { 51 | String message = RandomStringUtils.randomAlphanumeric(10); 52 | 53 | when(baseElement.isDisplayed()).thenReturn(IS_DISPLAYED); 54 | atlasWebElement.waitUntil(message, DISPLAYED_MATCHER); 55 | } 56 | 57 | @Test(expected = WaitUntilException.class) 58 | public void shouldThrowExceptionInOneArgumentWaitUntilMethod() { 59 | when(baseElement.isDisplayed()).thenReturn(NOT_DISPLAYED); 60 | atlasWebElement.waitUntil(DISPLAYED_MATCHER); 61 | } 62 | 63 | @Test(expected = WaitUntilException.class) 64 | public void shouldThrowExceptionInTwoArgumentWaitUntilMethod() { 65 | String message = RandomStringUtils.randomAlphanumeric(10); 66 | 67 | when(baseElement.isDisplayed()).thenReturn(NOT_DISPLAYED); 68 | atlasWebElement.waitUntil(message, DISPLAYED_MATCHER); 69 | } 70 | 71 | @Test 72 | public void shouldUseMethodForCollection() { 73 | collection = createElementsCollection(atlasWebElement); 74 | collection.waitUntil(hasSize(1)); 75 | } 76 | 77 | @Test(expected = WaitUntilException.class) 78 | public void shouldThrowExceptionForCollection() { 79 | collection = createElementsCollection(atlasWebElement); 80 | collection.waitUntil(hasSize(0)); 81 | } 82 | 83 | @Test 84 | public void shouldUseMethodForCollectionElements() { 85 | when(atlasWebElement.isDisplayed()).thenReturn(IS_DISPLAYED); 86 | collection = createElementsCollection(atlasWebElement); 87 | collection.waitUntil(hasItem(DISPLAYED_MATCHER)); 88 | } 89 | 90 | @Test(expected = WaitUntilException.class) 91 | public void shouldThrowExceptionForCollectionElements() { 92 | when(atlasWebElement.isDisplayed()).thenReturn(NOT_DISPLAYED); 93 | collection = createElementsCollection(atlasWebElement); 94 | collection.waitUntil(hasItem(DISPLAYED_MATCHER)); 95 | } 96 | 97 | @SuppressWarnings("unchecked") 98 | private static ElementsCollection createElementsCollection(AtlasWebElement... elements) { 99 | List target = new ArrayList(); 100 | target.addAll(asList(elements)); 101 | return new Atlas() 102 | .extension(new WaitUntilMethodExtension()) 103 | .create(target, ElementsCollection.class); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/WrappedElementMethodExtensionTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver; 2 | 3 | 4 | import io.qameta.atlas.core.Atlas; 5 | import io.qameta.atlas.webdriver.extension.WrappedElementMethodExtension; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.openqa.selenium.WebElement; 9 | 10 | import static io.qameta.atlas.webdriver.testdata.ObjectFactory.mockWebElement; 11 | import static org.mockito.Mockito.times; 12 | import static org.mockito.Mockito.verify; 13 | 14 | public class WrappedElementMethodExtensionTest { 15 | 16 | private AtlasWebElement atlasWebElement; 17 | private WebElement originWebElement; 18 | 19 | @Before 20 | public void initElements() { 21 | originWebElement = mockWebElement(); 22 | atlasWebElement = new Atlas() 23 | .extension(new WrappedElementMethodExtension()) 24 | .create(originWebElement, AtlasWebElement.class); 25 | } 26 | 27 | @Test 28 | public void getWrappedElementMethodTest() { 29 | WebElement element = atlasWebElement.getWrappedElement(); 30 | element.click(); 31 | verify(originWebElement, times(1)).click(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /atlas-webdriver/src/test/java/io/qameta/atlas/webdriver/testdata/ObjectFactory.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.webdriver.testdata; 2 | 3 | import io.qameta.atlas.webdriver.AtlasWebElement; 4 | import org.openqa.selenium.WebDriver; 5 | import org.openqa.selenium.WebElement; 6 | import org.openqa.selenium.interactions.Locatable; 7 | 8 | import static org.mockito.Mockito.mock; 9 | import static org.mockito.Mockito.withSettings; 10 | 11 | public final class ObjectFactory { 12 | 13 | private ObjectFactory() { 14 | } 15 | 16 | public static WebDriver mockWebDriver() { 17 | return mock(WebDriver.class, withSettings()); 18 | } 19 | 20 | public static WebElement mockWebElement() { 21 | return mock(WebElement.class, withSettings().extraInterfaces(Locatable.class)); 22 | } 23 | 24 | public static AtlasWebElement mockAtlasWebElement() { 25 | return mock(AtlasWebElement.class, withSettings()); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | mavenLocal() 7 | } 8 | 9 | } 10 | 11 | val gradleScriptDir by extra("${rootProject.projectDir}/gradle") 12 | 13 | tasks.withType(Wrapper::class) { 14 | gradleVersion = "7.5.1" 15 | } 16 | 17 | plugins { 18 | java 19 | signing 20 | `java-library` 21 | `maven-publish` 22 | id("ru.vyarus.quality") version "4.9.0" 23 | id("io.spring.dependency-management") version "1.1.0" 24 | id("io.github.gradle-nexus.publish-plugin") version "1.3.0" 25 | } 26 | 27 | configure(listOf(rootProject)) { 28 | description = "Atlas" 29 | group = "io.qameta.atlas" 30 | } 31 | 32 | nexusPublishing { 33 | repositories { 34 | sonatype() 35 | } 36 | } 37 | 38 | configure(subprojects) { 39 | group = "io.qameta.atlas" 40 | version = version 41 | 42 | apply(plugin = "java") 43 | apply(plugin = "signing") 44 | apply(plugin = "maven-publish") 45 | apply(plugin = "java-library") 46 | apply(plugin = "ru.vyarus.quality") 47 | apply(plugin = "io.spring.dependency-management") 48 | 49 | java { 50 | sourceCompatibility = JavaVersion.VERSION_1_8 51 | targetCompatibility = JavaVersion.VERSION_1_8 52 | 53 | withJavadocJar() 54 | withSourcesJar() 55 | } 56 | 57 | tasks.compileJava { 58 | options.encoding = "UTF-8" 59 | } 60 | 61 | tasks.withType(Javadoc::class) { 62 | (options as StandardJavadocDocletOptions).apply { 63 | addStringOption("Xdoclint:none", "-quiet") 64 | } 65 | } 66 | 67 | configure { 68 | dependencies { 69 | dependency("org.apache.commons:commons-lang3:3.12.0") 70 | 71 | dependency("org.seleniumhq.selenium:selenium-java:4.8.3") 72 | dependency("io.appium:java-client:8.3.0") 73 | dependency("io.github.bonigarcia:webdrivermanager:5.3.2") 74 | dependency("ru.yandex.qatools.matchers:webdriver-matchers:1.4.1") 75 | dependency("org.awaitility:awaitility:3.1.2") 76 | 77 | dependency("org.slf4j:slf4j-api:2.0.7") 78 | dependency("org.slf4j:slf4j-simple:2.0.7") 79 | 80 | dependency("org.hamcrest:hamcrest-all:1.3") 81 | dependency("org.assertj:assertj-core:3.24.2") 82 | dependency("org.mockito:mockito-core:4.8.0") 83 | dependency("junit:junit:4.13.2") 84 | } 85 | } 86 | 87 | publishing { 88 | publications { 89 | create("maven") { 90 | suppressAllPomMetadataWarnings() 91 | versionMapping { 92 | allVariants { 93 | fromResolutionResult() 94 | } 95 | } 96 | pom { 97 | name.set(project.name) 98 | description.set("Module ${project.name} of Atlas.") 99 | url.set("https://github.com/qameta/atlas") 100 | licenses { 101 | license { 102 | name.set("The Apache License, Version 2.0") 103 | url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") 104 | } 105 | } 106 | developers { 107 | developer { 108 | id.set("eroshenkoam") 109 | name.set("Artem Eroshenko") 110 | email.set("artem.eroshenko@qameta.io") 111 | } 112 | } 113 | scm { 114 | developerConnection.set("scm:git:git://github.com/qameta/atlas") 115 | connection.set("scm:git:git://github.com/qameta/atlas") 116 | url.set("https://github.com/qameta/atlas") 117 | } 118 | issueManagement { 119 | system.set("GitHub Issues") 120 | url.set("https://github.com/qameta/atlas/issues") 121 | } 122 | } 123 | } 124 | } 125 | } 126 | 127 | signing { 128 | sign(publishing.publications["maven"]) 129 | } 130 | 131 | tasks.withType().configureEach { 132 | onlyIf { !project.version.toString().endsWith("-SNAPSHOT") } 133 | } 134 | 135 | publishing.publications.named("maven") { 136 | pom { 137 | from(components["java"]) 138 | } 139 | } 140 | 141 | repositories { 142 | mavenLocal() 143 | mavenCentral() 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=1.9-SNAPSHOT 2 | org.gradle.daemon=true 3 | org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/bintray.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | 3 | bintray { 4 | user = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') 5 | key = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') 6 | 7 | configurations = ['archives'] 8 | 9 | publish = true 10 | pkg { 11 | userOrg = 'qameta' 12 | repo = 'maven' 13 | name = 'atlas' 14 | desc = 'Atlas Framework' 15 | websiteUrl = 'https://github.com/qameta/atlas' 16 | issueTrackerUrl = 'https://github.com/qameta/atlas' 17 | vcsUrl = 'https://github.com/qameta/atlas.git' 18 | licenses = ['Apache-2.0'] 19 | 20 | githubRepo = 'qameta/atlas' 21 | githubReleaseNotesFile = 'README.adoc' 22 | 23 | version { 24 | name = project.version 25 | released = new Date() 26 | gpg { 27 | sign = true 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /gradle/maven.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven' 2 | 3 | install { 4 | repositories.mavenInstaller { 5 | customizePom(pom, project) 6 | } 7 | } 8 | 9 | def customizePom(pom, gradleProject) { 10 | pom.whenConfigured { generatedPom -> 11 | // eliminate testng-scoped dependencies (no need in maven central poms) 12 | generatedPom.dependencies.removeAll { dep -> 13 | dep.scope == "test" 14 | } 15 | 16 | // sort to make pom dependencies order consistent to ease comparison of older poms 17 | generatedPom.dependencies = generatedPom.dependencies.sort { dep -> 18 | "$dep.scope:$dep.groupId:$dep.artifactId" 19 | } 20 | 21 | // add all items necessary for maven central publication 22 | generatedPom.project { 23 | name = gradleProject.description 24 | description = gradleProject.description 25 | url = "https://github.com/qameta/atlas" 26 | organization { 27 | name = "Qameta IO" 28 | url = "https://qameta.io" 29 | } 30 | licenses { 31 | license { 32 | name = 'The Apache Software License, Version 2.0' 33 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 34 | distribution = 'repo' 35 | } 36 | } 37 | scm { 38 | url = 'https://github.com/qameta/atlas' 39 | connection = 'scm:git:git://github.com/qameta/atlas' 40 | developerConnection = 'scm:git:git://github.com/qameta/atlas' 41 | } 42 | developers { 43 | developer { 44 | id = 'baev' 45 | name = 'Dmitry Baev' 46 | email = 'baev@qameta.io' 47 | } 48 | developer { 49 | id = 'eroshenkoam' 50 | name = 'Artem Eroshenko' 51 | email = 'eroshenkoam@qameta.io' 52 | } 53 | } 54 | issueManagement { 55 | system = 'Github Issues' 56 | url = 'https://github.com/qameta/atlas/issues' 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /gradle/release.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'net.researchgate.release' 2 | 3 | release { 4 | tagTemplate = '${version}' 5 | } 6 | 7 | afterReleaseBuild.dependsOn bintrayUpload 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qameta/atlas/b38decf9f8c53b20dd61a3fbe56f478fb66c8edb/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /samples/github/build.gradle: -------------------------------------------------------------------------------- 1 | description = 'Atlas Github Sample' 2 | 3 | quality { 4 | enabled = false 5 | } 6 | 7 | repositories { 8 | maven { url 'https://jitpack.io' } 9 | } 10 | 11 | dependencies { 12 | implementation project(':atlas-core') 13 | implementation project(':atlas-webdriver') 14 | implementation project(':atlas-appium') 15 | 16 | implementation 'org.aeonbits.owner:owner-java8:1.0.12' 17 | implementation 'org.seleniumhq.selenium:selenium-java:4.8.3' 18 | 19 | testImplementation 'ru.yandex.qatools.matchers:webdriver-matchers:1.4.1' 20 | testImplementation 'ru.yandex.qatools.matchers:matcher-decorators:1.4.1' 21 | testImplementation 'io.github.bonigarcia:webdrivermanager:5.3.2' 22 | testImplementation 'com.github.detro:ghostdriver:2.1.0' 23 | testImplementation 'junit:junit:4.13.2' 24 | testImplementation 'io.appium:java-client:8.3.0' 25 | testImplementation 'org.zeroturnaround:zt-zip:1.15' 26 | } 27 | -------------------------------------------------------------------------------- /samples/github/src/main/java/io/qameta/atlas/github/mobile/config/MobileConfig.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github.mobile.config; 2 | 3 | import org.aeonbits.owner.Config; 4 | import org.aeonbits.owner.Config.Sources; 5 | import org.aeonbits.owner.Converter; 6 | 7 | import java.io.File; 8 | import java.lang.reflect.Method; 9 | 10 | 11 | /** 12 | * Read props from mobile.properties. 13 | */ 14 | @Sources("classpath:mobile.properties") 15 | public interface MobileConfig extends Config { 16 | 17 | @DefaultValue("http://127.0.0.1:4723/wd/hub") 18 | @Key("appium.url") 19 | String url(); 20 | 21 | @Key("appium.capability.platformName") 22 | String platformName(); 23 | 24 | @Key("appium.capability.newCommandTimeout") 25 | Integer newCommandTimeout(); 26 | 27 | @Key("android.capability.deviceName") 28 | String deviceName(); 29 | 30 | @Key("android.capability.platformVersion") 31 | String platformVersion(); 32 | 33 | @Key("android.capability.appPackage") 34 | String appPackage(); 35 | 36 | @Key("android.capability.appActivity") 37 | String appActivity(); 38 | 39 | @Key("android.capability.unicodeKeyboard") 40 | Boolean unicodeKeyboard(); 41 | 42 | @Key("android.capability.resetKeyboard") 43 | Boolean resetKeyboard(); 44 | 45 | @Key("android.capability.automationName") 46 | String automationName(); 47 | 48 | @ConverterClass(FileConverter.class) 49 | @Key("android.capability.apk") 50 | File apkFile(); 51 | 52 | @Key("ios.capability.deviceName") 53 | String deviceIOSName(); 54 | 55 | @Key("ios.capability.platformVersion") 56 | String platformIOSVersion(); 57 | 58 | @ConverterClass(FileConverter.class) 59 | @Key("ios.capability.app") 60 | File appFile(); 61 | 62 | /** 63 | * Converter to {@link File}. 64 | */ 65 | class FileConverter implements Converter { 66 | @Override 67 | public File convert(Method method, String name) { 68 | return new File("src/test/resources/mobile-applications/".concat(name)).getAbsoluteFile(); 69 | } 70 | } 71 | } 72 | 73 | 74 | -------------------------------------------------------------------------------- /samples/github/src/main/java/io/qameta/atlas/github/mobile/page/ArticleScreen.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github.mobile.page; 2 | 3 | import io.qameta.atlas.appium.AtlasMobileElement; 4 | import io.qameta.atlas.appium.Screen; 5 | import io.qameta.atlas.appium.annotations.AndroidFindBy; 6 | 7 | /** 8 | * Article screen of mobile application. 9 | */ 10 | public interface ArticleScreen extends Screen { 11 | 12 | @AndroidFindBy(id = "org.wikipedia:id/view_page_title_text") 13 | AtlasMobileElement articleTitle(); 14 | } 15 | -------------------------------------------------------------------------------- /samples/github/src/main/java/io/qameta/atlas/github/mobile/page/MainScreen.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github.mobile.page; 2 | 3 | import io.qameta.atlas.appium.AtlasMobileElement; 4 | import io.qameta.atlas.appium.Screen; 5 | import io.qameta.atlas.appium.annotations.AndroidFindBy; 6 | import io.qameta.atlas.appium.annotations.IOSFindBy; 7 | import io.qameta.atlas.core.api.Retry; 8 | import io.qameta.atlas.webdriver.extension.Param; 9 | 10 | /** 11 | * Main screen of mobile application. 12 | */ 13 | public interface MainScreen extends Screen { 14 | 15 | @Retry(timeout = 20_000L) 16 | @IOSFindBy(xpath = "//XCUIElementTypeSearchField[@name='Search Wikipedia']") 17 | @AndroidFindBy(xpath = "//*[contains(@text, 'Search Wikipedia')]") 18 | AtlasMobileElement searchWikipedia(); 19 | 20 | @Retry 21 | @IOSFindBy(id = "{{ value }}") 22 | AtlasMobileElement button(@Param("value") String value); 23 | } 24 | -------------------------------------------------------------------------------- /samples/github/src/main/java/io/qameta/atlas/github/mobile/page/SearchScreen.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github.mobile.page; 2 | 3 | import io.qameta.atlas.appium.AtlasMobileElement; 4 | import io.qameta.atlas.appium.Screen; 5 | import io.qameta.atlas.appium.annotations.AndroidFindBy; 6 | import io.qameta.atlas.appium.annotations.IOSFindBy; 7 | import io.qameta.atlas.core.api.Retry; 8 | import io.qameta.atlas.webdriver.extension.Param; 9 | 10 | /** 11 | * Search screen of mobile application. 12 | */ 13 | public interface SearchScreen extends Screen { 14 | 15 | @Retry(timeout = 20_000L) 16 | @AndroidFindBy(xpath = "//*[@resource-id='org.wikipedia:id/page_list_item_container']" 17 | + "//*[contains(@text, '{{ value }}')]") 18 | @IOSFindBy(xpath = "//XCUIElementTypeLink[contains(@name, '{{ value }}')]") 19 | AtlasMobileElement item(@Param("value") String value); 20 | 21 | @IOSFindBy(xpath = "//XCUIElementTypeApplication[@name='Wikipedia']//XCUIElementTypeSearchField") 22 | @AndroidFindBy(xpath = "//*[contains(@text, 'Search...')]") 23 | AtlasMobileElement search(); 24 | 25 | @AndroidFindBy(id = "search_close_btn") 26 | AtlasMobileElement close(); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /samples/github/src/main/java/io/qameta/atlas/github/web/element/Header.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github.web.element; 2 | 3 | 4 | import io.qameta.atlas.webdriver.AtlasWebElement; 5 | import io.qameta.atlas.webdriver.extension.FindBy; 6 | 7 | /** 8 | * Header of web page. 9 | */ 10 | public interface Header extends AtlasWebElement { 11 | 12 | @FindBy(".//input[contains(@class,'header-search-input')]") 13 | AtlasWebElement searchInput(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /samples/github/src/main/java/io/qameta/atlas/github/web/element/RepositoryCard.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github.web.element; 2 | 3 | import io.qameta.atlas.webdriver.AtlasWebElement; 4 | import io.qameta.atlas.webdriver.extension.FindBy; 5 | 6 | /** 7 | * Repository card. 8 | */ 9 | public interface RepositoryCard extends AtlasWebElement { 10 | 11 | @FindBy(".//h3") 12 | AtlasWebElement title(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /samples/github/src/main/java/io/qameta/atlas/github/web/layout/WithHeader.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github.web.layout; 2 | 3 | import io.qameta.atlas.webdriver.extension.FindBy; 4 | import io.qameta.atlas.github.web.element.Header; 5 | 6 | /** 7 | * Using header, when need it. 8 | */ 9 | public interface WithHeader { 10 | 11 | @FindBy("//header[contains(@class,'Header')]") 12 | Header header(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /samples/github/src/main/java/io/qameta/atlas/github/web/page/ContributorsPage.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github.web.page; 2 | 3 | import io.qameta.atlas.github.web.element.RepositoryCard; 4 | import io.qameta.atlas.github.web.layout.WithHeader; 5 | import io.qameta.atlas.webdriver.ElementsCollection; 6 | import io.qameta.atlas.webdriver.WebPage; 7 | import io.qameta.atlas.webdriver.extension.FindBy; 8 | 9 | /** 10 | * Contributors page. 11 | */ 12 | public interface ContributorsPage extends WebPage, WithHeader { 13 | 14 | @FindBy(".//ol[contains(@class, 'contrib-data')]//li[contains(@class, 'contrib-person')]") 15 | ElementsCollection hovercards(); 16 | } 17 | -------------------------------------------------------------------------------- /samples/github/src/main/java/io/qameta/atlas/github/web/page/MainPage.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github.web.page; 2 | 3 | import io.qameta.atlas.webdriver.AtlasWebElement; 4 | import io.qameta.atlas.webdriver.WebPage; 5 | import io.qameta.atlas.github.web.layout.WithHeader; 6 | import io.qameta.atlas.webdriver.extension.FindBy; 7 | 8 | /** 9 | * Main page of site. 10 | */ 11 | public interface MainPage extends WebPage, WithHeader { 12 | 13 | @FindBy("//a[contains(text(), 'Or start a free trial of Enterprise Server')]") 14 | AtlasWebElement trial(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /samples/github/src/main/java/io/qameta/atlas/github/web/page/ProjectPage.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github.web.page; 2 | 3 | import io.qameta.atlas.github.web.layout.WithHeader; 4 | import io.qameta.atlas.webdriver.AtlasWebElement; 5 | import io.qameta.atlas.webdriver.WebPage; 6 | import io.qameta.atlas.webdriver.extension.FindBy; 7 | 8 | /** 9 | * Project page. 10 | */ 11 | public interface ProjectPage extends WebPage, WithHeader { 12 | 13 | @FindBy("//a[contains(.,'contributors')]") 14 | AtlasWebElement contributors(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /samples/github/src/main/java/io/qameta/atlas/github/web/page/SearchPage.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github.web.page; 2 | 3 | import io.qameta.atlas.webdriver.ElementsCollection; 4 | import io.qameta.atlas.webdriver.WebPage; 5 | import io.qameta.atlas.webdriver.extension.FindBy; 6 | import io.qameta.atlas.github.web.element.RepositoryCard; 7 | import io.qameta.atlas.github.web.layout.WithHeader; 8 | 9 | /** 10 | * Search page. 11 | */ 12 | public interface SearchPage extends WebPage, WithHeader { 13 | 14 | @FindBy(".//ul[contains(@class, 'repo-list')]//li[contains(@class, 'repo-list-item')]") 15 | ElementsCollection repositories(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /samples/github/src/main/java/io/qameta/atlas/github/web/site/GitHubSite.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github.web.site; 2 | 3 | import io.qameta.atlas.github.web.page.ContributorsPage; 4 | import io.qameta.atlas.github.web.page.MainPage; 5 | import io.qameta.atlas.github.web.page.ProjectPage; 6 | import io.qameta.atlas.github.web.page.SearchPage; 7 | import io.qameta.atlas.webdriver.WebSite; 8 | import io.qameta.atlas.webdriver.extension.*; 9 | 10 | /** 11 | * Point of testing WebSite. 12 | */ 13 | public interface GitHubSite extends WebSite { 14 | 15 | @Page 16 | MainPage onMainPage(); 17 | 18 | @Page(url = "search") 19 | SearchPage onSearchPage(@Query("q") String value); 20 | 21 | @Page(url = "{profile}/{project}/tree/master/") 22 | ProjectPage onProjectPage(@Path("profile") String profile, @Path("project") String project); 23 | 24 | @Page 25 | ContributorsPage onContributorsPage(); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /samples/github/src/test/java/io/qameta/atlas/github/FindRepositoryByNameTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github; 2 | 3 | import io.github.bonigarcia.wdm.WebDriverManager; 4 | import io.qameta.atlas.core.Atlas; 5 | import io.qameta.atlas.github.web.page.MainPage; 6 | import io.qameta.atlas.github.web.page.SearchPage; 7 | import io.qameta.atlas.webdriver.WebDriverConfiguration; 8 | import io.qameta.atlas.webdriver.WebPage; 9 | import org.junit.After; 10 | import org.junit.Before; 11 | import org.junit.Ignore; 12 | import org.junit.Test; 13 | import org.openqa.selenium.JavascriptExecutor; 14 | import org.openqa.selenium.WebDriver; 15 | import org.openqa.selenium.chrome.ChromeDriver; 16 | 17 | import static org.hamcrest.CoreMatchers.containsString; 18 | import static org.hamcrest.CoreMatchers.is; 19 | import static org.hamcrest.MatcherAssert.assertThat; 20 | import static org.hamcrest.Matchers.hasSize; 21 | import static ru.yandex.qatools.matchers.decorators.MatcherDecorators.should; 22 | import static ru.yandex.qatools.matchers.webdriver.driver.HasTextMatcher.textOnCurrentPage; 23 | 24 | /** 25 | * Demo of simple test. Using atlas-webdriver. 26 | */ 27 | public class FindRepositoryByNameTest { 28 | 29 | private WebDriver driver; 30 | 31 | private Atlas atlas; 32 | 33 | @Before 34 | public void startDriver() { 35 | WebDriverManager.chromedriver().setup(); 36 | driver = new ChromeDriver(); 37 | atlas = new Atlas(new WebDriverConfiguration(driver)); 38 | } 39 | 40 | @Test 41 | @Ignore 42 | public void simpleTest() { 43 | onMainPage().open("https://github.com"); 44 | onMainPage().header().searchInput().sendKeys("Atlas"); 45 | onMainPage().header().searchInput().submit(); 46 | onSearchPage().repositories().waitUntil(hasSize(10)); 47 | } 48 | 49 | @Test 50 | @Ignore 51 | public void simpleTestWithJS() { 52 | JavascriptExecutor js = (JavascriptExecutor) driver; 53 | onMainPage().open("https://github.com"); 54 | js.executeScript("arguments[0].click();", onMainPage().trial()); 55 | assertThat(driver, should(textOnCurrentPage(is(containsString("Start your 45-day free trial"))))); 56 | } 57 | 58 | @Test 59 | @Ignore 60 | public void secondTestWithJS() { 61 | onMainPage().open("https://github.com"); 62 | onMainPage().trial().executeScript("arguments[0].click();"); 63 | assertThat(driver, should(textOnCurrentPage(is(containsString("Start your 45-day free trial"))))); 64 | } 65 | 66 | @After 67 | public void stopDriver() { 68 | this.driver.quit(); 69 | } 70 | 71 | private MainPage onMainPage() { 72 | return onPage(MainPage.class); 73 | } 74 | 75 | private SearchPage onSearchPage() { 76 | return onPage(SearchPage.class); 77 | } 78 | 79 | private T onPage(Class page) { 80 | return atlas.create(driver, page); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /samples/github/src/test/java/io/qameta/atlas/github/WebSiteTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github; 2 | 3 | import io.github.bonigarcia.wdm.WebDriverManager; 4 | import io.qameta.atlas.core.Atlas; 5 | import io.qameta.atlas.github.web.element.RepositoryCard; 6 | import io.qameta.atlas.github.web.site.GitHubSite; 7 | import io.qameta.atlas.webdriver.AtlasWebElement; 8 | import io.qameta.atlas.webdriver.ElementsCollection; 9 | import io.qameta.atlas.webdriver.WebDriverConfiguration; 10 | import io.qameta.atlas.webdriver.extension.PageExtension; 11 | import org.junit.After; 12 | import org.junit.Before; 13 | import org.junit.Ignore; 14 | import org.junit.Test; 15 | import org.openqa.selenium.WebDriver; 16 | import org.openqa.selenium.chrome.ChromeDriver; 17 | 18 | import static org.hamcrest.Matchers.*; 19 | import static ru.yandex.qatools.matchers.webdriver.TextMatcher.text; 20 | 21 | /** 22 | * Demo of simple tests with WebSite implementation. 23 | * This feature allow you to make one point of your system under tests (website). 24 | * In additional you can open any Page through domain URL. 25 | * With WebSite implementation look to 26 | *

27 | * {@link io.qameta.atlas.webdriver.extension.Page}, 28 | * {@link io.qameta.atlas.webdriver.extension.PageExtension}, {@link io.qameta.atlas.webdriver.extension.Path}, 29 | * {@link io.qameta.atlas.webdriver.extension.Query}, {@link io.qameta.atlas.webdriver.extension.QueryMap}, 30 | * {@link io.qameta.atlas.webdriver.extension.Page}, {@link PageExtension}. 31 | */ 32 | public class WebSiteTest { 33 | 34 | private Atlas atlas; 35 | private WebDriver driver; 36 | 37 | @Before 38 | public void startDriver() { 39 | WebDriverManager.chromedriver().setup(); 40 | driver = new ChromeDriver(); 41 | atlas = new Atlas(new WebDriverConfiguration(driver, "https://github.com")); 42 | } 43 | 44 | @Ignore 45 | @Test 46 | public void simpleWebSiteTest() { 47 | onSite().onMainPage().open(); 48 | onSite().onMainPage().header().searchInput().sendKeys("Atlas"); 49 | onSite().onMainPage().header().searchInput().submit(); 50 | onSite().onSearchPage("Junit 5").repositories().waitUntil(hasSize(10)); 51 | } 52 | 53 | @Ignore 54 | @Test 55 | public void baseUriWebSiteTest() { 56 | onSite().onSearchPage("Junit 5").repositories().waitUntil(hasSize(10)) 57 | .should(everyItem(text(containsString("junit")))); 58 | } 59 | 60 | @Ignore 61 | @Test 62 | public void usePathWebSiteTest() { 63 | onSite().onProjectPage("qameta", "atlas").contributors().click(); 64 | onSite().onContributorsPage().hovercards().waitUntil(hasSize(4)); 65 | } 66 | 67 | @Ignore 68 | @Test 69 | public void shouldExtractRepositoriesInfo() { 70 | ElementsCollection nameOfRepositories = onSite().onSearchPage("Junit 5").repositories() 71 | .extract(RepositoryCard::title) 72 | .extract(AtlasWebElement::getText); 73 | nameOfRepositories.should(everyItem(anyOf(containsString("junit5"), 74 | containsString("jupiter")))); 75 | } 76 | 77 | 78 | private GitHubSite onSite() { 79 | return atlas.create(driver, GitHubSite.class); 80 | } 81 | 82 | @After 83 | public void stopDriver() { 84 | driver.quit(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /samples/github/src/test/java/io/qameta/atlas/github/WikipediaTest.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github; 2 | 3 | import io.appium.java_client.AppiumDriver; 4 | import io.appium.java_client.android.AndroidDriver; 5 | import io.appium.java_client.ios.IOSDriver; 6 | import io.qameta.atlas.appium.AppiumDriverConfiguration; 7 | import io.qameta.atlas.appium.Screen; 8 | import io.qameta.atlas.core.Atlas; 9 | import io.qameta.atlas.github.mobile.config.MobileConfig; 10 | import io.qameta.atlas.github.mobile.page.ArticleScreen; 11 | import io.qameta.atlas.github.mobile.page.MainScreen; 12 | import io.qameta.atlas.github.mobile.page.SearchScreen; 13 | import io.qameta.atlas.github.rules.UnZipResource; 14 | import org.aeonbits.owner.ConfigFactory; 15 | import org.junit.*; 16 | import org.openqa.selenium.remote.DesiredCapabilities; 17 | 18 | import java.net.MalformedURLException; 19 | import java.net.URL; 20 | 21 | import static org.hamcrest.Matchers.allOf; 22 | import static ru.yandex.qatools.matchers.webdriver.DisplayedMatcher.displayed; 23 | import static ru.yandex.qatools.matchers.webdriver.TextMatcher.text; 24 | 25 | /** 26 | * Demo of simple tests for IOS and Android platform. Using atlas-mobile. 27 | */ 28 | public class WikipediaTest { 29 | 30 | private AppiumDriver driver; 31 | private Atlas atlas; 32 | private MobileConfig config = ConfigFactory.create(MobileConfig.class); 33 | private static final String NEXT = "Next"; 34 | 35 | @ClassRule 36 | public static final UnZipResource unZipResource = new UnZipResource(); 37 | 38 | @Before 39 | public void setUp() throws MalformedURLException { 40 | final String platform = config.platformName().trim(); 41 | final URL url = new URL(config.url()); 42 | final DesiredCapabilities desiredCapabilities = new DesiredCapabilities(); 43 | 44 | if ("android".equalsIgnoreCase(platform)) { 45 | desiredCapabilities.setCapability("platformName", platform); 46 | desiredCapabilities.setCapability("deviceName", config.deviceName()); 47 | desiredCapabilities.setCapability("platformVersion", config.platformVersion()); 48 | desiredCapabilities.setCapability("appPackage", config.appPackage()); 49 | desiredCapabilities.setCapability("appActivity", config.appActivity()); 50 | desiredCapabilities.setCapability("unicodeKeyboard", config.unicodeKeyboard()); 51 | desiredCapabilities.setCapability("resetKeyboard", config.resetKeyboard()); 52 | desiredCapabilities.setCapability("automationName", config.automationName()); 53 | desiredCapabilities.setCapability("newCommandTimeout", config.newCommandTimeout()); 54 | desiredCapabilities.setCapability("app", config.apkFile()); 55 | driver = new AndroidDriver(url, desiredCapabilities); 56 | } else if ("ios".equalsIgnoreCase(platform)) { 57 | desiredCapabilities.setCapability("platformName", platform); 58 | desiredCapabilities.setCapability("deviceName", config.deviceIOSName()); 59 | desiredCapabilities.setCapability("platformVersion", config.platformIOSVersion()); 60 | desiredCapabilities.setCapability("newCommandTimeout", config.newCommandTimeout()); 61 | desiredCapabilities.setCapability("app", config.appFile()); 62 | driver = new IOSDriver(url, desiredCapabilities); 63 | } else { 64 | throw new UnsupportedOperationException("Set valid driver"); 65 | } 66 | atlas = new Atlas(new AppiumDriverConfiguration(driver)); 67 | 68 | } 69 | 70 | @Ignore 71 | @Test 72 | public void androidSimpleTest() { 73 | onMainScreen().searchWikipedia().click(); 74 | onSearchScreen().search().sendKeys("Java"); 75 | } 76 | 77 | @Ignore 78 | @Test 79 | public void iosSimpleTest() { 80 | onMainScreen().button(NEXT).click(); 81 | onMainScreen().button(NEXT).click(); 82 | onMainScreen().button(NEXT).click(); 83 | onMainScreen().button("Get started").click(); 84 | onMainScreen().searchWikipedia().click(); 85 | } 86 | 87 | @Ignore 88 | @Test 89 | public void androidSwipeToDown() { 90 | onMainScreen().searchWikipedia().click(); 91 | onSearchScreen().search().sendKeys("Atlas"); 92 | onSearchScreen().item("Atlas LV-3B").swipeDownOn().click(); 93 | onArticleScreen().articleTitle().should(allOf(displayed(), text("Atlas LV-3B"))); 94 | } 95 | 96 | @Ignore 97 | @Test 98 | public void androidSwipeToUp() { 99 | onMainScreen().searchWikipedia().click(); 100 | onSearchScreen().search().sendKeys("Java"); 101 | onSearchScreen().item("JaVale McGee").swipeDownOn(); 102 | onSearchScreen().item("Java (programming language)").swipeUpOn().click(); 103 | } 104 | 105 | @After 106 | public void stopDriver() { 107 | this.driver.quit(); 108 | } 109 | 110 | private ArticleScreen onArticleScreen() { 111 | return onPage(ArticleScreen.class); 112 | } 113 | 114 | private MainScreen onMainScreen() { 115 | return onPage(MainScreen.class); 116 | } 117 | 118 | private SearchScreen onSearchScreen() { 119 | return onPage(SearchScreen.class); 120 | } 121 | 122 | private T onPage(Class page) { 123 | return atlas.create(driver, page); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /samples/github/src/test/java/io/qameta/atlas/github/rules/UnZipResource.java: -------------------------------------------------------------------------------- 1 | package io.qameta.atlas.github.rules; 2 | 3 | import org.junit.rules.ExternalResource; 4 | import org.zeroturnaround.zip.ZipUtil; 5 | 6 | import java.io.File; 7 | import java.nio.file.Paths; 8 | 9 | import static java.nio.file.Files.exists; 10 | 11 | public class UnZipResource extends ExternalResource { 12 | 13 | private static final String PATH_TO_ZIP_APPS = "src/test/resources/mobile-applications.zip"; 14 | private static final String IOS = "src/test/resources/mobile-applications/Wikipedia.app"; 15 | private static final String ANDROID = "src/test/resources/mobile-applications/org.wikipedia.apk"; 16 | 17 | @Override 18 | protected void before() { 19 | if (exists(Paths.get(IOS)) && exists(Paths.get(ANDROID))) { 20 | return; 21 | } 22 | ZipUtil.unpack(new File(PATH_TO_ZIP_APPS), new File("src/test/resources/")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/github/src/test/resources/mobile-applications.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qameta/atlas/b38decf9f8c53b20dd61a3fbe56f478fb66c8edb/samples/github/src/test/resources/mobile-applications.zip -------------------------------------------------------------------------------- /samples/github/src/test/resources/mobile.properties: -------------------------------------------------------------------------------- 1 | #parameters for mobile driver create 2 | appium.url = http://127.0.0.1:4723/wd/hub 3 | appium.capability.newCommandTimeout = 30000 4 | 5 | # AndroidTest appium capability 6 | appium.capability.platformName = android 7 | android.capability.deviceName = Nexus 6P API 27 8 | android.capability.platformVersion = 8.1 9 | android.capability.appPackage = org.wikipedia 10 | android.capability.appActivity = .main.MainActivity 11 | android.capability.unicodeKeyboard = true 12 | android.capability.resetKeyboard = true 13 | android.capability.automationName = UiAutomator2 14 | android.capability.apk = org.wikipedia.apk 15 | 16 | 17 | ## IOSTest appium capability 18 | #appium.capability.platformName = ios 19 | #ios.capability.deviceName = iPhone SE 20 | #ios.capability.platformVersion = 11.2 21 | #ios.capability.app = Wikipedia.app 22 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'atlas' 2 | include 'atlas-core' 3 | include 'atlas-webdriver' 4 | include 'atlas-appium' 5 | 6 | def plugins = [ 7 | 'github' 8 | ] 9 | 10 | plugins.forEach({ 11 | include "samples/$it" 12 | project(":samples/$it").name = it 13 | }) --------------------------------------------------------------------------------