From 2de1588085fc9a7e6c2251545ad1c53a046354b2 Mon Sep 17 00:00:00 2001 From: Mark Gunlogson <31893232+noobgramming@users.noreply.github.com> Date: Tue, 4 Feb 2025 09:32:02 -0600 Subject: [PATCH 1/2] AUT-6940 move helpers into core OSS SDK and migrate to Appium v8 --- auto-sdk-java-helpers/pom.xml | 45 ++- .../helpers/allure/AllureDriverUtils.java | 85 +++++ .../auto/helpers/allure/AllureUtils.java | 52 +++ .../appenders/AllureLogsToStepAppender.java | 102 ++++++ .../auto/helpers/email/CommonMailClient.java | 306 ++++++++++++++++++ .../auto/helpers/email/GmailHelper.java | 267 +++++++++++++++ .../applause/auto/helpers/email/Protocol.java | 30 ++ .../auto/helpers/email/SearchCriteria.java | 31 ++ .../helpers/google/GoogleSheetParser.java | 207 ++++++++++++ .../google/GoogleSheetParserException.java | 25 ++ .../http/mapping/IRestObjectMapper.java | 25 ++ .../mapping/JacksonJSONRestObjectMapping.java | 40 +++ .../RestApiDefaultRestAssuredApiHelper.java | 48 +++ .../RestApiDefaultRestAssuredApiClient.java | 73 +++++ .../client/RestAssuredApiClient.java | 53 +++ .../helpers/jira/annotations/JiraDefect.java | 30 ++ .../auto/helpers/jira/annotations/JiraID.java | 30 ++ .../scanner/JiraAnnotationsScanner.java | 70 ++++ .../helpers/jira/clients/JiraXrayClient.java | 89 +++++ .../clients/modules/jira/JiraProjectAPI.java | 270 ++++++++++++++++ .../jira/clients/modules/jira/SearchAPI.java | 71 ++++ .../clients/modules/xray/ExecutionsAPI.java | 114 +++++++ .../clients/modules/xray/IterationsAPI.java | 69 ++++ .../jira/clients/modules/xray/StepsAPI.java | 225 +++++++++++++ .../jira/clients/modules/xray/TestrunAPI.java | 211 ++++++++++++ .../auto/helpers/jira/constants/Statuses.java | 32 ++ .../helpers/jira/constants/XrayEndpoints.java | 49 +++ .../auto/helpers/jira/dto/jql/Fields.java | 32 ++ .../auto/helpers/jira/dto/jql/Issues.java | 31 ++ .../jira/dto/jql/JqlFilteredResults.java | 32 ++ .../jira/dto/requestmappers/Fields.java | 33 ++ .../JiraCreateTicketRequest.java | 25 ++ .../jira/dto/requestmappers/JiraFields.java | 44 +++ .../dto/requestmappers/StepFieldsUpdate.java | 27 ++ .../StepIterationAttachment.java | 27 ++ .../jira/dto/requestmappers/XrayAddTo.java | 26 ++ .../responsemappers/AvailableIssueTypes.java | 29 ++ .../responsemappers/AvailableProjects.java | 30 ++ .../JiraCreateTicketResponse.java | 27 ++ .../responsemappers/XrayTestRunDetails.java | 47 +++ .../responsemappers/iteration/Iteration.java | 30 ++ .../iteration/IterationParameters.java | 28 ++ .../iteration/TestRunIteration.java | 34 ++ .../dto/responsemappers/steps/Action.java | 27 ++ .../responsemappers/steps/Attachments.java | 30 ++ .../dto/responsemappers/steps/Comment.java | 27 ++ .../jira/dto/responsemappers/steps/Data.java | 27 ++ .../dto/responsemappers/steps/Fields.java | 27 ++ .../jira/dto/responsemappers/steps/Step.java | 35 ++ .../jira/dto/responsemappers/steps/Steps.java | 28 ++ .../jira/dto/responsemappers/steps/Value.java | 27 ++ .../helpers/jira/dto/shared/Issuetype.java | 28 ++ .../auto/helpers/jira/dto/shared/Project.java | 25 ++ .../exceptions/JiraAnnotationException.java | 25 ++ .../JiraPropertiesFileException.java | 25 ++ .../UnidentifiedExecutionStatusException.java | 25 ++ .../auto/helpers/jira/helper/FilesHelper.java | 51 +++ .../jira/helper/ResponseValidator.java | 42 +++ .../helpers/jira/listeners/XrayListener.java | 129 ++++++++ .../jira/requestData/XrayRequestHeaders.java | 43 +++ .../jira/restclient/IJiraAppConfig.java | 32 ++ .../restclient/XrayRestAssuredClient.java | 66 ++++ .../helpers/mobile/MobileContextsUtils.java | 121 +++++++ .../helpers/mobile/MobileElementUtils.java | 133 ++++++++ .../auto/helpers/mobile/MobileUtils.java | 246 ++++++++++++++ .../deeplinks/MobileDeepLinksUtils.java | 114 +++++++ .../NativeMobileAppCommonDeeplink.java | 46 +++ .../SauceLabs/FileUploadingHelper.java | 143 ++++++++ .../helpers/testdata/TestDataProvider.java | 41 +++ .../testdata/yaml/YamlTestDataProvider.java | 50 +++ .../helpers/util/AwaitilityWaitUtils.java | 72 +++++ .../helpers/util/GenericObjectMapper.java | 25 ++ .../auto/helpers/util/RandomUtils.java | 65 ++++ .../auto/helpers/util/ThreadHelper.java | 37 +++ .../applause/auto/helpers/web/HtmlUtils.java | 63 ++++ .../auto/helpers/web/WebElementUtils.java | 305 +++++++++++++++++ .../applause/auto/helpers/web/WebUtils.java | 48 +++ pom.xml | 67 ++++ 78 files changed, 5445 insertions(+), 1 deletion(-) create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureDriverUtils.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureUtils.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/appenders/AllureLogsToStepAppender.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/CommonMailClient.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/GmailHelper.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/Protocol.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/SearchCriteria.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParser.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParserException.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/mapping/IRestObjectMapper.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/mapping/JacksonJSONRestObjectMapping.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/RestApiDefaultRestAssuredApiHelper.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestApiDefaultRestAssuredApiClient.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestAssuredApiClient.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/JiraDefect.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/JiraID.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/scanner/JiraAnnotationsScanner.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/JiraXrayClient.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/JiraProjectAPI.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/SearchAPI.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/ExecutionsAPI.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/IterationsAPI.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/StepsAPI.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/TestrunAPI.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/Statuses.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/XrayEndpoints.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Fields.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Issues.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/JqlFilteredResults.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/Fields.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraCreateTicketRequest.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraFields.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepFieldsUpdate.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepIterationAttachment.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/XrayAddTo.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableIssueTypes.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableProjects.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/JiraCreateTicketResponse.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/XrayTestRunDetails.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/Iteration.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/IterationParameters.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/TestRunIteration.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Action.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Attachments.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Comment.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Data.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Fields.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Step.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Steps.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Value.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Issuetype.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Project.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraAnnotationException.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraPropertiesFileException.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/UnidentifiedExecutionStatusException.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/FilesHelper.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/ResponseValidator.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/listeners/XrayListener.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/requestData/XrayRequestHeaders.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/restclient/IJiraAppConfig.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/restclient/XrayRestAssuredClient.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileContextsUtils.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileElementUtils.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileUtils.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/MobileDeepLinksUtils.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/NativeMobileAppCommonDeeplink.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/file_uploading/SauceLabs/FileUploadingHelper.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/TestDataProvider.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/yaml/YamlTestDataProvider.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/AwaitilityWaitUtils.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/GenericObjectMapper.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/RandomUtils.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/ThreadHelper.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/HtmlUtils.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebElementUtils.java create mode 100644 auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebUtils.java diff --git a/auto-sdk-java-helpers/pom.xml b/auto-sdk-java-helpers/pom.xml index 96e116f..2b92298 100644 --- a/auto-sdk-java-helpers/pom.xml +++ b/auto-sdk-java-helpers/pom.xml @@ -54,10 +54,53 @@ org.apache.commons commons-collections4 + + io.rest-assured + rest-assured + + + org.awaitility + awaitility + + + org.jsoup + jsoup + + + us.codecraft + xsoup + + + com.github.javafaker + javafaker + + + io.qameta.allure + allure-testng + + + org.json + json + + + io.github.yaml-path + yaml-path + + + com.google.api-client + google-api-client + + + com.google.apis + google-api-services-sheets + + + jakarta.mail + jakarta.mail-api + org.testng testng - test diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureDriverUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureDriverUtils.java new file mode 100644 index 0000000..87e5465 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureDriverUtils.java @@ -0,0 +1,85 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.allure; + +import io.qameta.allure.Allure; +import java.io.ByteArrayInputStream; +import java.util.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.TakesScreenshot; +import org.openqa.selenium.WebDriver; + +/** Some common allure utils (with attachments) */ +public class AllureDriverUtils { + + private static final Logger logger = LogManager.getLogger(AllureDriverUtils.class); + + /** + * Attach driver screenshot + * + * @param driver - automation driver + */ + public static void attachScreenshot(WebDriver driver) { + if (Objects.nonNull(driver)) { + logger.info("Taking screenshot on test failure"); + try { + Allure.addAttachment( + "Screenshot attachment", + "image/png", + new ByteArrayInputStream(((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES)), + "png"); + } catch (Exception e) { + logger.error("Error taking screenshot: " + e.getMessage()); + } + } + } + + /** + * Attach driver url + * + * @param driver - automation driver + */ + public static void attachCurrentURL(WebDriver driver) { + if (Objects.nonNull(driver)) { + logger.info("Taking current URL"); + try { + Allure.addAttachment("Current URL", "text/plain", driver.getCurrentUrl(), ".log"); + } catch (Exception e) { + logger.error("Error taking current URL: " + e.getMessage()); + } + } + } + + /** + * Attach driver page source + * + * @param driver - automation driver + */ + public static void attachCurrentPageSourceOnFailure(WebDriver driver) { + if (Objects.nonNull(driver)) { + logger.info("Taking page source"); + try { + Allure.addAttachment("Current page source", "text/plain", driver.getPageSource(), ".log"); + } catch (Exception e) { + logger.error("Error taking current page source: " + e.getMessage()); + } + } + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureUtils.java new file mode 100644 index 0000000..3aff49d --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureUtils.java @@ -0,0 +1,52 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.allure; + +import java.io.File; +import java.io.IOException; +import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class AllureUtils { + + private static final Logger logger = LogManager.getLogger(AllureUtils.class); + + /** + * Copy Allure categorisation .json file to report directory + * https://docs.qameta.io/allure/#_categories_2 + * + * @throws IOException + */ + public static void addAllureDefectsCategoriesConfiguration( + String allureReportPath, String allureDefectsCategorisationFilePath) { + try { + logger.info("Copy defects configs to categories.json file for Allure"); + File categoriesAllureFile = new File(allureReportPath + "/categories.json"); + if (!categoriesAllureFile.exists()) { + categoriesAllureFile.createNewFile(); + } + categoriesAllureFile.setReadable(true); + categoriesAllureFile.setWritable(true); + FileUtils.copyFile(new File(allureDefectsCategorisationFilePath), categoriesAllureFile); + + } catch (Exception e) { + logger.error("Error creating allure categories.json file " + e.getMessage()); + } + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/appenders/AllureLogsToStepAppender.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/appenders/AllureLogsToStepAppender.java new file mode 100644 index 0000000..518c1c5 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/appenders/AllureLogsToStepAppender.java @@ -0,0 +1,102 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.allure.appenders; + +import io.qameta.allure.Allure; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentSkipListSet; +import org.apache.logging.log4j.core.*; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.layout.PatternLayout; + +/** Log4j2 logs to Allure step definition appender */ +@Plugin( + name = "AllureLogsToStepAppender", + category = Core.CATEGORY_NAME, + elementType = Appender.ELEMENT_TYPE) +public class AllureLogsToStepAppender extends AbstractAppender { + + /** storage for filtered packages */ + private static Set filteredPackagesToAppendSet; + + protected AllureLogsToStepAppender( + String name, Filter filter, Layout layout, boolean ignoreExceptions) { + super(name, filter, layout, ignoreExceptions, null); + } + + /** + * Create log4j2 AllureLogsToStepAppender appender method + * + * @param name appender name + * @param layout appender layout + * @param filter appender filter + * @param filteredPackagesToAppend filter packages to append appender, should be provided as comma + * separated list + * @return + */ + @PluginFactory + public static AllureLogsToStepAppender createAppender( + @PluginAttribute("name") String name, + @PluginElement("Layout") Layout layout, + @PluginElement("Filter") final Filter filter, + @PluginAttribute("filteredPackagesToAppend") String filteredPackagesToAppend) { + if (Objects.isNull(name)) { + return null; + } + if (Objects.isNull(layout)) { + layout = PatternLayout.createDefaultLayout(); + } + if (Objects.nonNull(filteredPackagesToAppend)) { + filteredPackagesToAppendSet = + new ConcurrentSkipListSet<>(Arrays.asList(filteredPackagesToAppend.split(","))); + } + return new AllureLogsToStepAppender(name, filter, layout, true); + } + + /** + * log4j2 append method for Allure step appender + * + * @param event logging log4j2 intercepted event + */ + @Override + public void append(LogEvent event) { + if (isSourcePackageValidForAppender(event)) { + Allure.step(event.getMessage().getFormattedMessage()); + } + } + + private boolean isSourcePackageValidForAppender(LogEvent event) { + if (filteredPackagesToAppendSet.isEmpty()) { + return true; + } + String sourceClassName = event.getSource().getClassName(); + return filteredPackagesToAppendSet.stream() + .filter( + filteredPackagesToAppendSetItem -> + sourceClassName.contains(filteredPackagesToAppendSetItem)) + .findAny() + .isPresent(); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/CommonMailClient.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/CommonMailClient.java new file mode 100644 index 0000000..d373a9f --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/CommonMailClient.java @@ -0,0 +1,306 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.email; + +import jakarta.mail.*; +import jakarta.mail.Flags.Flag; +import jakarta.mail.search.FlagTerm; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** Provides some common methods between most email clients. */ +public abstract class CommonMailClient { + private String host; + private Properties props = new Properties(); + private Store store; + private String protocol; + private String userName; + private String password; + private static final Logger logger = LogManager.getLogger(CommonMailClient.class); + + /** + * Default constructor with params Example: username = some@email.com password = xxxx protocol = + * imaps host = imap.gmail.com + */ + public CommonMailClient(String userName, String password, Protocol protocol, String host) { + this.userName = userName.trim(); + this.password = password; + this.protocol = protocol.getValue(); + this.host = host.trim(); + store = setMailCredentials(); + } + + /** + * Extracts from a String using Regular expressions + * + * @param text The text to extract from + * @param regex The regular expression to use + * @return The extracted text after applying the regex + */ + public String extractWithRegexFromString(String text, String regex) { + if (text.isEmpty()) { + logger.info("Text is empty"); + return null; + } + logger.info("Extracting with regex = [" + regex + "..."); + Pattern pattern = Pattern.compile(regex, Pattern.DOTALL); + Matcher matcher = pattern.matcher(text); + + if (matcher.find()) { + logger.info("Extracted text is:: " + matcher.group(1).trim()); + return matcher.group(1).trim(); + } + throw new RuntimeException("No matches were found in the text."); + } + + /** + * input stream with properties for email client, protocol, username, password, host example of + * property file content: mail.store.protocol = imaps username = some@email.com password = xxxx + * host = imap.gmail.com (for example). + * + * @param resourceFile an InputStream that represents a .properties files that includes all the + * above-mentioned properties. + */ + public CommonMailClient(InputStream resourceFile) { + store = setMailCredentials(resourceFile); + } + + /** + * Initialize properties for current mailbox example setMailCredentials(new + * InputStream("/xxx/xxx/propertyFile")) + * + * @param resourceStream an InputStream that represents a .properties files that includes all the + * above-mentioned properties. + * @return + */ + private Store setMailCredentials(InputStream resourceStream) { + if (props.isEmpty()) { + setProperties(resourceStream); + } + Session session = Session.getDefaultInstance(props, null); + return getStore(session); + } + + /** + * Deletes all the read emails from the mentioned folder of the email account. + * + * @param emailFolder The email folder to use. + */ + public void emptyAllReadEmailsFromInbox(Folder emailFolder) { + deleteReadEmails(emailFolder); + } + + /** + * Marks all emails on the email account as read (it only does that for the 'Inbox' folder). + * + * @return A reference to the InboxFolder after marking all the emails in it as read. + */ + public Folder markAllEmailsAsRead() { + Folder inboxFolder = null; + try { + inboxFolder = openSelectedFolderWithRights("Inbox", Folder.READ_WRITE); + Message[] emails = getUnreadEmails(inboxFolder); + for (Message email : emails) { + email.setFlag(Flag.SEEN, true); + } + } catch (Exception ex) { + logger.info("An exception was thrown. Message = " + ex.getMessage()); + } + + return inboxFolder; + } + + /** + * Checks whether a certain email matches the provided search criteria or not. + * + * @param email The email object to check. + * @param criteria The search criteria to check against. + * @return True if the email is a match, false otherwise. + * @throws MessagingException Thrown in case an error happens when getting any details from the + * email. + */ + protected boolean doesEmailMatchCriteria(Message email, SearchCriteria criteria) + throws MessagingException { + if (criteria.getEmailSubject() != null && !criteria.getEmailSubject().isEmpty()) { + // check email subject + boolean subjectMatch = email.getSubject().matches(criteria.getEmailSubject()); + // return if false, otherwise continue + if (!subjectMatch) { + return false; + } + } + + if (criteria.getSentFrom() != null && !criteria.getSentFrom().isEmpty()) { + // check sentFrom + boolean sentFromMatch = + Arrays.stream(email.getFrom()) + .sequential() + .anyMatch(from -> from.toString().contains(criteria.getSentFrom().toLowerCase())); + // return if false, otherwise continue + if (!sentFromMatch) { + return false; + } + } + + if (criteria.getSentTo() != null && !criteria.getSentTo().isEmpty()) { + // check sentTo + // return if false, otherwise continue + return Arrays.stream(email.getAllRecipients()) + .sequential() + .anyMatch(to -> to.toString().contains(criteria.getSentTo().toLowerCase())); + } + + // email matches the whole criteria + return true; + } + + /** + * input stream with properties for email client, protocol, username, password, host example of + * property file content: mail.store.protocol = imaps username = some@email.com password = xxxx + * host = imap.gmail.com (for example). + * + * @param resourceStream an InputStream that represents a .properties files that includes all the + * above-mentioned properties. + * @return mailProperties + */ + private Properties setProperties(InputStream resourceStream) { + try { + props.load(resourceStream); + this.userName = props.getProperty("username"); + this.password = props.getProperty("password"); + this.protocol = props.getProperty("mail.store.protocol"); + host = props.getProperty("host"); + } catch (FileNotFoundException e) { + logger.error("FileNotFoundException on method setProperties\nMessage: {}", e.getMessage()); + } catch (IOException e1) { + logger.error("IOException on method setProperties"); + } + return props; + } + + /** + * @param folderName mail folder name example INBOX + * @param accessMode check Folder.to get which mode exists + * @return required Folder + */ + public Folder openSelectedFolderWithRights(String folderName, int accessMode) { + Folder inbox = null; + try { + inbox = store.getFolder(folderName); + } catch (MessagingException e1) { + e1.printStackTrace(); + } + try { + inbox.open(accessMode); + } catch (MessagingException e1) { + e1.printStackTrace(); + } + return inbox; + } + + /** + * Initialize properties for current mailbox. Username, password, protocol, and host should be set + * in advance + */ + private Store setMailCredentials() { + Session session = Session.getDefaultInstance(getProperties(), null); + return getStore(session); + } + + private Store getStore(Session session) { + Store store = null; + try { + store = session.getStore(protocol); + } catch (NoSuchProviderException e1) { + e1.printStackTrace(); + } + try { + store.connect(host, userName, password); + } catch (MessagingException e1) { + e1.printStackTrace(); + } + return store; + } + + /** + * Creating properties instance Username, password, protocol, and host should be set in advance + */ + private Properties getProperties() { + Properties properties = new Properties(); + + properties.put("username", userName); + properties.put("password", password); + properties.put("mail.store.protocol", protocol); + properties.put("host", host); + + return properties; + } + + /** + * Get array with unread emails. + * + * @param folder The email folder to check. + * @return unread emails for selected folder + */ + public Message[] getUnreadEmails(Folder folder) { + Message[] emails = new Message[0]; + try { + Flags seen = new Flags(Flags.Flag.SEEN); + FlagTerm unseenFlagTerm = new FlagTerm(seen, false); + emails = folder.search(unseenFlagTerm); + } catch (MessagingException e1) { + e1.printStackTrace(); + } + + return emails; + } + + /** + * Delete all read emails form selected folder. + * + * @param folder The email folder to check. + */ + public void deleteReadEmails(Folder folder) { + Message[] emails; + try { + Flags seen = new Flags(Flags.Flag.SEEN); + FlagTerm seenFlagTerm = new FlagTerm(seen, true); + emails = folder.search(seenFlagTerm); + folder.setFlags(emails, new Flags(Flags.Flag.DELETED), true); + } catch (MessagingException e1) { + e1.printStackTrace(); + } + } + + /** Close the current connection store. */ + public void closeConnection() { + try { + store.close(); + } catch (MessagingException e) { + logger.error("Error occurred when closing connection."); + e.printStackTrace(); + } + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/GmailHelper.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/GmailHelper.java new file mode 100644 index 0000000..24135ff --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/GmailHelper.java @@ -0,0 +1,267 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.email; + +import com.applause.auto.helpers.util.ThreadHelper; +import jakarta.mail.BodyPart; +import jakarta.mail.Flags.Flag; +import jakarta.mail.Folder; +import jakarta.mail.Message; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMultipart; +import java.io.IOException; +import java.util.ArrayList; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.remote.RemoteWebDriver; + +/** A concrete email client implementation for Gmail. */ +public class GmailHelper extends CommonMailClient { + + private static final Logger logger = LogManager.getLogger(GmailHelper.class); + private static final int TEN_MINUTES_TIMEOUT_WAIT_MILLI = 600000; + private static final int FIVE_SECOND_WAIT_MILLI = 5000; + + /** + * Default constructor with params. + * + *

PLEASE NOTE: + * + *

To make this work you should use gmail appPassword. + * + *

For more information how to set it, please check + * https://support.google.com/accounts/answer/185833?hl=en + * + *

Example: username = some@email.com password = xxxx + */ + public GmailHelper(String username, String appPassword) { + super(username.trim(), appPassword, Protocol.IMAP, "imap.gmail.com"); + } + + public GmailHelper(String username, String appPassword, Protocol protocol, String host) { + super(username, appPassword, protocol, host); + } + + /** Clears all read emails from the inbox folder of the email. */ + public void emptyEmailBox() { + Folder inboxFolder = markAllEmailsAsRead(); + emptyAllReadEmailsFromInbox(inboxFolder); + closeConnection(); + } + + /** + * Waits for a specific email to arrive. + * + * @param driver The currently active selenium WebDriver instance. It is kept alive as long as the + * email didn't arrive yet and the timeout value isn't reached. + * @param criteria The search criteria to use when searching for the email. + * @param checkOnlyUnreadEmails If true, then we would get fetch the unread emails. If false, * we + * would check all the emails in the folder (can be slow). + * @param markEmailAsSeen If true, the email would be marked as seen after checking it. + * @return A message array that contains all the emails that match the mentioned criteria. + */ + public Message[] waitForEmailToArrive( + WebDriver driver, + SearchCriteria criteria, + boolean checkOnlyUnreadEmails, + boolean markEmailAsSeen) { + return waitForEmailToArrive( + driver, criteria, checkOnlyUnreadEmails, markEmailAsSeen, TEN_MINUTES_TIMEOUT_WAIT_MILLI); + } + + /** + * Waits for a specific email to arrive. + * + * @param driver The currently active selenium WebDriver instance. It is kept alive as long as the + * email didn't arrive yet and the timeout value isn't reached. + * @param criteria The search criteria to use when searching for the email. + * @param checkOnlyUnreadEmails If true, then we would get fetch the unread emails. If false, * we + * would check all the emails in the folder (can be slow). + * @param markEmailAsSeen If true, the email would be marked as seen after checking it. + * @param timeOutInMillis The time to wait in milli-seconds before timing out and giving up to + * find the email. + * @return A message array that contains all the emails that match the mentioned criteria. + */ + public Message[] waitForEmailToArrive( + WebDriver driver, + SearchCriteria criteria, + boolean checkOnlyUnreadEmails, + boolean markEmailAsSeen, + long timeOutInMillis) { + logger.info("Waiting for email to arrive..."); + int timeElapsed = 0; + Message[] foundEmails = null; + + while (timeElapsed < timeOutInMillis) { + foundEmails = isEmailReceived(criteria, checkOnlyUnreadEmails, markEmailAsSeen); + if (foundEmails.length == 0) { + logger.info( + "Email not received after " + + timeElapsed / 1000 + + " seconds. Retrying after 5 seconds..."); + ThreadHelper.sleep(FIVE_SECOND_WAIT_MILLI); + timeElapsed += FIVE_SECOND_WAIT_MILLI; + + /** + * Get the session ID to prevent remote Cloud provider from closing the connection for being + * idle (Usually timeouts after 90 seconds). + */ + if (driver != null) { + logger.info("Session ID: {}", ((RemoteWebDriver) driver).getSessionId()); + } else { + logger.info("No driver provided, moving on..."); + } + } else { + break; + } + } + + /** + * Shutdown hook to make sure the email server connection is terminated correctly before the + * application terminates. + */ + Runtime.getRuntime() + .addShutdownHook( + new Thread( + () -> { + logger.info("Shutdown hook started. Terminating the GmailHelper email instance"); + // close the email connection + closeConnection(); + })); + + return foundEmails; + } + + /** + * Parses the content of the email one part at a time. Sometimes this might not work when the + * email is not formatted correctly, and you might need to use @parseEmailFromInputStream. + * + * @param email The email object to check + * @return A string representing the content of the concatenated parts of the email object. + */ + public String parseEmailParts(Message email) { + String result = ""; + try { + if (email.isMimeType("text/plain")) { + result = email.getContent().toString(); + } else if (email.isMimeType("multipart/*")) { + MimeMultipart mimeMultipart = (MimeMultipart) email.getContent(); + result = getTextFromMimeMultipart(mimeMultipart); + } + } catch (Exception ex) { + logger.info("An exception was thrown. Message = " + ex.getMessage()); + } + + return result; + } + + /** + * Parses the email content form its input stream. This method will return an aggregated + * representation of all the email parts as text. Very useful in case parsing the email as parts + * fail. + * + * @param email The email object to check + * @return A string representation to the email's input stream. + */ + public String parseEmailFromInputStream(Message email) { + try { + return new String(email.getInputStream().readAllBytes()); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * Extracts the text from multipart emails + * + * @param mimeMultipart The multipart email to get the content from. + * @return A string representing the email content + * @throws MessagingException + * @throws IOException + */ + private String getTextFromMimeMultipart(MimeMultipart mimeMultipart) + throws MessagingException, IOException { + String result = ""; + int count = mimeMultipart.getCount(); + for (int i = 0; i < count; i++) { + BodyPart bodyPart = mimeMultipart.getBodyPart(i); + if (bodyPart.isMimeType("text/plain")) { + result += "\n" + bodyPart.getContent(); + break; // without break same text appears twice in my tests + } else if (bodyPart.isMimeType("text/html")) { + String html = (String) bodyPart.getContent(); + result += "\n" + org.jsoup.Jsoup.parse(html).text(); + } else if (bodyPart.getContent() instanceof MimeMultipart) { + result += getTextFromMimeMultipart((MimeMultipart) bodyPart.getContent()); + } + } + return result; + } + + /** + * Checks whether a certain email exists in the inbox or not. It uses the provided criteria when + * searching. If multiple emails are found, it will return all of them. + * + * @param criteria The search criteria to use when searching for the email. + * @param checkOnlyUnreadEmails If true, then we would get fetch the unread emails. If false, we + * would check all the emails in the folder (can be slow). + * @param markEmailAsSeen If true, the email would be marked as seen after checking it. + * @return A message array that contains all the emails that match the mentioned criteria. + */ + public Message[] isEmailReceived( + SearchCriteria criteria, boolean checkOnlyUnreadEmails, boolean markEmailAsSeen) { + ArrayList matchedEmails = new ArrayList<>(); + try { + Message[] emails; + if (checkOnlyUnreadEmails) { + emails = getUnreadEmails(openSelectedFolderWithRights("Inbox", Folder.READ_WRITE)); + } else { + emails = openSelectedFolderWithRights("Inbox", Folder.READ_WRITE).getMessages(); + } + + if (emails.length == 0) { + logger.info("No emails found, moving on..."); + return null; + } + + logger.info("Found emails: " + emails.length); + for (int i = 0; i < emails.length; i++) { + Message email = emails[i]; + logger.info("---------------------------------"); + logger.info("Email Number: [{}]", i + 1); + logger.info("Subject: [{}]", email.getSubject()); + logger.info("From: [{}]", email.getFrom()); + + // check if the email matches the criteria + if (doesEmailMatchCriteria(email, criteria)) { + matchedEmails.add(email); + } + + if (markEmailAsSeen) { + email.setFlag(Flag.SEEN, true); + } + } + } catch (Exception e) { + logger.info("An exception was thrown. Message = " + e.getMessage()); + } + + return matchedEmails.toArray(new Message[matchedEmails.size()]); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/Protocol.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/Protocol.java new file mode 100644 index 0000000..e032f6a --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/Protocol.java @@ -0,0 +1,30 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.email; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum Protocol { + POP3("pop3"), + IMAP("imaps"), + SMTP("smtp"); + private final String value; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/SearchCriteria.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/SearchCriteria.java new file mode 100644 index 0000000..8fbb33f --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/SearchCriteria.java @@ -0,0 +1,31 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.email; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** Simple POJO that represents the search criteria when searching for emails. */ +@AllArgsConstructor +@Data +public class SearchCriteria { + + private String emailSubject; + private String sentFrom; + private String sentTo; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParser.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParser.java new file mode 100644 index 0000000..66a8937 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParser.java @@ -0,0 +1,207 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.google; + +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.http.HttpRequestInitializer; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.services.sheets.v4.Sheets; +import com.google.api.services.sheets.v4.SheetsScopes; +import com.google.api.services.sheets.v4.model.*; +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.util.*; +import java.util.stream.Collectors; +import lombok.Getter; +import lombok.SneakyThrows; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Google Sheet parser (from Google Drive) [Requires some configuration management from Google Cloud + * side] Some common tutorials: + */ +public class GoogleSheetParser { + + private static final Logger logger = LogManager.getLogger(GoogleSheetParser.class); + + @Getter private String sheetId; + + @Getter private String googleSheetParsingCloudConfiguredApplicationName; + + private Sheets service; + + /** + * Create sheet parser using API key access That will work once Google sheet document is available + * for sharing with "Everyone with a link" option + * + * @param apiKey + * @param sheetId + */ + public GoogleSheetParser( + String apiKey, String sheetId, String googleSheetParsingCloudConfiguredApplicationName) { + this.sheetId = sheetId; + this.googleSheetParsingCloudConfiguredApplicationName = + googleSheetParsingCloudConfiguredApplicationName; + NetHttpTransport transport = new NetHttpTransport.Builder().build(); + HttpRequestInitializer httpRequestInitializer = + request -> { + request.setInterceptor(intercepted -> intercepted.getUrl().set("key", apiKey)); + }; + this.service = + new Sheets.Builder(transport, GsonFactory.getDefaultInstance(), httpRequestInitializer) + .setApplicationName(googleSheetParsingCloudConfiguredApplicationName) + .build(); + } + + /** + * Create parser using service account .json file + * + * @param serviceAccountJsonFileInputStream input stream for .json file with configs + * @param sheetId Google sheet id value (unique part of access link) + * @throws IOException + * @throws GeneralSecurityException + */ + @SneakyThrows + public GoogleSheetParser( + InputStream serviceAccountJsonFileInputStream, + String sheetId, + String googleSheetParsingCloudConfiguredApplicationName) { + this.sheetId = sheetId; + this.googleSheetParsingCloudConfiguredApplicationName = + googleSheetParsingCloudConfiguredApplicationName; + HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport(); + GoogleCredential googleCredentials = + GoogleCredential.fromStream(serviceAccountJsonFileInputStream) + .createScoped(Collections.singletonList(SheetsScopes.SPREADSHEETS)); + + this.service = + new Sheets.Builder(httpTransport, GsonFactory.getDefaultInstance(), googleCredentials) + .setApplicationName(googleSheetParsingCloudConfiguredApplicationName) + .build(); + } + + /** + * Get all sheet cell records + * + * @param sheetTitle sheet title + * @return kind of a matrix values from Google sheet file per sheet title from a document + * @throws IOException + */ + public List> getAllSheetCellRecords(String sheetTitle) throws IOException { + SheetProperties sheetProperties = getSheetObjectBySheetTitle(sheetTitle).getProperties(); + List dataFilters = new ArrayList<>(); + DataFilter dataFilterForWholeDocumentCells = getAllSheetCellsDataFilter(sheetProperties); + dataFilters.add(dataFilterForWholeDocumentCells); + + BatchGetValuesByDataFilterRequest batchGetValuesByDataFilterRequest = + getBatchFilterRequest(dataFilters); + BatchGetValuesByDataFilterResponse batchGetValuesByDataFilterResponse = + service + .spreadsheets() + .values() + .batchGetByDataFilter(sheetId, batchGetValuesByDataFilterRequest) + .execute(); + List matchedValueRanges = + batchGetValuesByDataFilterResponse.getValueRanges(); + List> values = getCellMatrixFromValueRanges(dataFilters, matchedValueRanges); + logger.info("Lines loaded from Sheet: " + values.size()); + return values; + } + + @SneakyThrows + private Sheet getSheetObjectBySheetTitle(String sheetName) { + Spreadsheet spreadsheetDocument = service.spreadsheets().get(sheetId).execute(); + return spreadsheetDocument.getSheets().stream() + .filter(sheet -> sheet.getProperties().getTitle().equals(sheetName)) + .findFirst() + .orElseThrow( + () -> + new GoogleSheetParserException( + "Sheet with title: " + sheetName + " not " + "found")); + } + + private DataFilter getAllSheetCellsDataFilter(SheetProperties sheetProperties) { + DataFilter dataFilter = new DataFilter(); + GridRange gridRange = new GridRange(); + GridProperties gridProperties = sheetProperties.getGridProperties(); + gridRange.setSheetId(sheetProperties.getSheetId()); + int startIndex = 0; + int endRowIndex = gridProperties.getRowCount() - 1; + int startColumnIndex = 0; + int endColumnIndex = gridProperties.getColumnCount() - 1; + logger.info( + "Creating data filter for sheet: {} with row range [{} , {}] and column range [{} , {}]", + sheetProperties.getTitle(), + startIndex, + endRowIndex, + startColumnIndex, + endColumnIndex); + gridRange.setStartRowIndex(startIndex); + gridRange.setEndRowIndex(endRowIndex); + gridRange.setStartColumnIndex(startColumnIndex); + gridRange.setEndColumnIndex(endColumnIndex); + + dataFilter.setGridRange(gridRange); + return dataFilter; + } + + private BatchGetValuesByDataFilterRequest getBatchFilterRequest(List dataFilters) { + BatchGetValuesByDataFilterRequest batchGetValuesByDataFilterRequest = + new BatchGetValuesByDataFilterRequest(); + batchGetValuesByDataFilterRequest.setDataFilters(dataFilters); + return batchGetValuesByDataFilterRequest; + } + + private List> getCellMatrixFromValueRanges( + List dataFilters, List matchedValueRanges) { + int dataFiltersCount = dataFilters.size(); + if (matchedValueRanges.size() != dataFiltersCount) { + throw new GoogleSheetParserException( + "No value ranges data present for " + + dataFilters.stream() + .map(filter -> filter.getGridRange()) + .collect(Collectors.toList())); + } + + List> values = + (List>) + matchedValueRanges + // TODO Implement dynamic filtering approach with passing List as param + .get(dataFiltersCount - 1) + .getValueRange() + .values() + .stream() + .filter(valueObject -> Collection.class.isAssignableFrom(valueObject.getClass())) + .findFirst() + .orElseThrow( + () -> + new GoogleSheetParserException( + "No Collection value object found in value range result response")); + if (Objects.nonNull(values) && !values.isEmpty()) { + return values; + } else { + logger.error("No data 'matrix' found in range"); + return List.of(Collections.emptyList()); + } + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParserException.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParserException.java new file mode 100644 index 0000000..b3e7df9 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParserException.java @@ -0,0 +1,25 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.google; + +public class GoogleSheetParserException extends RuntimeException { + + public GoogleSheetParserException(String message) { + super(message); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/mapping/IRestObjectMapper.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/mapping/IRestObjectMapper.java new file mode 100644 index 0000000..bc6fb57 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/mapping/IRestObjectMapper.java @@ -0,0 +1,25 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.http.mapping; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** Functional Interface that is representing object mapper method for REST API */ +public interface IRestObjectMapper { + ObjectMapper restJsonObjectMapper(); +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/mapping/JacksonJSONRestObjectMapping.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/mapping/JacksonJSONRestObjectMapping.java new file mode 100644 index 0000000..a41ac4c --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/mapping/JacksonJSONRestObjectMapping.java @@ -0,0 +1,40 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.http.mapping; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** Jackson library json REST API typical object mapper instance */ +public class JacksonJSONRestObjectMapping implements IRestObjectMapper { + + /** + * Object mapper for REST JSON Jackson mapping + * + * @return + */ + @Override + public ObjectMapper restJsonObjectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false); + + return objectMapper; + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/RestApiDefaultRestAssuredApiHelper.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/RestApiDefaultRestAssuredApiHelper.java new file mode 100644 index 0000000..87f615b --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/RestApiDefaultRestAssuredApiHelper.java @@ -0,0 +1,48 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.http.restassured; + +import com.applause.auto.helpers.http.mapping.JacksonJSONRestObjectMapping; +import com.applause.auto.helpers.http.restassured.client.RestApiDefaultRestAssuredApiClient; +import com.applause.auto.helpers.http.restassured.client.RestAssuredApiClient; +import io.restassured.specification.RequestSpecification; +import java.time.Duration; +import lombok.Getter; + +/** + * Default rest assured http client for REST API usage wrapper class For requests with different + * retry policies - AwaitilityWaitUtils could be used + */ +@Getter +public class RestApiDefaultRestAssuredApiHelper { + + private RestAssuredApiClient restAssuredApiClient; + + public RestApiDefaultRestAssuredApiHelper() { + restAssuredApiClient = new RestApiDefaultRestAssuredApiClient(Duration.ofSeconds(60)); + } + + /** + * 'with' describing default RestAssuredConfig + * + * @return + */ + public RequestSpecification withDefaultRestHttpClientConfigsSpecification() { + return restAssuredApiClient.restAssuredRequestSpecification(new JacksonJSONRestObjectMapping()); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestApiDefaultRestAssuredApiClient.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestApiDefaultRestAssuredApiClient.java new file mode 100644 index 0000000..d9bcacc --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestApiDefaultRestAssuredApiClient.java @@ -0,0 +1,73 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.http.restassured.client; + +import static io.restassured.config.ConnectionConfig.connectionConfig; + +import com.applause.auto.helpers.http.mapping.IRestObjectMapper; +import io.restassured.config.ObjectMapperConfig; +import io.restassured.config.RestAssuredConfig; +import io.restassured.filter.log.LogDetail; +import io.restassured.filter.log.RequestLoggingFilter; +import io.restassured.specification.RequestSpecification; +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +/** Default rest assured REST API client impl. */ +public class RestApiDefaultRestAssuredApiClient extends RestAssuredApiClient { + + public RestApiDefaultRestAssuredApiClient(Duration apiRequestWaitTimeoutDuration) { + super(apiRequestWaitTimeoutDuration); + } + + /** + * get rest assured request specification object for default client + * + * @return + */ + @Override + public RequestSpecification restAssuredRequestSpecification(IRestObjectMapper restObjectMapper) { + return super.restAssuredRequestSpecification(restObjectMapper) + .filter(new RequestLoggingFilter(LogDetail.URI)) + .response() + .logDetail(LogDetail.STATUS) + .request(); + } + + /** + * rest assured config with object mapper for default client + * + * @param restObjectMapper + * @return + */ + @Override + protected RestAssuredConfig restAssuredConfig(IRestObjectMapper restObjectMapper) { + RestAssuredConfig restAssuredConfig = new RestAssuredConfig(); + restAssuredConfig = + restAssuredConfig + .objectMapperConfig( + new ObjectMapperConfig() + .jackson2ObjectMapperFactory( + (cls, charset) -> restObjectMapper.restJsonObjectMapper())) + .connectionConfig( + connectionConfig() + .closeIdleConnectionsAfterEachResponseAfter( + getApiRequestWaitTimeoutDuration().getSeconds(), TimeUnit.SECONDS)); + return restAssuredConfig; + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestAssuredApiClient.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestAssuredApiClient.java new file mode 100644 index 0000000..8fd2b84 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestAssuredApiClient.java @@ -0,0 +1,53 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.http.restassured.client; + +import static io.restassured.RestAssured.given; + +import com.applause.auto.helpers.http.mapping.IRestObjectMapper; +import io.restassured.config.RestAssuredConfig; +import io.restassured.specification.RequestSpecification; +import java.time.Duration; +import lombok.Getter; + +/** Rest assured api client */ +@Getter +public abstract class RestAssuredApiClient { + + private Duration apiRequestWaitTimeoutDuration; + + public RestAssuredApiClient(Duration apiRequestWaitTimeoutDuration) { + this.apiRequestWaitTimeoutDuration = apiRequestWaitTimeoutDuration; + } + + /** + * get rest assured request specification object + * + * @return + */ + public RequestSpecification restAssuredRequestSpecification(IRestObjectMapper restObjectMapper) { + return given().when().request().config(restAssuredConfig(restObjectMapper)); + } + + /** + * abstract rest assured config object + * + * @return + */ + protected abstract RestAssuredConfig restAssuredConfig(IRestObjectMapper restObjectMapper); +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/JiraDefect.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/JiraDefect.java new file mode 100644 index 0000000..50f252d --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/JiraDefect.java @@ -0,0 +1,30 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface JiraDefect { + + String identifier() default ""; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/JiraID.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/JiraID.java new file mode 100644 index 0000000..fecbbbf --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/JiraID.java @@ -0,0 +1,30 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface JiraID { + + String identifier() default ""; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/scanner/JiraAnnotationsScanner.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/scanner/JiraAnnotationsScanner.java new file mode 100644 index 0000000..1fc1dd6 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/scanner/JiraAnnotationsScanner.java @@ -0,0 +1,70 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.annotations.scanner; + +import com.applause.auto.helpers.jira.annotations.JiraDefect; +import com.applause.auto.helpers.jira.annotations.JiraID; +import com.applause.auto.helpers.jira.exceptions.JiraAnnotationException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Objects; +import org.testng.ITestResult; + +public class JiraAnnotationsScanner { + + /** + * Scan test and get declared JiraId value Annotation is mandatory to be declared, otherwise + * exception is thrown + * + * @param result + * @return JiraId identifier + * @throws JiraAnnotationException + */ + public static String getJiraIdentifier(ITestResult result) throws JiraAnnotationException { + Method[] methods = result.getTestClass().getRealClass().getMethods(); + try { + return Objects.requireNonNull( + Arrays.stream(methods) + .filter(method -> method.getName().equals(result.getMethod().getMethodName())) + .findFirst() + .get() + .getAnnotation(JiraID.class) + .identifier()); + } catch (NullPointerException nullPointerException) { + throw new JiraAnnotationException( + String.format("Missing JiraId annotation for test [%s]", result.getName())); + } + } + + /** + * Scan test and get declared JiraDefect value + * + * @param result + * @return JiraDefect identifier + */ + public static String getJiraDefect(ITestResult result) { + Method[] methods = result.getTestClass().getRealClass().getMethods(); + return Objects.requireNonNull( + Arrays.stream(methods) + .filter(method -> method.getName().equals(result.getMethod().getMethodName())) + .findFirst() + .orElse(null)) + .getAnnotation(JiraDefect.class) + .identifier(); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/JiraXrayClient.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/JiraXrayClient.java new file mode 100644 index 0000000..fe1f9f3 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/JiraXrayClient.java @@ -0,0 +1,89 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.clients; + +import com.applause.auto.helpers.jira.clients.modules.jira.JiraProjectAPI; +import com.applause.auto.helpers.jira.clients.modules.jira.SearchAPI; +import com.applause.auto.helpers.jira.clients.modules.xray.ExecutionsAPI; +import com.applause.auto.helpers.jira.clients.modules.xray.IterationsAPI; +import com.applause.auto.helpers.jira.clients.modules.xray.StepsAPI; +import com.applause.auto.helpers.jira.clients.modules.xray.TestrunAPI; +import java.util.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Class for communicating with JIRA/X-Ray Server + DC instance API end-points. + * + *

Cannot be used with Cloud instances, use GraphQL instead. + * + *

X-Ray documentation: https://docs.getxray.app/display/XRAY/REST+API + */ +public class JiraXrayClient { + + private static final Logger logger = LogManager.getLogger(JiraXrayClient.class); + + private JiraProjectAPI jiraProjectAPI; + private SearchAPI searchAPI; + private ExecutionsAPI executionsAPI; + private IterationsAPI iterationsAPI; + private StepsAPI stepsAPI; + private TestrunAPI testrunAPI; + + public JiraProjectAPI getJiraProjectApi() { + if (Objects.isNull(jiraProjectAPI)) { + jiraProjectAPI = new JiraProjectAPI(); + } + return jiraProjectAPI; + } + + public SearchAPI getSearchAPI() { + if (Objects.isNull(searchAPI)) { + searchAPI = new SearchAPI(); + } + return searchAPI; + } + + public ExecutionsAPI getExecutionsAPI() { + if (Objects.isNull(executionsAPI)) { + executionsAPI = new ExecutionsAPI(); + } + return executionsAPI; + } + + public IterationsAPI getIterationsAPI() { + if (Objects.isNull(iterationsAPI)) { + iterationsAPI = new IterationsAPI(); + } + return iterationsAPI; + } + + public StepsAPI getStepsAPI() { + if (Objects.isNull(stepsAPI)) { + stepsAPI = new StepsAPI(); + } + return stepsAPI; + } + + public TestrunAPI getTestrunAPI() { + if (Objects.isNull(testrunAPI)) { + testrunAPI = new TestrunAPI(); + } + return testrunAPI; + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/JiraProjectAPI.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/JiraProjectAPI.java new file mode 100644 index 0000000..482c096 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/JiraProjectAPI.java @@ -0,0 +1,270 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.clients.modules.jira; + +import static com.applause.auto.helpers.jira.constants.XrayEndpoints.*; +import static com.applause.auto.helpers.jira.helper.ResponseValidator.checkResponseInRange; +import static com.applause.auto.helpers.jira.requestData.XrayRequestHeaders.getAtlassianNoCheckHeader; +import static com.applause.auto.helpers.jira.requestData.XrayRequestHeaders.getContentTypeMultipartFormDataHeader; +import static com.applause.auto.helpers.jira.restclient.XrayRestAssuredClient.getRestClient; + +import com.applause.auto.helpers.jira.dto.requestmappers.JiraCreateTicketRequest; +import com.applause.auto.helpers.jira.dto.responsemappers.AvailableIssueTypes; +import com.applause.auto.helpers.jira.dto.responsemappers.AvailableProjects; +import com.applause.auto.helpers.jira.dto.responsemappers.JiraCreateTicketResponse; +import com.applause.auto.helpers.jira.dto.responsemappers.steps.Steps; +import com.applause.auto.helpers.jira.dto.shared.Issuetype; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.restassured.response.Response; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Arrays; +import java.util.List; +import org.apache.commons.lang3.Range; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class JiraProjectAPI { + + private static final Logger logger = LogManager.getLogger(JiraProjectAPI.class); + private ObjectMapper mapper = new ObjectMapper(); + + /** + * Creates a simple JIRA ticket, additional fields can be created as per project's needs. This + * method can be used to create a new test case, test plan, test execution etc. + * https://developer.atlassian.com/server/jira/platform/jira-rest-api-examples/ + * + * @param jiraCreateTicketRequestMapping, Object of JiraCreateTicketRequestMapping ticket's title + * @return ticket key extracted from response: {"id":"", "key":"", "self":""} + * @throws JsonProcessingException + */ + public String createTicket(JiraCreateTicketRequest jiraCreateTicketRequestMapping) + throws JsonProcessingException { + logger.info("Creating Jira ticket: {}", jiraCreateTicketRequestMapping.toString()); + Response response = postTicket(jiraCreateTicketRequestMapping); + checkResponseInRange(response, Range.between(200, 300), "Creating new Jira Ticket"); + JiraCreateTicketResponse jiraCreateTicketResponseMapping = + mapper.readValue(response.asString(), JiraCreateTicketResponse.class); + String createdJiraTicketId = jiraCreateTicketResponseMapping.getKey(); + logger.info("Created Jira ticket: {}", createdJiraTicketId); + return createdJiraTicketId; + } + + /** + * Modify existing Jira Ticket by sending the short or full json data + * + * @param jiraTicketID + * @param jsonAsString + * @example: { "fields" : { "labels": [ "my_new_label" ] } } + */ + public void updateTicket(String jiraTicketID, String jsonAsString) { + logger.info("Updating Jira ticket: {} with [ {} ]", jiraTicketID, jsonAsString); + Response response = putUpdateTicket(jiraTicketID, jsonAsString); + checkResponseInRange(response, Range.between(200, 300), "Updating Jira Ticket"); + } + + /** + * Delete existing Jira Ticket + * + * @param jiraTicketID + */ + public void deleteTicket(String jiraTicketID) { + logger.info("Deleting Jira ticket: {}", jiraTicketID); + Response response = deleteExistingTicket(jiraTicketID); + checkResponseInRange(response, Range.between(200, 300), "Deleting Jira Ticket"); + } + + /** + * Get Test Case steps + * + * @param jiraTicketID + * @return Steps object + */ + public Steps getTestCaseSteps(String jiraTicketID) throws JsonProcessingException { + Response response = getJiraTestCaseSteps(jiraTicketID); + Steps steps = mapper.readValue(response.asString(), Steps.class); + checkResponseInRange(response, Range.between(200, 300), "Get Test Case Steps"); + return steps; + } + + /** + * Upload file to specific jira ticket (issue, testcase, execution, plan etc) Checks performed: 1. + * If provided file exists 2. If response contains file name and if status code is in 200 range + * + * @param jiraTicketID, represents the jira ticket identifier + * @param pathToFile, full path to file with its extension + * @return void + */ + public void uploadAttachment(String jiraTicketID, String pathToFile) + throws FileNotFoundException { + Response response = postAttachment(jiraTicketID, pathToFile); + String fileName = new File(pathToFile).getName(); + if (!response.getBody().asString().contains(fileName)) { + logger.error("Failed to upload attachment {}", fileName); + } + checkResponseInRange(response, Range.between(200, 300), "Adding attachment"); + } + + /** + * @param projectKey, represents the jira project identifier Example CARQA-1234, where CARQA is + * the project identifier + * @return projectId + */ + public String getProjectId(String projectKey) throws JsonProcessingException { + StringBuilder apiEndpoint = new StringBuilder(); + apiEndpoint.append(LATEST_API).append("/").append(PROJECT); + Response response = getProjectCode(projectKey); + checkResponseInRange(response, Range.between(200, 300), "Determine project Id"); + AvailableProjects[] availableProjects = + mapper.readValue(response.asString(), AvailableProjects[].class); + return Arrays.stream(availableProjects) + .filter(project -> project.getKey().equalsIgnoreCase(projectKey)) + .findFirst() + .get() + .getId(); + } + + /** + * @param projectId, represents the project identifier code + * @return list of Issuetype objects + */ + public List getAvailableIssueTypes(String projectId) throws JsonProcessingException { + Response response = getAvailableIssues(projectId); + checkResponseInRange(response, Range.between(200, 300), "Get project issue types"); + return mapper.readValue(response.asString(), AvailableIssueTypes.class).getValues(); + } + + /** + * Attach label to Jira ticket + * + * @param jiraTicketID + * @param labelName + */ + public void addLabel(String jiraTicketID, String labelName) { + logger.info("Updating Jira ticket: {} with [ {} ] label", jiraTicketID, labelName); + Response response = putLabelToTicket(jiraTicketID, labelName); + checkResponseInRange(response, Range.between(200, 300), "Adding Jira Ticket label"); + } + + private Response postTicket(JiraCreateTicketRequest jiraCreateTicketRequestMapping) + throws JsonProcessingException { + logger.info("Creating Jira ticket: {}", jiraCreateTicketRequestMapping.toString()); + return getRestClient() + .given() + .and() + .body(mapper.writeValueAsString(jiraCreateTicketRequestMapping)) + .when() + .post(ISSUE_PATH) + .then() + .extract() + .response(); + } + + private Response putUpdateTicket(String jiraTicketID, String jsonAsString) { + logger.info("Updating Jira Ticket {} with [ {} ]", jiraTicketID, jsonAsString); + StringBuilder apiEndpoint = new StringBuilder(ISSUE_PATH); + apiEndpoint.append("/").append(jiraTicketID); + return getRestClient() + .given() + .and() + .body(jsonAsString) + .when() + .put(apiEndpoint.toString()) + .then() + .extract() + .response(); + } + + private Response getJiraTestCaseSteps(String jiraTicketID) { + logger.info("Getting X-Ray Test Case Steps response for case: {}", jiraTicketID); + StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); + apiEndpoint.append(TEST).append("/").append(jiraTicketID).append("/").append(STEPS); + return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + } + + private Response postAttachment(String jiraTicketID, String pathToFile) + throws FileNotFoundException { + logger.info("Uploading attachment {} to Jira Ticket {}", pathToFile, jiraTicketID); + File attachment = new File(pathToFile); + if (!attachment.exists()) { + throw new FileNotFoundException(String.format("Unable to find file %s", pathToFile)); + } + StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); + apiEndpoint.append(ISSUE_PATH).append("/").append(jiraTicketID).append("/").append(ATTACHMENTS); + return getRestClient() + .given() + .header(getContentTypeMultipartFormDataHeader()) + .header(getAtlassianNoCheckHeader()) + .multiPart("file", attachment) + .and() + .when() + .post(apiEndpoint.toString()) + .then() + .extract() + .response(); + } + + private Response getProjectCode(String projectKey) { + logger.info("Returning project code for project key {}", projectKey); + StringBuilder apiEndpoint = new StringBuilder(); + apiEndpoint.append(LATEST_API).append("/").append(PROJECT); + return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + } + + private Response getAvailableIssues(String projectId) { + logger.info("Returning available issues for project {}", projectId); + StringBuilder apiEndpoint = new StringBuilder(); + apiEndpoint + .append(ISSUE_PATH) + .append("/") + .append(CREATEMETA) + .append("/") + .append(projectId) + .append("/") + .append(ISSUE_TYPES); + return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + } + + private Response deleteExistingTicket(String jiraTicketID) { + logger.info("Deleting issue {}", jiraTicketID); + StringBuilder apiEndpoint = new StringBuilder(ISSUE_PATH); + apiEndpoint.append("/").append(jiraTicketID); + return getRestClient() + .given() + .when() + .delete(apiEndpoint.toString()) + .then() + .extract() + .response(); + } + + private Response putLabelToTicket(String jiraTicketID, String labelName) { + StringBuilder apiEndpoint = new StringBuilder(ISSUE_PATH); + apiEndpoint.append("/").append(jiraTicketID); + return getRestClient() + .given() + .and() + .body(String.format("{\"update\":{\"labels\":[{\"add\":\"%s\"}]}}", labelName)) + .when() + .put(apiEndpoint.toString()) + .then() + .extract() + .response(); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/SearchAPI.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/SearchAPI.java new file mode 100644 index 0000000..9d5e0e6 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/SearchAPI.java @@ -0,0 +1,71 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.clients.modules.jira; + +import static com.applause.auto.helpers.jira.constants.XrayEndpoints.*; +import static com.applause.auto.helpers.jira.helper.ResponseValidator.checkResponseInRange; +import static com.applause.auto.helpers.jira.restclient.XrayRestAssuredClient.getRestClient; + +import com.applause.auto.helpers.jira.dto.jql.JqlFilteredResults; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.restassured.response.Response; +import java.util.Objects; +import org.apache.commons.lang3.Range; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class SearchAPI { + + private static final Logger logger = LogManager.getLogger(SearchAPI.class); + private ObjectMapper mapper = new ObjectMapper(); + + /** + * @param jqlQuery, represents the JQL query used in Jira + * @param maxResults, overrides default limited 50 results. Can be set to null for default. + * @example : "labels IN ("xray_automation") AND issuetype=12106 AND summary~"Test Description"" + * @return JqlFilteredResults object containing number of findings and list of issues + */ + public JqlFilteredResults filterIssues(String jqlQuery, Integer maxResults) + throws JsonProcessingException { + Response response = getIssuesByJqlFiltering(jqlQuery, maxResults); + checkResponseInRange(response, Range.between(200, 300), "Get issue by jql filter"); + JqlFilteredResults results = mapper.readValue(response.asString(), JqlFilteredResults.class); + if (results.getTotal() == 0) { + logger.warn("JQL search returned 0 results"); + } + return results; + } + + public Response getIssuesByJqlFiltering(String jqlQuery, Integer maxResults) { + logger.info("Searching issues by JQL query [ {} ] ", jqlQuery); + StringBuilder apiEndpoint = new StringBuilder(); + apiEndpoint.append(LATEST_API).append("/").append(SEARCH).append("?jql=").append(jqlQuery); + if (Objects.nonNull(maxResults)) { + apiEndpoint.append("&maxResults=").append(maxResults); + } + return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + } + + public Response getIssueResponseObject(String jiraTicketID) { + logger.info("Returning issue response for {}", jiraTicketID); + StringBuilder apiEndpoint = new StringBuilder(); + apiEndpoint.append(ISSUE_PATH).append("/").append(jiraTicketID); + return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/ExecutionsAPI.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/ExecutionsAPI.java new file mode 100644 index 0000000..5eaafc5 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/ExecutionsAPI.java @@ -0,0 +1,114 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.clients.modules.xray; + +import static com.applause.auto.helpers.jira.constants.XrayEndpoints.*; +import static com.applause.auto.helpers.jira.helper.ResponseValidator.checkResponseInRange; +import static com.applause.auto.helpers.jira.restclient.XrayRestAssuredClient.getRestClient; + +import com.applause.auto.helpers.jira.dto.requestmappers.XrayAddTo; +import com.applause.auto.helpers.jira.dto.responsemappers.JiraCreateTicketResponse; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.restassured.response.Response; +import org.apache.commons.lang3.Range; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class ExecutionsAPI { + + private static final Logger logger = LogManager.getLogger(ExecutionsAPI.class); + private ObjectMapper mapper = new ObjectMapper(); + + /** + * Adds Test Execution to existing Test Plan, request example: "{"add":["testExecKey"]}", + * Response: 200 OK, if Test Execution was added successfully + * + * @param xrayAddToMapping, Test Execution you want to associate to a specific Test Plan + * @return statusCode + * @throws JsonProcessingException + */ + public void addExecutionToTestPlan(XrayAddTo xrayAddToMapping, String testPlanKey) + throws JsonProcessingException { + Response response = postTestExecutionToTestPlan(xrayAddToMapping, testPlanKey); + checkResponseInRange(response, Range.between(200, 300), "Add test execution to test plan"); + } + + /** + * Adds Test to an existing Test Execution, request example: "{"add":["testKey"]}". Response: 200 + * OK, if test was added successfully + * + * @param jiraCreateTicketResponseMapping, to get Test Execution key from + * @param xrayAddToMapping, Test key/s you want to associate to a specific Test Execution + * @return statusCode + * @throws JsonProcessingException + */ + public void addTestToTestExecution( + JiraCreateTicketResponse jiraCreateTicketResponseMapping, XrayAddTo xrayAddToMapping) + throws JsonProcessingException { + Response response = postTestToTestExecution(jiraCreateTicketResponseMapping, xrayAddToMapping); + checkResponseInRange(response, Range.between(200, 300), "Add test to test execution"); + } + + private Response postTestExecutionToTestPlan(XrayAddTo xrayAddToMapping, String testPlanKey) + throws JsonProcessingException { + logger.info( + "Adding X-Ray Test Execution {} to X-Ray Test Plan {}", xrayAddToMapping, testPlanKey); + StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); + apiEndpoint + .append(TEST_PLAN) + .append("/") + .append(testPlanKey) + .append("/") + .append(TEST_EXECUTION); + return getRestClient() + .given() + .and() + .body(mapper.writeValueAsString(xrayAddToMapping)) + .when() + .post(apiEndpoint.toString()) + .then() + .extract() + .response(); + } + + private Response postTestToTestExecution( + JiraCreateTicketResponse jiraCreateTicketResponseMapping, XrayAddTo xrayAddToMapping) + throws JsonProcessingException { + logger.info( + "Adding X-Ray Test(s) [ {} ] to X-Ray Test Execution [ {} ]", + xrayAddToMapping.toString(), + jiraCreateTicketResponseMapping.getKey()); + StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); + apiEndpoint + .append(TEST_EXEC) + .append("/") + .append(jiraCreateTicketResponseMapping.getKey()) + .append("/") + .append(TEST); + return getRestClient() + .given() + .and() + .body(mapper.writeValueAsString(xrayAddToMapping)) + .when() + .post(apiEndpoint.toString()) + .then() + .extract() + .response(); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/IterationsAPI.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/IterationsAPI.java new file mode 100644 index 0000000..0b61bd1 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/IterationsAPI.java @@ -0,0 +1,69 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.clients.modules.xray; + +import static com.applause.auto.helpers.jira.constants.XrayEndpoints.*; +import static com.applause.auto.helpers.jira.helper.ResponseValidator.checkResponseInRange; +import static com.applause.auto.helpers.jira.restclient.XrayRestAssuredClient.getRestClient; + +import com.applause.auto.helpers.jira.dto.responsemappers.steps.Step; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.restassured.response.Response; +import org.apache.commons.lang3.Range; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class IterationsAPI { + + private static final Logger logger = LogManager.getLogger(IterationsAPI.class); + private ObjectMapper mapper = new ObjectMapper(); + + /** + * Get Test Run Iteration Steps information Returned object will contain also each step ID which + * is needed for further step update request + * + * @param testRunId + * @param iterationId + * @return Step array object + */ + public Step[] getTestRunIterationStepsData(int testRunId, int iterationId) + throws JsonProcessingException { + Response response = getTestRunIterationSteps(testRunId, iterationId); + Step[] steps = mapper.readValue(response.asString(), Step[].class); + checkResponseInRange(response, Range.between(200, 300), "Get Test Run Iteration Steps Data"); + return steps; + } + + private Response getTestRunIterationSteps(int testRunId, int iterationId) { + logger.info( + "Getting X-Ray Test Run {} iteration steps response for ID: {}", testRunId, iterationId); + StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); + apiEndpoint + .append(TEST_RUN) + .append("/") + .append(testRunId) + .append("/") + .append(ITERATION) + .append("/") + .append(iterationId) + .append("/") + .append(STEP); + return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/StepsAPI.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/StepsAPI.java new file mode 100644 index 0000000..d5ed721 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/StepsAPI.java @@ -0,0 +1,225 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.clients.modules.xray; + +import static com.applause.auto.helpers.jira.constants.XrayEndpoints.*; +import static com.applause.auto.helpers.jira.helper.FilesHelper.*; +import static com.applause.auto.helpers.jira.helper.ResponseValidator.checkResponseInRange; +import static com.applause.auto.helpers.jira.restclient.XrayRestAssuredClient.getRestClient; + +import com.applause.auto.helpers.jira.dto.requestmappers.StepFieldsUpdate; +import com.applause.auto.helpers.jira.dto.requestmappers.StepIterationAttachment; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.restassured.response.Response; +import java.io.IOException; +import org.apache.commons.lang3.Range; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class StepsAPI { + + private static final Logger logger = LogManager.getLogger(StepsAPI.class); + private ObjectMapper mapper = new ObjectMapper(); + + /** + * Modify Test Run Step by using PUT API This method is used to update: status, comment, actual + * result Should be used when test is NOT parametrised and dataset is NOT present + * + * @param testRunId + * @param stepId + * @param fields - fields which will be updated + */ + public void updateTestRunStep(int testRunId, int stepId, StepFieldsUpdate fields) + throws JsonProcessingException { + Response response = putTestRunStep(testRunId, stepId, fields); + checkResponseInRange(response, Range.between(200, 300), "Update Test Run Step"); + } + + /** + * Modify Test Run Iteration Step by using PUT API This method is used to update: status, comment, + * actual result Should be used when test is parametrised and dataset is present + * + * @param testRunId + * @param iterationId + * @param stepId + * @param fields - fields which will be updated + */ + public void updateTestRunIterationStep( + int testRunId, int iterationId, int stepId, StepFieldsUpdate fields) + throws JsonProcessingException { + Response response = putTestRunIterationStep(testRunId, iterationId, stepId, fields); + checkResponseInRange(response, Range.between(200, 300), "Update Test Run Iteration Step"); + } + + /** + * Upload Test Run Step attachment + * + * @param testRunId + * @param stepId + * @param filePath - path to file + */ + public void uploadTestRunStepAttachment(int testRunId, int stepId, String filePath) + throws IOException { + Response response = postTestRunStepAttachment(testRunId, stepId, filePath); + checkResponseInRange(response, Range.between(200, 300), "Upload Test Run Step attachment"); + } + + /** + * Upload Test Run Iteration Step attachment + * + * @param testRunId + * @param iterationId + * @param stepId + * @param filePath - path to file + */ + public void uploadTestRunIterationStepAttachment( + int testRunId, int iterationId, int stepId, String filePath) throws IOException { + Response response = + postTestRunIterationStepAttachment(testRunId, iterationId, stepId, filePath); + checkResponseInRange( + response, Range.between(200, 300), "Upload Test Run Iteration Step attachment"); + } + + private Response postTestRunStepAttachment(int testRunId, int stepId, String filePath) + throws IOException { + logger.info("Attaching {} to X-Ray Test Run {} step {}", filePath, testRunId, stepId); + StepIterationAttachment stepIterationAttachment = new StepIterationAttachment(); + stepIterationAttachment.setData(encodeBase64File(filePath)); + stepIterationAttachment.setFilename(getFileNameFromPath(filePath)); + stepIterationAttachment.setContentType(getFileType(filePath)); + StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); + apiEndpoint + .append(TEST_RUN) + .append("/") + .append(testRunId) + .append("/") + .append(STEP) + .append("/") + .append(stepId) + .append("/") + .append(ATTACHMENT); + return getRestClient() + .given() + .and() + .body(mapper.writeValueAsString(stepIterationAttachment)) + .when() + .post(apiEndpoint.toString()) + .then() + .extract() + .response(); + } + + private Response postTestRunIterationStepAttachment( + int testRunId, int iterationId, int stepId, String filePath) throws IOException { + logger.info( + "Attaching {} to X-Ray Test Run {} iteration {} step {}", + filePath, + testRunId, + iterationId, + stepId); + StepIterationAttachment stepIterationAttachment = new StepIterationAttachment(); + stepIterationAttachment.setData(encodeBase64File(filePath)); + stepIterationAttachment.setFilename(getFileNameFromPath(filePath)); + stepIterationAttachment.setContentType(getFileType(filePath)); + StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); + apiEndpoint + .append(TEST_RUN) + .append("/") + .append(testRunId) + .append("/") + .append(ITERATION) + .append("/") + .append(iterationId) + .append("/") + .append(STEP) + .append("/") + .append(stepId) + .append("/") + .append(ATTACHMENT); + return getRestClient() + .given() + .and() + .body(mapper.writeValueAsString(stepIterationAttachment)) + .when() + .post(apiEndpoint.toString()) + .then() + .extract() + .response(); + } + + private Response putTestRunStep(int testRunId, int stepId, StepFieldsUpdate stepFieldsUpdate) + throws JsonProcessingException { + logger.info( + "Updating X-Ray Test Run {} step {} with {}", + testRunId, + stepId, + stepFieldsUpdate.toString()); + StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); + apiEndpoint + .append(TEST_RUN) + .append("/") + .append(testRunId) + .append("/") + .append(STEP) + .append("/") + .append(stepId); + return getRestClient() + .given() + .and() + .body(mapper.writeValueAsString(stepFieldsUpdate)) + .when() + .put(apiEndpoint.toString()) + .then() + .extract() + .response(); + } + + private Response putTestRunIterationStep( + int testRunId, int iterationId, int stepId, StepFieldsUpdate stepFieldsUpdate) + throws JsonProcessingException { + logger.info( + "Updating X-Ray Test Run {} iteration {} step {} with {}", + testRunId, + iterationId, + stepId, + stepFieldsUpdate.toString()); + StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); + apiEndpoint + .append(TEST_RUN) + .append("/") + .append(testRunId) + .append("/") + .append(ITERATION) + .append("/") + .append(iterationId) + .append("/") + .append(STEP) + .append("/") + .append(stepId); + return getRestClient() + .given() + .and() + .body(mapper.writeValueAsString(stepFieldsUpdate)) + .when() + .put(apiEndpoint.toString()) + .then() + .extract() + .response(); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/TestrunAPI.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/TestrunAPI.java new file mode 100644 index 0000000..f2fed01 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/TestrunAPI.java @@ -0,0 +1,211 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.clients.modules.xray; + +import static com.applause.auto.helpers.jira.constants.XrayEndpoints.*; +import static com.applause.auto.helpers.jira.helper.FilesHelper.*; +import static com.applause.auto.helpers.jira.helper.ResponseValidator.checkResponseInRange; +import static com.applause.auto.helpers.jira.restclient.XrayRestAssuredClient.getRestClient; + +import com.applause.auto.helpers.jira.dto.requestmappers.StepIterationAttachment; +import com.applause.auto.helpers.jira.dto.responsemappers.JiraCreateTicketResponse; +import com.applause.auto.helpers.jira.dto.responsemappers.XrayTestRunDetails; +import com.applause.auto.helpers.jira.dto.responsemappers.iteration.TestRunIteration; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.restassured.response.Response; +import java.io.IOException; +import org.apache.commons.lang3.Range; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class TestrunAPI { + + private static final Logger logger = LogManager.getLogger(TestrunAPI.class); + private ObjectMapper mapper = new ObjectMapper(); + + /** + * Get Test Run ID which can be used in updating Test status in a certain Test Execution + * + * @param jiraCreateTicketResponseMapping, to get value of testExecKey, + * @param testKey, + * @return testRunID, + * @throws JsonProcessingException + */ + public int getTestRunID(JiraCreateTicketResponse jiraCreateTicketResponseMapping, String testKey) + throws JsonProcessingException { + Response response = getTestRunIdOfTestFromExecution(jiraCreateTicketResponseMapping, testKey); + XrayTestRunDetails xrayTestRunDetailsMapping = + mapper.readValue(response.asString(), XrayTestRunDetails.class); + checkResponseInRange(response, Range.between(200, 300), "Collecting Test Run ID"); + return xrayTestRunDetailsMapping.getId(); + } + + /** + * Updates Test Run status, + * + * @param testRunId, + * @param statusToUpdate: Can be extracted after test execution from ITestResult using its + * getStatus() and then converted into one of the following values accepted by X-Ray as per + * project needs: + *

EXECUTING – Test is being executed; this is a non-final status; + *

FAIL – Test failed + *

ABORTED – Test was aborted + *

PASS – Test passed successfully + */ + public void updateTestRun(int testRunId, String statusToUpdate) { + Response response = putTestRunStatus(testRunId, statusToUpdate); + checkResponseInRange(response, Range.between(200, 300), "Update test run"); + } + + /** + * Get Test Run information + * + * @param testRunId + * @return XrayTestRunDetails object + */ + public XrayTestRunDetails getTestRunData(int testRunId) throws JsonProcessingException { + Response response = getTestRunBasedOnID(testRunId); + XrayTestRunDetails xrayTestRunDetailsMapping = + mapper.readValue(response.asString(), XrayTestRunDetails.class); + checkResponseInRange(response, Range.between(200, 300), "Get Test Run Data"); + return xrayTestRunDetailsMapping; + } + + /** + * Get Test Run Iteration information + * + * @param testRunId + * @param iterationId + * @return TestRunIteration object + */ + public TestRunIteration getTestRunIterationData(int testRunId, int iterationId) + throws JsonProcessingException { + Response response = getTestRunIterationBasedOnID(testRunId, iterationId); + TestRunIteration testRunIteration = + mapper.readValue(response.asString(), TestRunIteration.class); + checkResponseInRange(response, Range.between(200, 300), "Get Test Run Iteration Data"); + return testRunIteration; + } + + /** + * Upload Test Run attachment + * + * @param testRunId + * @param filePath - path to file + */ + public void uploadTestRunAttachment(int testRunId, String filePath) throws IOException { + Response response = postTestRunAttachment(testRunId, filePath); + checkResponseInRange(response, Range.between(200, 300), "Upload Test Run attachment"); + } + + /** + * Post comment to Test Run + * + * @param testRunId + * @param comment + */ + public void postTestRunComment(int testRunId, String comment) { + Response response = postComment(testRunId, comment); + checkResponseInRange(response, Range.between(200, 300), "Posting Test Run comment"); + } + + private Response getTestRunIdOfTestFromExecution( + JiraCreateTicketResponse jiraCreateTicketResponseMapping, String testKey) { + logger.info("Getting X-Ray Test Run ID for test: {}", testKey); + StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); + apiEndpoint + .append(TEST_RUN) + .append("?") + .append(testExecIssueKeyParam) + .append(jiraCreateTicketResponseMapping.getKey()) + .append("&") + .append(testIssueKeyParam) + .append(testKey); + return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + } + + private Response putTestRunStatus(int testRunId, String statusToUpdate) { + logger.info("Updating X-Ray Test Run: {} with status: {}", testRunId, statusToUpdate); + StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); + apiEndpoint + .append(TEST_RUN + "/") + .append(testRunId) + .append("/") + .append(STATUS) + .append("?") + .append(statusParam) + .append(statusToUpdate); + return getRestClient().given().when().put(apiEndpoint.toString()).then().extract().response(); + } + + private Response getTestRunBasedOnID(int testRunId) { + logger.info("Getting X-Ray Test Run response for ID: {}", testRunId); + StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); + apiEndpoint.append(TEST_RUN).append("/").append(testRunId); + return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + } + + private Response getTestRunIterationBasedOnID(int testRunId, int iterationId) { + logger.info("Getting X-Ray Test Run {} iteration response for ID: {}", testRunId, iterationId); + StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); + apiEndpoint + .append(TEST_RUN) + .append("/") + .append(testRunId) + .append("/") + .append(ITERATION) + .append("/") + .append(iterationId); + return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + } + + private Response postTestRunAttachment(int testRunId, String filePath) throws IOException { + logger.info("Attaching {} to X-Ray Test Run {}", filePath, testRunId); + StepIterationAttachment stepIterationAttachment = new StepIterationAttachment(); + stepIterationAttachment.setData(encodeBase64File(filePath)); + stepIterationAttachment.setFilename(getFileNameFromPath(filePath)); + stepIterationAttachment.setContentType(getFileType(filePath)); + StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); + apiEndpoint.append(TEST_RUN).append("/").append(testRunId).append("/").append(ATTACHMENT); + return getRestClient() + .given() + .and() + .body(mapper.writeValueAsString(stepIterationAttachment)) + .when() + .post(apiEndpoint.toString()) + .then() + .extract() + .response(); + } + + private Response postComment(int testRunId, String comment) { + logger.info("Posting comment [{}] to X-Ray Test Run {}", comment, testRunId); + StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); + apiEndpoint.append(TEST_RUN).append("/").append(testRunId).append("/").append(COMMENT); + return getRestClient() + .given() + .and() + .body(comment) + .when() + .put(apiEndpoint.toString()) + .then() + .extract() + .response(); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/Statuses.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/Statuses.java new file mode 100644 index 0000000..e951409 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/Statuses.java @@ -0,0 +1,32 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.constants; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public enum Statuses { + PASS("PASS"), + FAIL("FAIL"), + ABORTED("ABORTED"), + BLOCKED("BLOCKED"), + TODO("TODO"); + + @Getter private String value; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/XrayEndpoints.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/XrayEndpoints.java new file mode 100644 index 0000000..842b999 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/XrayEndpoints.java @@ -0,0 +1,49 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.constants; + +public class XrayEndpoints { + + public static final String LATEST_API = "/api/latest"; + + public static final String XRAY_PATH = "/raven/latest/api/"; + public static final String ISSUE_PATH = LATEST_API + "/issue"; + + public static final String PROJECT = "project"; + public static final String TEST_PLAN = "testplan"; + public static final String TEST = "test"; + public static final String STEP = "step"; + public static final String STEPS = "steps"; + public static final String TEST_EXECUTION = "testexecution"; + public static final String TEST_EXEC = "testexec"; + public static final String TEST_RUN = "testrun"; + public static final String ITERATION = "iteration"; + public static final String STATUS = "status"; + public static final String ATTACHMENT = "attachment"; + public static final String ATTACHMENTS = "attachments"; + public static final String ISSUE_TYPES = "issuetypes"; + public static final String CREATEMETA = "createmeta"; + public static final String SEARCH = "search"; + public static final String COMMENT = "comment"; + + /** Query parameters */ + public static final String testExecIssueKeyParam = "testExecIssueKey="; + + public static final String testIssueKeyParam = "testIssueKey="; + public static final String statusParam = "status="; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Fields.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Fields.java new file mode 100644 index 0000000..c1f3ef8 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Fields.java @@ -0,0 +1,32 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.jql; + +import com.applause.auto.helpers.jira.dto.shared.Issuetype; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Fields { + private String summary; + private String[] labels; + private String created; + private Issuetype issuetype; + private String environment; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Issues.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Issues.java new file mode 100644 index 0000000..7e1079f --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Issues.java @@ -0,0 +1,31 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.jql; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import lombok.Getter; + +@Data +@Getter +@JsonIgnoreProperties(ignoreUnknown = true) +public class Issues { + public String id; + public String key; + public Fields fields; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/JqlFilteredResults.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/JqlFilteredResults.java new file mode 100644 index 0000000..0d52985 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/JqlFilteredResults.java @@ -0,0 +1,32 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.jql; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.List; +import lombok.Data; +import lombok.Getter; + +@Data +@Getter +@JsonIgnoreProperties(ignoreUnknown = true) +public class JqlFilteredResults { + private int maxResults; + private int total; + private List issues; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/Fields.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/Fields.java new file mode 100644 index 0000000..b084727 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/Fields.java @@ -0,0 +1,33 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.requestmappers; + +import com.applause.auto.helpers.jira.dto.shared.Issuetype; +import com.applause.auto.helpers.jira.dto.shared.Project; +import lombok.Data; + +@Data +public class Fields { + // summary represents ticket's title. + private String summary; + // issueTypeId is different per project, and unique per ticket type (defect, task, test plan etc). + // Example: Test Plan: 12106, Xray Test: 10402. + private Issuetype issuetype; + // Project id is the identifier of the project. + private Project project; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraCreateTicketRequest.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraCreateTicketRequest.java new file mode 100644 index 0000000..c97d6c8 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraCreateTicketRequest.java @@ -0,0 +1,25 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.requestmappers; + +import lombok.Data; + +@Data +public class JiraCreateTicketRequest { + private Fields fields; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraFields.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraFields.java new file mode 100644 index 0000000..f543a0c --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraFields.java @@ -0,0 +1,44 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.requestmappers; + +import com.applause.auto.helpers.jira.dto.shared.Issuetype; +import com.applause.auto.helpers.jira.dto.shared.Project; + +public class JiraFields { + + private Fields fields = null; + + public JiraFields(String issueType, String projectId, String summary) { + fields = new Fields(); + + Issuetype issuetype = new Issuetype(); + issuetype.setId(issueType); + fields.setIssuetype(issuetype); + + Project project = new Project(); + project.setId(projectId); + fields.setProject(project); + + fields.setSummary(summary); + } + + public Fields getFields() { + return fields; + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepFieldsUpdate.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepFieldsUpdate.java new file mode 100644 index 0000000..e5d2b02 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepFieldsUpdate.java @@ -0,0 +1,27 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.requestmappers; + +import lombok.Data; + +@Data +public class StepFieldsUpdate { + private String status; + private String comment; + private String actualResult; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepIterationAttachment.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepIterationAttachment.java new file mode 100644 index 0000000..4aa9085 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepIterationAttachment.java @@ -0,0 +1,27 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.requestmappers; + +import lombok.Data; + +@Data +public class StepIterationAttachment { + private String data; + private String filename; + private String contentType; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/XrayAddTo.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/XrayAddTo.java new file mode 100644 index 0000000..d0346a1 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/XrayAddTo.java @@ -0,0 +1,26 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.requestmappers; + +import java.util.List; +import lombok.Data; + +@Data +public class XrayAddTo { + private List add; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableIssueTypes.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableIssueTypes.java new file mode 100644 index 0000000..1eb7380 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableIssueTypes.java @@ -0,0 +1,29 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.responsemappers; + +import com.applause.auto.helpers.jira.dto.shared.Issuetype; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.List; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class AvailableIssueTypes { + private List values; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableProjects.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableProjects.java new file mode 100644 index 0000000..f1fa8f2 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableProjects.java @@ -0,0 +1,30 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.responsemappers; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class AvailableProjects { + private String self; + private String id; + private String key; + private String name; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/JiraCreateTicketResponse.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/JiraCreateTicketResponse.java new file mode 100644 index 0000000..2e5e1e0 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/JiraCreateTicketResponse.java @@ -0,0 +1,27 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.responsemappers; + +import lombok.Data; + +@Data +public class JiraCreateTicketResponse { + private String id; + private String key; + private String self; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/XrayTestRunDetails.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/XrayTestRunDetails.java new file mode 100644 index 0000000..f14e96d --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/XrayTestRunDetails.java @@ -0,0 +1,47 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.responsemappers; + +import com.applause.auto.helpers.jira.dto.responsemappers.iteration.Iteration; +import com.applause.auto.helpers.jira.dto.responsemappers.steps.Step; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.ArrayList; +import java.util.List; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class XrayTestRunDetails { + private int id; + private String status; + private String color; + private String testKey; + private String testExecKey; + private String executedBy; + private String startedOn; + private String finishedOn; + private String startedOnIso; + private String finishedOnIso; + private int duration; + private List iterations; + private List defects; + private List evidences; + private List testEnvironments; + private List fixVersions; + private ArrayList steps; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/Iteration.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/Iteration.java new file mode 100644 index 0000000..71eade7 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/Iteration.java @@ -0,0 +1,30 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.responsemappers.iteration; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.List; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Iteration { + private int id; + private String status; + private List parameters; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/IterationParameters.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/IterationParameters.java new file mode 100644 index 0000000..8f79d2c --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/IterationParameters.java @@ -0,0 +1,28 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.responsemappers.iteration; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class IterationParameters { + private String name; + private String value; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/TestRunIteration.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/TestRunIteration.java new file mode 100644 index 0000000..381b352 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/TestRunIteration.java @@ -0,0 +1,34 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.responsemappers.iteration; + +import com.applause.auto.helpers.jira.dto.responsemappers.steps.Step; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.ArrayList; +import java.util.List; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class TestRunIteration { + private int id; + private String testRunId; + private String status; + private List parameters; + private ArrayList steps; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Action.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Action.java new file mode 100644 index 0000000..3bad947 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Action.java @@ -0,0 +1,27 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.responsemappers.steps; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Action { + private Value value; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Attachments.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Attachments.java new file mode 100644 index 0000000..8a14518 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Attachments.java @@ -0,0 +1,30 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.responsemappers.steps; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Attachments { + private int id; + private String fileName; + private String fileURL; + private String filePath; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Comment.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Comment.java new file mode 100644 index 0000000..82d213f --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Comment.java @@ -0,0 +1,27 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.responsemappers.steps; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Comment { + private String rendered; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Data.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Data.java new file mode 100644 index 0000000..729bb37 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Data.java @@ -0,0 +1,27 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.responsemappers.steps; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@lombok.Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Data { + private String type; + private Value value; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Fields.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Fields.java new file mode 100644 index 0000000..e2f8d9d --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Fields.java @@ -0,0 +1,27 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.responsemappers.steps; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Fields { + public Action Action; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Step.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Step.java new file mode 100644 index 0000000..40fd9c4 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Step.java @@ -0,0 +1,35 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.responsemappers.steps; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.List; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Step { + private int id; + private int index; + private String status; + private Fields fields; + private List attachments; + private Comment comment; + private List evidences; + private Comment actualResult; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Steps.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Steps.java new file mode 100644 index 0000000..a3c43e0 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Steps.java @@ -0,0 +1,28 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.responsemappers.steps; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.ArrayList; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Steps { + private ArrayList steps; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Value.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Value.java new file mode 100644 index 0000000..fb537b8 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Value.java @@ -0,0 +1,27 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.responsemappers.steps; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Value { + private String raw; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Issuetype.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Issuetype.java new file mode 100644 index 0000000..a05194f --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Issuetype.java @@ -0,0 +1,28 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.shared; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Issuetype { + private String id; + private String name; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Project.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Project.java new file mode 100644 index 0000000..a845fca --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Project.java @@ -0,0 +1,25 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.dto.shared; + +import lombok.Data; + +@Data +public class Project { + private String id; +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraAnnotationException.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraAnnotationException.java new file mode 100644 index 0000000..a61ead3 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraAnnotationException.java @@ -0,0 +1,25 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.exceptions; + +public class JiraAnnotationException extends RuntimeException { + + public JiraAnnotationException(String message) { + super(message); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraPropertiesFileException.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraPropertiesFileException.java new file mode 100644 index 0000000..6ae228a --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraPropertiesFileException.java @@ -0,0 +1,25 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.exceptions; + +public class JiraPropertiesFileException extends RuntimeException { + + public JiraPropertiesFileException(String message) { + super(message); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/UnidentifiedExecutionStatusException.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/UnidentifiedExecutionStatusException.java new file mode 100644 index 0000000..8753298 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/UnidentifiedExecutionStatusException.java @@ -0,0 +1,25 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.exceptions; + +public class UnidentifiedExecutionStatusException extends RuntimeException { + + public UnidentifiedExecutionStatusException(String message) { + super(message); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/FilesHelper.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/FilesHelper.java new file mode 100644 index 0000000..f413bf9 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/FilesHelper.java @@ -0,0 +1,51 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.helper; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Base64; +import lombok.SneakyThrows; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class FilesHelper { + + private static final Logger logger = LogManager.getLogger(FilesHelper.class); + + public static String encodeBase64File(String filePath) throws IOException { + logger.info("Encoding file: {} to Base64", filePath); + byte[] fileContent = Files.readAllBytes(getFile(filePath).toPath()); + return Base64.getEncoder().encodeToString(fileContent); + } + + public static String getFileNameFromPath(String filePath) { + return getFile(filePath).getName(); + } + + public static String getFileType(String filePath) throws IOException { + return Files.probeContentType(getFile(filePath).toPath()); + } + + @SneakyThrows + private static File getFile(String filePath) { + logger.info("Returning file from path: {}", filePath); + return new File(filePath); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/ResponseValidator.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/ResponseValidator.java new file mode 100644 index 0000000..8053db6 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/ResponseValidator.java @@ -0,0 +1,42 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.helper; + +import io.restassured.response.Response; +import org.apache.commons.lang3.Range; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class ResponseValidator { + + private static final Logger logger = LogManager.getLogger(ResponseValidator.class); + + public static void checkResponseInRange( + Response response, Range expectedRange, String action) { + int statusCode = response.statusCode(); + if (expectedRange.contains(statusCode)) { + logger.info("{} was successfully performed", action); + logger.info(response.getBody().asString()); + } else { + logger.error("{} failed with status code {}", action, statusCode); + if (response.getBody() != null) { + logger.error(response.getBody().asString()); + } + } + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/listeners/XrayListener.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/listeners/XrayListener.java new file mode 100644 index 0000000..a249abd --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/listeners/XrayListener.java @@ -0,0 +1,129 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.listeners; + +import com.applause.auto.helpers.jira.annotations.scanner.JiraAnnotationsScanner; +import com.applause.auto.helpers.jira.clients.JiraXrayClient; +import com.applause.auto.helpers.jira.dto.requestmappers.XrayAddTo; +import com.applause.auto.helpers.jira.dto.responsemappers.JiraCreateTicketResponse; +import com.applause.auto.helpers.jira.exceptions.JiraAnnotationException; +import com.applause.auto.helpers.jira.exceptions.UnidentifiedExecutionStatusException; +import com.fasterxml.jackson.core.JsonProcessingException; +import java.util.Arrays; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.testng.ITestContext; +import org.testng.ITestResult; +import org.testng.TestListenerAdapter; + +public class XrayListener extends TestListenerAdapter { + + private static final Logger logger = LogManager.getLogger(XrayListener.class); + private JiraXrayClient jiraXrayClient = new JiraXrayClient(); + + @Override + public void onTestFailure(ITestResult result) { + logger.info("Setting status for FAILED test: [{}]", result.getTestName()); + super.onTestFailure(result); + updateJiraXray(result); + } + + @Override + public void onTestSuccess(ITestResult result) { + logger.info("Setting status for PASSED test: [{}]", result.getTestName()); + super.onTestSuccess(result); + updateJiraXray(result); + } + + @Override + public void onTestSkipped(ITestResult result) { + logger.info("Setting status for SKIPPED test: [{}]", result.getTestName()); + super.onTestSkipped(result); + updateJiraXray(result); + } + + /** + * Updates Jira X-Ray with execution results + * + * @param result + */ + private void updateJiraXray(ITestResult result) { + try { + int testRunId = addTestToTestExecution(result); + jiraXrayClient.getTestrunAPI().updateTestRun(testRunId, getExecutionStatus(result)); + } catch (JsonProcessingException | JiraAnnotationException e) { + logger.error("Failed to add Test to Test Execution and update its execution status."); + e.printStackTrace(); + } + } + + /** + * Adds test to X-Ray Test Execution based on data passed as @JiraID annotation + * + * @param result + * @return testRunId, String value that represents identifier of the test case executed under a + * Test Execution + * @throws JsonProcessingException + */ + private int addTestToTestExecution(ITestResult result) + throws JsonProcessingException, JiraAnnotationException { + JiraCreateTicketResponse jiraCreateTicketResponseMapping = new JiraCreateTicketResponse(); + ITestContext context = result.getTestContext(); + String jiraTestIdentifier = JiraAnnotationsScanner.getJiraIdentifier(result); + // Assuming testExecKey refers to Test Execution key (ticket ID) the current test is using which + // must be created before execution of the test + String testExecutionKey = context.getAttribute("testExecKey").toString(); + jiraCreateTicketResponseMapping.setKey(testExecutionKey); + XrayAddTo xrayAddToMapping = new XrayAddTo(); + xrayAddToMapping.setAdd(Arrays.asList(jiraTestIdentifier)); + jiraXrayClient + .getExecutionsAPI() + .addTestToTestExecution(jiraCreateTicketResponseMapping, xrayAddToMapping); + return jiraXrayClient + .getTestrunAPI() + .getTestRunID(jiraCreateTicketResponseMapping, jiraTestIdentifier); + } + + /** + * Gets test execution status based on the ITestResult and maps it to accepted X-Ray values + * + * @param result + * @return status + */ + private String getExecutionStatus(ITestResult result) { + String testKey = result.getMethod().getMethodName(); + String status = ""; + logger.info("Getting execution status for test key: {}", testKey); + int statusCode = result.getStatus(); + switch (statusCode) { + case 1: + status = "PASS"; + break; + case 2: + status = "FAIL"; + break; + case 3: + status = "BLOCKED"; + break; + default: + throw new UnidentifiedExecutionStatusException( + String.format("Unable to determine status for code %s", statusCode)); + } + return status; + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/requestData/XrayRequestHeaders.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/requestData/XrayRequestHeaders.java new file mode 100644 index 0000000..3625a3d --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/requestData/XrayRequestHeaders.java @@ -0,0 +1,43 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.requestData; + +import io.restassured.http.Header; + +public class XrayRequestHeaders { + + public static Header getAcceptApplicationJsonHeader() { + return new Header("Accept", "application/json"); + } + + public static Header getContentTypeMultipartFormDataHeader() { + return new Header("Content-Type", "multipart/form-data"); + } + + public static Header getBearerAuthorizationHeader(String token) { + return new Header("Authorization", "Bearer " + token); + } + + public static Header getJSONContentTypeHeader() { + return new Header("Content-Type", "application/json; charset=utf-8"); + } + + public static Header getAtlassianNoCheckHeader() { + return new Header("X-Atlassian-Token", "no-check"); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/restclient/IJiraAppConfig.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/restclient/IJiraAppConfig.java new file mode 100644 index 0000000..b1c1cdc --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/restclient/IJiraAppConfig.java @@ -0,0 +1,32 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.restclient; + +import org.aeonbits.owner.Config; +import org.aeonbits.owner.Config.LoadPolicy; +import org.aeonbits.owner.Config.Sources; + +@LoadPolicy(Config.LoadType.MERGE) +@Sources({"classpath:jira.properties"}) +interface IJiraAppConfig extends Config { + @Key("jira.url") + String jiraUrl(); + + @Key("jira.xray.token") + String jiraToken(); +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/restclient/XrayRestAssuredClient.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/restclient/XrayRestAssuredClient.java new file mode 100644 index 0000000..e6747b6 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/restclient/XrayRestAssuredClient.java @@ -0,0 +1,66 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.jira.restclient; + +import static com.applause.auto.helpers.jira.requestData.XrayRequestHeaders.*; + +import com.applause.auto.helpers.http.restassured.RestApiDefaultRestAssuredApiHelper; +import com.applause.auto.helpers.jira.exceptions.JiraPropertiesFileException; +import io.restassured.specification.RequestSpecification; +import java.io.File; +import org.aeonbits.owner.ConfigFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class XrayRestAssuredClient { + + private static final Logger logger = LogManager.getLogger(XrayRestAssuredClient.class); + private static final String JIRA_PROPERTIES_FILE_PATH = "src/main/resources/jira.properties"; + + private static String jiraUrl; + private static String token; + private static IJiraAppConfig configApp = ConfigFactory.create(IJiraAppConfig.class); + + private static void loadProperties() { + if (!new File(JIRA_PROPERTIES_FILE_PATH).exists()) { + throw new JiraPropertiesFileException( + "Couldn't find jira.properties file in resources folder!"); + } + token = configApp.jiraToken(); + jiraUrl = configApp.jiraUrl(); + checkJiraUrl(jiraUrl); + } + + public static RequestSpecification getRestClient() { + loadProperties(); + RestApiDefaultRestAssuredApiHelper restApiDefaultRestAssuredApiHelper = + new RestApiDefaultRestAssuredApiHelper(); + return restApiDefaultRestAssuredApiHelper + .withDefaultRestHttpClientConfigsSpecification() + .baseUri(jiraUrl) + .header(getBearerAuthorizationHeader(token)) + .header(getAcceptApplicationJsonHeader()) + .header(getJSONContentTypeHeader()); + } + + private static void checkJiraUrl(String jiraUrl) { + if (!jiraUrl.startsWith("https")) { + logger.warn("API calls might return 415 because provided Jira url is not https!"); + } + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileContextsUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileContextsUtils.java new file mode 100644 index 0000000..f454324 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileContextsUtils.java @@ -0,0 +1,121 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.mobile; + +import static com.applause.auto.helpers.util.AwaitilityWaitUtils.waitForCondition; + +import io.appium.java_client.remote.SupportsContextSwitching; +import java.util.Set; +import java.util.concurrent.Callable; +import lombok.SneakyThrows; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** Helper class to work with mobile web and native contexts */ +public class MobileContextsUtils { + + private static final Logger logger = LogManager.getLogger(MobileContextsUtils.class); + + private static final String WEB_CONTEXT_PART_NAME = "web"; + private static final String NATIVE_CONTEXT_PART_NAME = "native"; + + /** + * Perform actions in a web context, return result and switch back to native context + * + * @param driver - automation driver + * @param actions - callable to actions to perform inside switched context + * @param waitForContextTimeout - wait timeout to wait for context to appear + * @param waitPollingInterval - polling interval timeout for wait for context to appear + * @param Generic class that is used. + * @return An object instance from callable actions performed in context + */ + @SneakyThrows + public static A performActionsInWebView( + SupportsContextSwitching driver, + Callable actions, + int waitForContextTimeout, + int waitPollingInterval) { + switchToFirstAvailableWebContext(driver, waitForContextTimeout, waitPollingInterval); + try { + return actions.call(); + } finally { + switchToFirstAvailableNativeContext(driver, waitForContextTimeout, waitPollingInterval); + } + } + + /** + * Switching to web context + * + * @param driver - automation driver + * @param waitForContextTimeout - wait timeout to wait for context to appear + * @param waitPollingInterval - polling interval timeout for wait for context to appear + */ + public static void switchToFirstAvailableWebContext( + SupportsContextSwitching driver, int waitForContextTimeout, int waitPollingInterval) { + waitForContextAndSwitchToIt( + driver, WEB_CONTEXT_PART_NAME, waitForContextTimeout, waitPollingInterval); + } + + /** + * Switching to native context + * + * @param driver - automation driver + * @param waitForContextTimeout - wait timeout to wait for context to appear + * @param waitPollingInterval - polling interval timeout for wait for context to appear + */ + public static void switchToFirstAvailableNativeContext( + SupportsContextSwitching driver, int waitForContextTimeout, int waitPollingInterval) { + waitForContextAndSwitchToIt( + driver, NATIVE_CONTEXT_PART_NAME, waitForContextTimeout, waitPollingInterval); + } + + /** + * Wait for mobile app context by string part and switch to it + * + * @param driver - automation driver + * @param contextStringPart - part of expected context name on mobile view + * @param waitForContextTimeout - wait timeout to wait for context to appear + * @param waitPollingInterval - polling interval timeout for wait for context to appear + */ + public static void waitForContextAndSwitchToIt( + SupportsContextSwitching driver, + String contextStringPart, + int waitForContextTimeout, + int waitPollingInterval) { + logger.info("Looking for context containing: " + contextStringPart); + waitForCondition( + () -> { + Set contexts = driver.getContextHandles(); + logger.info("Available contexts: " + contexts); + return contexts.stream() + .anyMatch(context -> StringUtils.containsIgnoreCase(context, contextStringPart)); + }, + waitForContextTimeout, + waitPollingInterval, + "Waiting for context string part: " + contextStringPart); + Set contexts = driver.getContextHandles(); + String neededContext = + contexts.stream() + .filter(contextItem -> StringUtils.containsIgnoreCase(contextItem, contextStringPart)) + .findFirst() + .get(); + logger.info("Switching to context with name: " + neededContext); + driver.context(neededContext); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileElementUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileElementUtils.java new file mode 100644 index 0000000..eca9082 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileElementUtils.java @@ -0,0 +1,133 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.mobile; + +import static com.applause.auto.helpers.mobile.MobileUtils.getMobileDriver; + +import io.appium.java_client.AppiumDriver; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.NonNull; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.Point; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.PointerInput; +import org.openqa.selenium.interactions.Sequence; + +/** Helper for mobile elements on native mobile */ +public class MobileElementUtils { + + private static final Logger logger = LogManager.getLogger(MobileElementUtils.class); + + /** + * Tap element center + * + * @param driver - automation driver + * @param element - mobile element for tap on + */ + public static void tapElementCenter(AppiumDriver driver, WebElement element) { + Point centerOfElement = getCenter(element); + tapElementByCoordinates(driver, centerOfElement); + } + + /** + * Tap element by coordinates + * + * @param driver - automation driver + * @param elementCoordinates - coordinates point for tap + */ + public static void tapElementByCoordinates(AppiumDriver driver, Point elementCoordinates) { + PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger"); + Sequence tap = new Sequence(finger, 1); + tap.addAction( + finger.createPointerMove( + Duration.ofMillis(500), + PointerInput.Origin.viewport(), + elementCoordinates.getX(), + elementCoordinates.getY())); + tap.addAction(finger.createPointerDown(0)); // 0 represents the left mouse button + tap.addAction(finger.createPointerUp(0)); + driver.perform(List.of(tap)); + } + + /** + * Long press on element center (Updated for Appium v8) + * + * @param driver - automation driver + * @param element - mobile element for long press on + */ + public static void longPressOnElementCenter(AppiumDriver driver, WebElement element) { + Point centerOfElement = getCenter(element); + + PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger"); + Sequence longPress = new Sequence(finger, 1); + + longPress.addAction( + finger.createPointerMove( + Duration.ofMillis(500), + PointerInput.Origin.viewport(), + centerOfElement.getX(), + centerOfElement.getY())); + longPress.addAction(finger.createPointerDown(0)); // Press down + longPress.addAction( + finger.createPointerMove( + Duration.ofMillis(1000), + PointerInput.Origin.viewport(), + centerOfElement.getX(), + centerOfElement.getY())); // Hold for duration. Appium longpress is default 1000 ms + longPress.addAction(finger.createPointerUp(0)); // Release + + driver.perform(List.of(longPress)); + } + + /** + * Click element with execute script, this is new Appium mobile approach of elements and device + * interactions through .executeScript(...) + * + * @param driver - automation driver + * @param element - mobile element for click on + * @param tapCount - tap count + * @param touchCount - tap count + * @param duration - duration for tap + */ + public static void clickElementWithExecuteScript( + WebDriver driver, WebElement element, int tapCount, int touchCount, int duration) { + Point elementCenter = getCenter(element); + logger.info("Element coords: " + elementCenter); + Map tap = new HashMap<>(); + tap.put("tapCount", (double) tapCount); + tap.put("touchCount", (double) touchCount); + tap.put("duration", (double) duration); + tap.put("x", (double) elementCenter.getX()); + tap.put("y", (double) elementCenter.getY()); + getMobileDriver(driver).executeScript("mobile: tap", tap); + } + + public static Point getCenter(@NonNull final WebElement element) { + Dimension size = element.getSize(); + Point location = element.getLocation(); + int centerX = location.getX() + size.getWidth() / 2; + int centerY = location.getY() + size.getHeight() / 2; + return new Point(centerX, centerY); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileUtils.java new file mode 100644 index 0000000..f863a94 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileUtils.java @@ -0,0 +1,246 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.mobile; + +import com.applause.auto.helpers.util.ThreadHelper; +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.HidesKeyboard; +import io.appium.java_client.InteractsWithApps; +import io.appium.java_client.android.StartsActivity; +import io.appium.java_client.ios.IOSDriver; +import java.time.Duration; +import java.util.List; +import java.util.stream.IntStream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.interactions.PointerInput; +import org.openqa.selenium.interactions.Sequence; +import org.openqa.selenium.remote.RemoteWebDriver; + +/** Common mobile native utils */ +public class MobileUtils { + + private static final Logger logger = LogManager.getLogger(MobileUtils.class); + + private static final String CF_BUNDLE_IDENTIFIER = "CFBundleIdentifier"; + + /** + * Scroll down scroll view X times + * + * @param driver - automation driver + * @param pStartXCoef - start x coef. ( pStartXCoef * x screen width) + * @param pStartYCoef - start y coef. ( pStartYCoef * Y screen height) + * @param pEndYCoef - end y coef. ( pEndYCoef * Y screen height) + * @param waitOption1 - wait action after first press + * @param waitOption2 - wait action after move to + * @param scrollTimes - how many times to scroll + */ + public static void scrollVerticalSeveralTimes( + AppiumDriver driver, + double pStartXCoef, + double pStartYCoef, + double pEndYCoef, + int waitOption1, + int waitOption2, + int scrollTimes) { + IntStream.range(0, scrollTimes) + .forEach( + action -> + scrollVertical( + driver, pStartXCoef, pStartYCoef, pEndYCoef, waitOption1, waitOption2)); + } + + /** + * Scroll down scroll view + * + * @param driver - automation driver + * @param pStartXCoef - start x coef. ( pStartXCoef * x screen width) + * @param pStartYCoef - start y coef. ( pStartYCoef * Y screen height) + * @param pEndYCoef - end y coef. ( pEndYCoef * Y screen height) + * @param waitOption1 - wait action after first press + * @param waitOption2 - wait action after move to + */ + public static void scrollVertical( + AppiumDriver driver, + double pStartXCoef, + double pStartYCoef, + double pEndYCoef, + int waitOption1, + int waitOption2) { + + Dimension size = driver.manage().window().getSize(); + + int startY = (int) (size.getHeight() * pStartYCoef); + int endY = (int) (size.getHeight() * pEndYCoef); + int startX = (int) (size.getWidth() * pStartXCoef); // Make sure startX is an int + + logger.info( + "Swiping Down: [startX]: " + startX + " , [startY]: " + startY + " , [endY]: " + endY); + + try { + PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger"); + Sequence scroll = new Sequence(finger, 1); + + scroll.addAction( + finger.createPointerMove( + Duration.ofMillis(0), PointerInput.Origin.viewport(), startX, startY)); + scroll.addAction(finger.createPointerDown(0)); + scroll.addAction( + finger.createPointerMove( + Duration.ofMillis(waitOption1), PointerInput.Origin.viewport(), startX, startY)); + scroll.addAction( + finger.createPointerMove( + Duration.ofMillis(waitOption2), PointerInput.Origin.viewport(), startX, endY)); + scroll.addAction(finger.createPointerUp(0)); + + driver.perform(List.of(scroll)); + + } catch (Exception wex) { + logger.warn( + "Swipe cause error, probably nothing to swipe or driver issue: " + wex.getMessage()); + } + } + + /** Clear chrome cache locally through ADB shell */ + public static void clearAndroidChromeCacheForLocalExecution() { + logger.info("Clearing android chrome cache"); + new ProcessBuilder().command("db", "shell", "pm", "clear", "com.android.chrome"); + } + + /** + * Hide mobile keyboard + * + * @param driver - automation driver + */ + public static void hideKeyboard(HidesKeyboard driver) { + try { + driver.hideKeyboard(); + } catch (Exception e) { + logger.error("Error occured while hiding a keyboard"); + } + } + + /** + * Press screen by coordinates + * + * @param driver - automation driver + * @param x - x coordinate press + * @param y - y coordinate press + * @param waitOption - wait action option value in millis + */ + public static void pressByCoordinates(AppiumDriver driver, int x, int y, long waitOption) { + logger.info("Pressing natively by coordinates: " + x + " " + y); + + PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger"); + Sequence press = new Sequence(finger, 1); + + press.addAction( + finger.createPointerMove( + Duration.ofMillis(0), PointerInput.Origin.viewport(), x, y)); // Move to coordinates + press.addAction(finger.createPointerDown(0)); // Press down + if (waitOption > 0) { // Add wait if specified + press.addAction( + finger.createPointerMove( + Duration.ofMillis(waitOption), PointerInput.Origin.viewport(), x, y)); // Hold + } + press.addAction(finger.createPointerUp(0)); // Release + + driver.perform(List.of(press)); + } + + /** + * Refresh page source (for mobile UIs with big page layout xml) + * + * @param driver - automation driver + * @param waitBeforePageSourceRefresh - wait before page source refresh in millis + * @param waitAfterPageSourceRefresh - wait after page source refresh in millis + */ + public static void refreshPageSource( + WebDriver driver, int waitBeforePageSourceRefresh, int waitAfterPageSourceRefresh) { + logger.info("Refresh page source"); + ThreadHelper.sleep(waitBeforePageSourceRefresh); + getMobileDriver(driver).getPageSource(); + ThreadHelper.sleep(waitAfterPageSourceRefresh); + } + + /** + * Get mobile appium driver + * + * @param driver - automation driver + * @return casted mobile driver or exception if driver casting to mobile one is not supported + */ + public static AppiumDriver getMobileDriver(WebDriver driver) { + if (driver instanceof AppiumDriver) { + return (AppiumDriver) driver; + } else { + throw new IllegalStateException("No mobile driver found"); + } + } + + /** + * Get App Bundle Identifier for android device + * + * @return String + */ + public static String getBundleIdentifierAndroid(StartsActivity driver) { + String bundleId = driver.getCurrentPackage(); + logger.info(String.format("App bundle ID is [%s]", bundleId)); + return bundleId; + } + + /** + * Get App Bundle Identifier iOS + * + * @return String + */ + public static String getBundleIdentifierIos(RemoteWebDriver driver) { + String bundleId = + getMobileDriver(driver).getCapabilities().getCapability(CF_BUNDLE_IDENTIFIER).toString(); + logger.info(String.format("App bundle ID is [%s]", bundleId)); + return bundleId; + } + + /** + * Get App Bundle Identifier + * + * @return String + */ + public static String getBundleIdentifier(RemoteWebDriver driver) { + return driver instanceof IOSDriver + ? getBundleIdentifierIos(driver) + : getBundleIdentifierAndroid((StartsActivity) driver); + } + + /** Move App In Background */ + public static void moveAppToBackground(InteractsWithApps driver) { + logger.info("Move App To Background"); + driver.runAppInBackground(Duration.ofSeconds(-1)); + } + + /** + * Activate App using bundleId and Return to AUT + * + * @param bundleId Bundle ID + */ + public static void activateApp(InteractsWithApps driver, String bundleId) { + logger.info("Return to AUT."); + driver.activateApp(bundleId); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/MobileDeepLinksUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/MobileDeepLinksUtils.java new file mode 100644 index 0000000..7a2b788 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/MobileDeepLinksUtils.java @@ -0,0 +1,114 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.mobile.deeplinks; + +import com.applause.auto.helpers.mobile.MobileUtils; +import com.applause.auto.helpers.util.ThreadHelper; +import io.appium.java_client.AppiumBy; +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.HidesKeyboard; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.openqa.selenium.By; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.FluentWait; +import org.openqa.selenium.support.ui.Wait; + +/** Provides some common mobile deeplinks navigation methods */ +public class MobileDeepLinksUtils { + + private static final Logger logger = LogManager.getLogger(MobileDeepLinksUtils.class); + + /** + * Open deeplink on Android + * + * @param driver - mobile driver + * @param androidDeepLink - Andrdoid deeplink DTO object + */ + public static void openDeepLinkOnAndroid( + AppiumDriver driver, NativeMobileAppCommonDeeplink.AndroidDeepLink androidDeepLink) { + logger.info("Opening a Android deeplink: " + androidDeepLink.getDeepLinkUrl()); + Map parameters = new HashMap<>(); + parameters.put("url", androidDeepLink.getDeepLinkUrl()); + parameters.put("package", androidDeepLink.getDeepLinkPackage()); + driver.executeScript("mobile:deepLink", parameters); + // another possible approach + // getMobileDriver().get(androidDeepLink.getDeepLinkUrl()); + } + + /** + * Open deeplink on iOS + * + * @param driver - mobile driver + * @param iosDeepLink - iOS deeplink DTO object + * @param waitForSafariURLBarTimeoutInSec - wait for Safari URL bar to appear during opening a + * deeplink + * @param waitForSafariURLBarPollingInSec - polling timeout for Safari URL bar to appear during + * opening a deeplink + */ + public static void openDeepLinkOniOS( + T driver, + NativeMobileAppCommonDeeplink.iOSDeepLink iosDeepLink, + int waitForSafariURLBarTimeoutInSec, + int waitForSafariURLBarPollingInSec) { + logger.info("Launch Safari and enter the deep link in the address bar"); + Map parameters = new HashMap<>(); + parameters.put("bundleId", "com.apple.mobilesafari"); + driver.executeScript("mobile:launchApp", parameters); + + By urlButtonSelector = + AppiumBy.iOSNsPredicateString("type == 'XCUIElementTypeButton' && name CONTAINS 'URL'"); + + // Wait for the url button to appear and click on it so the text field will appear + // iOS 13 now has the keyboard open by default because the URL field has focus when opening + // the Safari browser + Wait wait = + new FluentWait(driver) + .withTimeout(Duration.ofSeconds(waitForSafariURLBarTimeoutInSec)) + .pollingEvery(Duration.ofSeconds(waitForSafariURLBarPollingInSec)) + .ignoring(Exception.class); + + wait.until(ExpectedConditions.presenceOfElementLocated(urlButtonSelector)); + + MobileUtils.hideKeyboard(driver); + + driver.findElement(urlButtonSelector).click(); + // URL bar is 'jumping' here on the left side, custom wait + ThreadHelper.sleep(1000); + + By urlFieldSelector = + AppiumBy.iOSNsPredicateString("type == 'XCUIElementTypeTextField' && name CONTAINS 'URL'"); + driver.findElement(urlFieldSelector).sendKeys(iosDeepLink.getDeepLinkUrl()); + By goSelector = + AppiumBy.iOSNsPredicateString("type == 'XCUIElementTypeButton' && label CONTAINS 'go'"); + try { + wait.until(ExpectedConditions.presenceOfElementLocated(goSelector)); + driver.findElement(goSelector).click(); + } catch (Exception e) { + logger.error("iOS deeplink navigation confirmation button click issue"); + } + By openSelector = + AppiumBy.iOSNsPredicateString( + "type == 'XCUIElementTypeButton' && (name CONTAINS 'Open' OR name == 'Go')"); + wait.until(ExpectedConditions.presenceOfElementLocated(openSelector)); + driver.findElement(openSelector).click(); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/NativeMobileAppCommonDeeplink.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/NativeMobileAppCommonDeeplink.java new file mode 100644 index 0000000..41239e9 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/NativeMobileAppCommonDeeplink.java @@ -0,0 +1,46 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.mobile.deeplinks; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +/** Deeplink DTO to keep deeplink data for mobile deeplink navigation */ +@Builder +@Data +public class NativeMobileAppCommonDeeplink { + + private AndroidDeepLink androidDeepLink; + private iOSDeepLink iOSDeepLink; + + /** Android deep link class. */ + @Data + @AllArgsConstructor + public static class AndroidDeepLink { + private String deepLinkUrl; + private String deepLinkPackage; + } + + /** iOS deep link class. */ + @Data + @AllArgsConstructor + public static class iOSDeepLink { + private String deepLinkUrl; + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/file_uploading/SauceLabs/FileUploadingHelper.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/file_uploading/SauceLabs/FileUploadingHelper.java new file mode 100644 index 0000000..2808c2c --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/file_uploading/SauceLabs/FileUploadingHelper.java @@ -0,0 +1,143 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.mobile.file_uploading.SauceLabs; + +import com.applause.auto.helpers.mobile.MobileUtils; +import com.applause.auto.helpers.util.ThreadHelper; +import com.google.common.collect.ImmutableMap; +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.InteractsWithApps; +import io.appium.java_client.android.AndroidDriver; +import io.restassured.internal.util.IOUtils; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebDriver; +import org.testng.TestException; + +/** The type File Uploading Helper. */ +public class FileUploadingHelper { + private static final Logger logger = LogManager.getLogger(FileUploadingHelper.class); + + public static final String DEVICE_IMAGES_LOCATION_ANDROID = "sdcard/Download/%s"; + + /** + * Upload Images to the device + * + * @param imagesPath + */ + public static void uploadImages( + T driver, String imagesPath, long timeOuts) { + String bundleIdentifier = MobileUtils.getBundleIdentifier(driver); + MobileUtils.moveAppToBackground(driver); + /** + * we have static wait because there is no way to check if the app was moved to the Background. + * Sometimes, especially in the cloud, I found that image uploading started too early, and they + * were uploaded but the app didn't see them + */ + ThreadHelper.sleep(5000); + getFilesFromPaths(imagesPath).forEach(image -> uploadFile(driver, image)); + ThreadHelper.sleep(timeOuts); + MobileUtils.activateApp(driver, bundleIdentifier); + } + + /** + * Upload one file + * + * @param fileName + */ + public static void uploadFile(WebDriver driver, File fileName) { + try { + logger.info("Uploading [{}] file", fileName); + ((AndroidDriver) MobileUtils.getMobileDriver(driver)) + .pushFile(String.format(DEVICE_IMAGES_LOCATION_ANDROID, fileName.getName()), fileName); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Get Files From Path + * + * @param pathToFolder + * @return Set + */ + private static Set getFilesFromPaths(String pathToFolder) { + try (Stream stringStream = + Files.walk(Paths.get(pathToFolder)).filter(Files::isRegularFile)) { + List pathList = stringStream.collect(Collectors.toList()); + Set filesInFolder = + pathList.stream().map(path -> new File(path.toString())).collect(Collectors.toSet()); + return filesInFolder; + } catch (IOException e) { + logger.info(e.getStackTrace().toString()); + } + return Collections.emptySet(); + } + + /** Clear Measurement images folder. */ + public static void clearImagesFolder(WebDriver driver) { + logger.info( + "Clearing Mocked Measurement images folder on device [sdcard/greendot/hijacking/strip]"); + try { + List removePicsArgs = Arrays.asList("-rf", "/sdcard/greendot/hijacking/strip/*.*"); + Map removePicsCmd = ImmutableMap.of("command", "rm", "args", removePicsArgs); + ((AndroidDriver) MobileUtils.getMobileDriver(driver)) + .executeScript("mobile: shell", removePicsCmd); + } catch (Exception e) { + logger.info(e.getMessage()); + logger.info( + "\n\nTo run any shell commands, we need to set the 'relaxed security' flag. Please start your Appium server with [appium --relaxed-security]\n"); + } + } + + /** + * Camera image injection or camera mocking Inject any image and then use device Camera with that + * image in front + * + *

Need to use this caps to Enable image-injection on RDC -> + * desiredCapabilities.setCapability("sauceLabsImageInjectionEnabled", true); + * + *

Link - https://docs.saucelabs.com/mobile-apps/features/camera-image-injection/ + * + * @param driver + * @param fileLocation + */ + public static void injectImageToSauce(WebDriver driver, String fileLocation) { + try { + logger.info("Injecting [{}] file to Sauce device.", fileLocation); + FileInputStream in = new FileInputStream(fileLocation); + String qrCodeImage = Base64.getEncoder().encodeToString(IOUtils.toByteArray(in)); + + // Provide the transformed image to the device + ((JavascriptExecutor) driver).executeScript("sauce:inject-image=" + qrCodeImage); + } catch (Exception e) { + e.printStackTrace(); + throw new TestException("Image injection error."); + } + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/TestDataProvider.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/TestDataProvider.java new file mode 100644 index 0000000..843aeb5 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/TestDataProvider.java @@ -0,0 +1,41 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.testdata; + +import java.util.Collection; + +public interface TestDataProvider { + + /** + * read single value from test data file + * + * @param dataPathSyntax + * @param + * @return + */ + T readSingleValueFromTestDataFile(String dataPathSyntax); + + /** + * read collection of values from test data file + * + * @param dataPathSyntax + * @param + * @return + */ + > C readValuesFromTestDataFile(String dataPathSyntax); +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/yaml/YamlTestDataProvider.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/yaml/YamlTestDataProvider.java new file mode 100644 index 0000000..7fb27ed --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/yaml/YamlTestDataProvider.java @@ -0,0 +1,50 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.testdata.yaml; + +import com.applause.auto.helpers.testdata.TestDataProvider; +import io.github.yamlpath.YamlExpressionParser; +import io.github.yamlpath.YamlPath; +import java.io.FileInputStream; +import java.util.Set; +import lombok.Getter; +import lombok.SneakyThrows; + +/** Yaml test data reader based on https://github.com/yaml-path/YamlPath */ +public class YamlTestDataProvider implements TestDataProvider { + + @Getter private final String yamlTestDataFilePath; + + private YamlExpressionParser yamlExpressionParser; + + @SneakyThrows + public YamlTestDataProvider(String yamlTestDataFilePath) { + this.yamlTestDataFilePath = yamlTestDataFilePath; + this.yamlExpressionParser = YamlPath.from(new FileInputStream(yamlTestDataFilePath)); + } + + @Override + public T readSingleValueFromTestDataFile(String yamlPathSyntax) { + return yamlExpressionParser.readSingle(yamlPathSyntax); + } + + @Override + public Set readValuesFromTestDataFile(String yamlPathSyntax) { + return yamlExpressionParser.read(yamlPathSyntax); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/AwaitilityWaitUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/AwaitilityWaitUtils.java new file mode 100644 index 0000000..80dc8e1 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/AwaitilityWaitUtils.java @@ -0,0 +1,72 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.util; + +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import org.awaitility.Awaitility; + +/** Awaitility Wait Utils */ +public class AwaitilityWaitUtils { + + /** + * Wait for callable state with predicate and return it's value + * + * @param - callable actions type parameter + * @param callable - actions to call during wait + * @param predicate - predicate to match for actions that will be called during wait + * @param waitInterval - wait interval in seconds + * @param pollingInterval - polling timeout for wait interval in seconds + * @param alias - text alias for this wait + * @return T - object instance that will be returned from callable actions during wait + */ + public static T waitForCondition( + Callable callable, + Predicate predicate, + int waitInterval, + int pollingInterval, + String alias) { + return Awaitility.with() + .pollInterval(pollingInterval, TimeUnit.SECONDS) + .pollInSameThread() + .atMost(waitInterval, TimeUnit.SECONDS) + .ignoreExceptions() + .alias(alias) + .until(callable, predicate); + } + + /** + * Wait for callable state with predicate and return it's value + * + * @param callable - boolean callable actions state that will be checked during wait + * @param waitInterval - wait interval in seconds + * @param pollingInterval - polling timeout for wait interval in seconds + * @param alias - text alias for this wait + */ + public static void waitForCondition( + Callable callable, int waitInterval, int pollingInterval, String alias) { + Awaitility.with() + .pollInterval(pollingInterval, TimeUnit.SECONDS) + .pollInSameThread() + .atMost(waitInterval, TimeUnit.SECONDS) + .ignoreExceptions() + .alias(alias) + .until(callable); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/GenericObjectMapper.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/GenericObjectMapper.java new file mode 100644 index 0000000..4407ee9 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/GenericObjectMapper.java @@ -0,0 +1,25 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Getter; + +public class GenericObjectMapper { + @Getter private static final ObjectMapper objectMapper = new ObjectMapper(); +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/RandomUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/RandomUtils.java new file mode 100644 index 0000000..9efecf0 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/RandomUtils.java @@ -0,0 +1,65 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.util; + +import com.github.javafaker.Faker; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** Random dat utils */ +public class RandomUtils { + + private static final Logger logger = LogManager.getLogger(RandomUtils.class); + + /** + * Get Faker object for further fake data generation * / + * + * @return Faker - faker object from Faker library https://github.com/DiUS/java-faker + */ + public static Faker getFaker() { + return new Faker(); + } + + /** + * Get random password + * + * @param upperCaseSymbolsCount - count of upper case symbols + * @param lowCaseSymbolsCount - count of lower case symbols + * @param numericSymbolsCount - numeric symbols count + * @param withSpecialCharacter - should include special character or not + * @return generated password + */ + public static String getRandomValidUserAccountPassword( + int upperCaseSymbolsCount, + int lowCaseSymbolsCount, + int numericSymbolsCount, + boolean withSpecialCharacter) { + StringBuilder randomPasswordStringBuilder = + new StringBuilder() + .append(RandomStringUtils.randomAlphabetic(upperCaseSymbolsCount).toUpperCase()) + .append(RandomStringUtils.randomAlphabetic(lowCaseSymbolsCount).toLowerCase()) + .append(RandomStringUtils.randomNumeric(numericSymbolsCount)); + if (withSpecialCharacter) { + randomPasswordStringBuilder.append("$"); + } + String randomPassword = randomPasswordStringBuilder.toString(); + logger.info("Newly generated random password is: " + randomPassword); + return randomPassword; + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/ThreadHelper.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/ThreadHelper.java new file mode 100644 index 0000000..d2cfb45 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/ThreadHelper.java @@ -0,0 +1,37 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.util; + +/** Utility helper class that provides common actions needed to control the current Thread. */ +public class ThreadHelper { + + private ThreadHelper() {} + + /** + * Suspends the current thread for a specified period of milliseconds. + * + * @param milliseconds A long value that represents the milliseconds. + */ + public static void sleep(long milliseconds) { + try { + Thread.sleep(milliseconds); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/HtmlUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/HtmlUtils.java new file mode 100644 index 0000000..4568e6a --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/HtmlUtils.java @@ -0,0 +1,63 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.web; + +import com.applause.auto.helpers.util.AwaitilityWaitUtils; +import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.openqa.selenium.WebDriver; +import us.codecraft.xsoup.Xsoup; + +/** Common methods for HTML-based content. */ +public class HtmlUtils { + + private static final Logger logger = LogManager.getLogger(HtmlUtils.class); + + /** + * Wait for page viewport to be not empty + * + * @param driver - automation driver + * @param xpathRootLocator - root Xpath locator for viewport DOM element + * @param waitInterval - wait interval to wait for viewport + * @param pollingInterval - polling interval for wait of viewport + */ + public static void waitForPageViewPortNotEmpty( + WebDriver driver, String xpathRootLocator, int waitInterval, int pollingInterval) { + AwaitilityWaitUtils.waitForCondition( + () -> { + String currentPageSource = driver.getPageSource(); + boolean gsdHtmlInViewportLoaded = + wasHtmlInViewportLoadedByRootElementXpathLocator(currentPageSource, xpathRootLocator); + logger.info("Page viewport was loaded correctly: " + gsdHtmlInViewportLoaded); + return gsdHtmlInViewportLoaded; + }, + waitInterval, + pollingInterval, + "Wait for html viewport to be loaded"); + } + + private static boolean wasHtmlInViewportLoadedByRootElementXpathLocator( + String currentHtml, String xpathRootLocator) { + Document doc = Jsoup.parse(currentHtml); + List list = Xsoup.compile(xpathRootLocator).evaluate(doc).list(); + return !list.isEmpty(); + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebElementUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebElementUtils.java new file mode 100644 index 0000000..559a21e --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebElementUtils.java @@ -0,0 +1,305 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.web; + +import com.applause.auto.helpers.util.ThreadHelper; +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.PerformsTouchActions; +import io.appium.java_client.TouchAction; +import io.appium.java_client.android.AndroidDriver; +import io.appium.java_client.ios.IOSDriver; +import io.appium.java_client.touch.offset.PointOption; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.openqa.selenium.*; + +/** Common utils for web elements */ +public class WebElementUtils { + + private static final Logger logger = LogManager.getLogger(WebElementUtils.class); + + /** + * Get element Y offset + * + * @param driver - automation driver + * @param element - web element + * @return element Y offset value + */ + public static int getElementYOffset(WebDriver driver, WebElement element) { + int offsetY = + Integer.parseInt( + ((JavascriptExecutor) driver) + .executeScript("return arguments[0].offsetTop", element) + .toString()); + logger.info("Current element" + element + " offset y = " + offsetY); + return offsetY; + } + + /** + * Get element text content with js + * + * @param driver - automation driver + * @param webElementWithTextContent - web element + * @return element text content + */ + public static String getElementTextContentWithJS( + WebDriver driver, WebElement webElementWithTextContent) { + JavascriptExecutor js = (JavascriptExecutor) driver; + try { + String textContent = + (String) js.executeScript("return arguments[0].textContent", webElementWithTextContent); + logger.info("Text content is: " + textContent); + return textContent; + } catch (Exception e) { + return StringUtils.EMPTY; + } + } + + /** + * Custom sendkeys by symbol with delay + * + * @param textBoxElement - web element + * @param text - text to input + * @param waitBetweenEachInput - wait between input in millis + */ + public static void customSendKeysBySymbolWithDelay( + WebElement textBoxElement, String text, int waitBetweenEachInput) { + for (int symbolIndex = 0; symbolIndex < text.length(); symbolIndex++) { + textBoxElement.sendKeys(Character.toString(text.charAt(symbolIndex))); + ThreadHelper.sleep(waitBetweenEachInput); + } + } + + /** + * Clear input field value with backspace with delay + * + * @param inputWebElement - web element + * @param waitBetweenEachDelete - wait between each backspace deletion + */ + public static void clearFieldValueWithBackspaceWithDelay( + WebElement inputWebElement, int waitBetweenEachDelete) { + int valueCharacters = inputWebElement.getAttribute("value").length(); + for (int i = 0; i < valueCharacters + 1; i++) { + inputWebElement.sendKeys(Keys.BACK_SPACE); + ThreadHelper.sleep(waitBetweenEachDelete); + } + } + + /** + * Check whether mobile web execution is being performed + * + * @param driver - automation driver + * @return boolean state whether this kind of driver is mobile one of not + */ + public static boolean isMobileWebExecutionDriver(WebDriver driver) { + return driver instanceof AppiumDriver; + } + + /** + * Clicks an element at an accurate point on devices, with native tap. This method is to mitigate + * issues where the different device sizes cause the element locations to differ. + * + * @param element + */ + public static void clickElementWithNativeTapWithOffset( + WebDriver driver, WebElement element, int xOffset, int yOffset, boolean isTablet) { + int x = getAccuratePointX(driver, element) + xOffset; + int y = getAccuratePointY(driver, element, isTablet) + yOffset; + logger.info("Clicking element with native tap at (" + x + ", " + y + ")."); + new TouchAction((PerformsTouchActions) driver).tap(PointOption.point(x, y)).release().perform(); + } + + /** + * Gets accurate X point for element + * + * @param element + * @return Accurate X point for element + */ + public static int getAccuratePointX(WebDriver driver, WebElement element) { + double widthRatio = + (double) driver.manage().window().getSize().width + / (double) getJavascriptWindowWidth(driver); + return (int) (getLocation(driver, element).getX() * widthRatio) + + (element.getSize().getWidth() / 2); + } + + /** + * @return JavaScript window width + */ + public static int getJavascriptWindowWidth(WebDriver driver) { + int windowWidth = + ((Long) + ((JavascriptExecutor) driver) + .executeScript("return window.innerWidth || document.body.clientWidth")) + .intValue(); + logger.info("Current window width is: " + windowWidth); + return windowWidth; + } + + /** + * Gets the element location on the screen using JS. TODO: We are currently using this as a + * workaround as the default getLocation is not working in W3C mode + * + * @param element The element to retrieve the location from. + * @return A point representing the location + */ + public static Point getLocation(WebDriver driver, WebElement element) { + return getElementRect(driver, element).getPoint(); + } + + /** + * Gets element rect. TODO: remove this when getLocation & getDimension work again in W3C mode. + * + * @param element the element + * @return the element rect + */ + public static Rectangle getElementRect(WebDriver driver, WebElement element) { + Map result = + (Map) + ((JavascriptExecutor) driver) + .executeScript("return arguments[0].getBoundingClientRect()", element); + logger.info(result.toString()); + return new Rectangle( + (int) Double.parseDouble(result.get("x").toString()), + (int) Double.parseDouble(result.get("y").toString()), + (int) Double.parseDouble(result.get("height").toString()), + (int) Double.parseDouble(result.get("width").toString())); + } + + /** + * Gets accurate Y point for element + * + * @param element + * @param isTablet - + * @return Accurate Y point for element + */ + public static int getAccuratePointY(WebDriver driver, WebElement element, boolean isTablet) { + int windowDiff = + isTablet + ? getWindowHeightDiffBetweenAppiumAndSelenium(driver) + : (int) (getWindowHeightDiffBetweenAppiumAndSelenium(driver) / 1.5); + int seleniumWindowHeight = driver.manage().window().getSize().getHeight(); + double heightRatio = ((double) seleniumWindowHeight / getJavascriptWindowHeight(driver)); + return (int) ((heightRatio * getLocation(driver, element).getY()) + windowDiff) + + element.getSize().getHeight() / 2; + } + + /** + * @return the window height difference between Appium and Selenium + */ + public static int getWindowHeightDiffBetweenAppiumAndSelenium(WebDriver driver) { + int seleniumWindowHeight = driver.manage().window().getSize().getHeight(); + int appiumHeight = getAppiumWindowHeight(driver); + return appiumHeight - seleniumWindowHeight; + } + + /** + * @return JavaScript window height + */ + public static int getJavascriptWindowHeight(WebDriver driver) { + return ((Long) + ((JavascriptExecutor) driver) + .executeScript("return window.innerHeight || document.body.clientHeight")) + .intValue(); + } + + public static int getAppiumWindowHeight(WebDriver driver) { + String currentContext = getContext(driver); + changeContext(driver, "NATIVE_APP"); + int appiumHeight = + isAndroid(driver) + ? getAndroidDriver(driver).manage().window().getSize().getHeight() + : getIOSDriver(driver).manage().window().getSize().getHeight(); + changeContext(driver, currentContext); + return appiumHeight; + } + + public static boolean changeContext(WebDriver driver, String desiredContext) { + return isAndroid(driver) + ? changeContextAndroid(driver, desiredContext) + : changeContextIos(driver, desiredContext); + } + + private static boolean changeContextAndroid(WebDriver driver, String desiredContext) { + try { + Set contextNames = getAndroidDriver(driver).getContextHandles(); + Iterator contextNameIterator = contextNames.iterator(); + + String contextName; + do { + if (!contextNameIterator.hasNext()) { + return false; + } + + contextName = (String) contextNameIterator.next(); + } while (!contextName.contains(desiredContext)); + + logger.debug(String.format("Switching to context [%s].", contextName)); + getAndroidDriver(driver).context(contextName); + return true; + } catch (Exception var6) { + logger.error(String.format("Unable to switch to context [%s].", desiredContext), var6); + return false; + } + } + + private static boolean changeContextIos(WebDriver driver, String desiredContext) { + try { + Set contextNames = getIOSDriver(driver).getContextHandles(); + Iterator contextNameIterator = contextNames.iterator(); + + String contextName; + do { + if (!contextNameIterator.hasNext()) { + return false; + } + + contextName = (String) contextNameIterator.next(); + } while (!contextName.contains(desiredContext)); + + logger.debug(String.format("Switching to context [%s].", contextName)); + getIOSDriver(driver).context(contextName); + return true; + } catch (Exception var6) { + logger.error(String.format("Unable to switch to context [%s].", desiredContext), var6); + return false; + } + } + + public static String getContext(WebDriver driver) { + return isAndroid(driver) + ? getAndroidDriver(driver).getContext() + : getIOSDriver(driver).getContext(); + } + + private static boolean isAndroid(WebDriver driver) { + return driver instanceof AndroidDriver; + } + + private static AndroidDriver getAndroidDriver(WebDriver driver) { + return (AndroidDriver) driver; + } + + private static IOSDriver getIOSDriver(WebDriver driver) { + return (IOSDriver) driver; + } +} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebUtils.java new file mode 100644 index 0000000..176c723 --- /dev/null +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebUtils.java @@ -0,0 +1,48 @@ +/* + * + * Copyright © 2025 Applause App Quality, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.applause.auto.helpers.web; + +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebDriver; + +/** Web common utils */ +public class WebUtils { + + /** + * JS scroll down + * + * @param driver - automation driver + * @param yValue - y value scroll to + */ + public static void jsScrollDown(WebDriver driver, final int yValue) { + ((JavascriptExecutor) driver).executeScript("window.scrollBy(0, " + yValue + ");"); + } + + /** + * Get page Y position + * + * @param driver - automation driver + * @return Y page position value + */ + public static int getPagePositionY(WebDriver driver) { + String javascript = "return window.scrollY;"; + return (int) + Float.parseFloat( + String.valueOf(((JavascriptExecutor) driver).executeScript("return window.scrollY;"))); + } +} diff --git a/pom.xml b/pom.xml index 3b39690..7a515f9 100644 --- a/pom.xml +++ b/pom.xml @@ -70,6 +70,18 @@ 6.0.0 4.4 + 5.5.0 + 4.2.2 + 1.18.3 + 0.3.7 + 1.0.2 + 2.29.1 + 20250107 + 0.0.12 + 2.7.2 + v4-rev612-1.25.0 + 2.1.3 + 5.13.0 5.2.0 @@ -248,6 +260,61 @@ freemarker ${freemarker.version} + + io.rest-assured + rest-assured + ${rest-assured.version} + + + org.awaitility + awaitility + ${awaitility.version} + + + org.jsoup + jsoup + ${jsoup.version} + + + us.codecraft + xsoup + ${xsoup.version} + + + com.github.javafaker + javafaker + ${faker.version} + + + io.qameta.allure + allure-testng + ${allure.version} + + + org.json + json + ${json.version} + + + io.github.yaml-path + yaml-path + ${yaml-path.version} + + + com.google.api-client + google-api-client + ${google-api-client.version} + + + com.google.apis + google-api-services-sheets + ${google-api-services-sheets.version} + + + jakarta.mail + jakarta.mail-api + ${jakarta.mail-api.version} + From c0846eec08a122f0c8ebe8b7979ff89de8ea72cb Mon Sep 17 00:00:00 2001 From: Mark Gunlogson <31893232+noobgramming@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:44:56 -0600 Subject: [PATCH 2/2] AUT-6940 refactors for docs, java 21, deprecated calls, fixed 1000+ linter warnings :-| --- .gitignore | 4 + .../auto/helpers/AnalyticsHelper.java | 5 + .../auto/helpers/EnvironmentHelper.java | 13 +- .../helpers/allure/AllureDriverUtils.java | 78 +++---- .../auto/helpers/allure/AllureUtils.java | 44 ++-- .../appenders/AllureLogsToStepAppender.java | 75 ++++--- .../helpers/analytics/AnalyticsEntry.java | 22 ++ .../analytics/AnalyticsInterceptor.java | 13 +- .../auto/helpers/control/BrowserControl.java | 11 +- .../auto/helpers/control/DeviceControl.java | 11 +- .../auto/helpers/email/CommonMailClient.java | 146 +++++++----- .../auto/helpers/email/GmailHelper.java | 96 ++++---- .../auto/helpers/email/SearchCriteria.java | 20 +- .../helpers/google/GoogleSheetParser.java | 210 ++++++++---------- .../google/GoogleSheetParserException.java | 4 +- .../mapping/JacksonJSONRestObjectMapping.java | 12 +- .../RestApiDefaultRestAssuredApiHelper.java | 4 +- .../RestApiDefaultRestAssuredApiClient.java | 14 +- .../client/RestAssuredApiClient.java | 23 +- .../scanner/JiraAnnotationsScanner.java | 44 ++-- .../helpers/jira/clients/JiraXrayClient.java | 9 +- .../clients/modules/jira/JiraProjectAPI.java | 198 ++++++++--------- .../jira/clients/modules/jira/SearchAPI.java | 39 ++-- .../clients/modules/xray/ExecutionsAPI.java | 76 +++---- .../clients/modules/xray/IterationsAPI.java | 39 ++-- .../jira/clients/modules/xray/StepsAPI.java | 174 +++++++-------- .../jira/clients/modules/xray/TestrunAPI.java | 172 +++++++------- .../auto/helpers/jira/constants/Statuses.java | 2 +- .../helpers/jira/constants/XrayEndpoints.java | 8 +- .../auto/helpers/jira/dto/jql/Fields.java | 11 +- .../auto/helpers/jira/dto/jql/Issues.java | 10 +- .../jira/dto/jql/JqlFilteredResults.java | 10 +- .../jira/dto/requestmappers/Fields.java | 12 +- .../JiraCreateTicketRequest.java | 7 +- .../jira/dto/requestmappers/JiraFields.java | 18 +- .../dto/requestmappers/StepFieldsUpdate.java | 9 +- .../StepIterationAttachment.java | 9 +- .../jira/dto/requestmappers/XrayAddTo.java | 6 +- .../responsemappers/AvailableIssueTypes.java | 11 +- .../responsemappers/AvailableProjects.java | 17 +- .../JiraCreateTicketResponse.java | 9 +- .../responsemappers/XrayTestRunDetails.java | 40 ++-- .../responsemappers/iteration/Iteration.java | 8 +- .../iteration/IterationParameters.java | 7 +- .../iteration/TestRunIteration.java | 16 +- .../dto/responsemappers/steps/Action.java | 11 +- .../responsemappers/steps/Attachments.java | 17 +- .../dto/responsemappers/steps/Comment.java | 11 +- .../jira/dto/responsemappers/steps/Data.java | 12 +- .../dto/responsemappers/steps/Fields.java | 11 +- .../jira/dto/responsemappers/steps/Step.java | 21 +- .../jira/dto/responsemappers/steps/Steps.java | 8 +- .../jira/dto/responsemappers/steps/Value.java | 6 +- .../helpers/jira/dto/shared/Issuetype.java | 7 +- .../auto/helpers/jira/dto/shared/Project.java | 7 +- .../exceptions/JiraAnnotationException.java | 5 +- .../JiraPropertiesFileException.java | 2 +- .../UnidentifiedExecutionStatusException.java | 2 +- .../auto/helpers/jira/helper/FilesHelper.java | 39 +++- .../jira/helper/ResponseValidator.java | 11 +- .../restclient/XrayRestAssuredClient.java | 22 +- .../helpers/mobile/MobileContextsUtils.java | 31 ++- .../helpers/mobile/MobileElementUtils.java | 21 +- .../auto/helpers/mobile/MobileUtils.java | 75 ++++--- .../deeplinks/MobileDeepLinksUtils.java | 32 +-- .../NativeMobileAppCommonDeeplink.java | 46 ++-- .../saucelabs}/FileUploadingHelper.java | 84 ++++--- .../helpers/sync/all/AllMatchCondition.java | 16 +- .../sync/all/AllMatchConditionBuilder.java | 11 +- .../helpers/testdata/TestDataProvider.java | 12 +- .../testdata/yaml/YamlTestDataProvider.java | 21 +- .../helpers/util/AwaitilityWaitUtils.java | 22 +- .../auto/helpers/util/RandomUtils.java | 34 ++- .../auto/helpers/util/ThreadHelper.java | 8 +- .../XrayRequestHeaders.java | 9 +- .../applause/auto/helpers/web/HtmlUtils.java | 16 +- .../auto/helpers/web/WebElementUtils.java | 195 +++++++++++----- .../applause/auto/helpers/web/WebUtils.java | 12 +- auto-sdk-java-integrations/pom.xml | 8 +- .../auto/testng/TestNgContextUtils.java | 2 +- .../auto/testng}/listeners/XrayListener.java | 68 +++--- build-tools/checkstyle.xml | 6 +- pom.xml | 21 +- 83 files changed, 1457 insertions(+), 1251 deletions(-) rename auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/{file_uploading/SauceLabs => fileuploading/saucelabs}/FileUploadingHelper.java (61%) rename auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/{jira/requestData => util}/XrayRequestHeaders.java (85%) rename {auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira => auto-sdk-java-testng/src/main/java/com/applause/auto/testng}/listeners/XrayListener.java (68%) diff --git a/.gitignore b/.gitignore index 24a4cb4..fa71811 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,7 @@ Temporary Items !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json + + +# =============================== Allure reporting =============================== +allure-results/ \ No newline at end of file diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/AnalyticsHelper.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/AnalyticsHelper.java index 00b6282..a60ca7f 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/AnalyticsHelper.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/AnalyticsHelper.java @@ -781,6 +781,11 @@ public List getRequestsWithMultipleFilt return filtered; } + /** + * Returns a new instance of {@link AnalyticsInterceptor} associated with this object. + * + * @return A new {@link AnalyticsInterceptor} instance. + */ public AnalyticsInterceptor getInterceptor() { return new AnalyticsInterceptor(this); } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/EnvironmentHelper.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/EnvironmentHelper.java index d5e3f19..1a5e8f8 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/EnvironmentHelper.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/EnvironmentHelper.java @@ -21,7 +21,6 @@ import com.applause.auto.context.IPageObjectExtension; import com.applause.auto.data.enums.DriverType; import java.util.Locale; -import lombok.AllArgsConstructor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.openqa.selenium.remote.RemoteWebDriver; @@ -31,10 +30,18 @@ * driver type, which browser, which version. */ @SuppressWarnings("checkstyle:AbbreviationAsWordInName") -@AllArgsConstructor public class EnvironmentHelper implements IPageObjectExtension { private static final Logger logger = LogManager.getLogger(EnvironmentHelper.class); - private IPageObjectContext pageObjectContext; + private final IPageObjectContext pageObjectContext; + + /** + * Constructor for EnvironmentHelper. + * + * @param pageObjectContext The {@link IPageObjectContext} to use. + */ + public EnvironmentHelper(final IPageObjectContext pageObjectContext) { + this.pageObjectContext = pageObjectContext; + } /** * determine the cast class type of the created driver object and set the DriverType into the diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureDriverUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureDriverUtils.java index 87e5465..388eb63 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureDriverUtils.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureDriverUtils.java @@ -19,67 +19,69 @@ import io.qameta.allure.Allure; import java.io.ByteArrayInputStream; -import java.util.Objects; +import lombok.NonNull; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.openqa.selenium.OutputType; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; -/** Some common allure utils (with attachments) */ -public class AllureDriverUtils { +/** Provides utility methods for attaching information to Allure reports. */ +public final class AllureDriverUtils { - private static final Logger logger = LogManager.getLogger(AllureDriverUtils.class); + private static final Logger LOGGER = LogManager.getLogger(AllureDriverUtils.class); + private static final String SCREENSHOT_ATTACHMENT = "Screenshot attachment"; + private static final String IMAGE_PNG = "image/png"; + private static final String CURRENT_URL = "Current URL"; + private static final String TEXT_PLAIN = "text/plain"; + private static final String CURRENT_PAGE_SOURCE = "Current page source"; + private static final String LOG_EXTENSION = ".log"; + + private AllureDriverUtils() { + // Utility class - no public constructor + } /** - * Attach driver screenshot + * Attaches a screenshot to the Allure report. * - * @param driver - automation driver + * @param driver The WebDriver instance to capture the screenshot from. */ - public static void attachScreenshot(WebDriver driver) { - if (Objects.nonNull(driver)) { - logger.info("Taking screenshot on test failure"); - try { - Allure.addAttachment( - "Screenshot attachment", - "image/png", - new ByteArrayInputStream(((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES)), - "png"); - } catch (Exception e) { - logger.error("Error taking screenshot: " + e.getMessage()); - } + public static void attachScreenshot(@NonNull final WebDriver driver) { + LOGGER.info("Taking screenshot on test failure"); + try { + var screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES); + Allure.addAttachment( + SCREENSHOT_ATTACHMENT, IMAGE_PNG, new ByteArrayInputStream(screenshot), "png"); + } catch (Exception e) { + LOGGER.error("Error taking screenshot: {}", e.getMessage()); } } /** - * Attach driver url + * Attaches the current URL to the Allure report. * - * @param driver - automation driver + * @param driver The WebDriver instance to get the current URL from. */ - public static void attachCurrentURL(WebDriver driver) { - if (Objects.nonNull(driver)) { - logger.info("Taking current URL"); - try { - Allure.addAttachment("Current URL", "text/plain", driver.getCurrentUrl(), ".log"); - } catch (Exception e) { - logger.error("Error taking current URL: " + e.getMessage()); - } + public static void attachCurrentURL(@NonNull final WebDriver driver) { + LOGGER.info("Attaching current URL"); + try { + Allure.addAttachment(CURRENT_URL, TEXT_PLAIN, driver.getCurrentUrl(), LOG_EXTENSION); + } catch (Exception e) { + LOGGER.error("Error taking current URL: {}", e.getMessage()); } } /** - * Attach driver page source + * Attaches the current page source to the Allure report. * - * @param driver - automation driver + * @param driver The WebDriver instance to get the page source from. */ - public static void attachCurrentPageSourceOnFailure(WebDriver driver) { - if (Objects.nonNull(driver)) { - logger.info("Taking page source"); - try { - Allure.addAttachment("Current page source", "text/plain", driver.getPageSource(), ".log"); - } catch (Exception e) { - logger.error("Error taking current page source: " + e.getMessage()); - } + public static void attachCurrentPageSourceOnFailure(@NonNull final WebDriver driver) { + LOGGER.info("Attaching page source"); + try { + Allure.addAttachment(CURRENT_PAGE_SOURCE, TEXT_PLAIN, driver.getPageSource(), LOG_EXTENSION); + } catch (Exception e) { + LOGGER.error("Error taking current page source: {}", e.getMessage()); } } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureUtils.java index 3aff49d..0b9bf50 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureUtils.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/AllureUtils.java @@ -17,36 +17,42 @@ */ package com.applause.auto.helpers.allure; -import java.io.File; import java.io.IOException; -import org.apache.commons.io.FileUtils; +import java.nio.file.Files; +import java.nio.file.Path; +import lombok.NonNull; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -public class AllureUtils { +/** Utility class for Allure reporting. */ +public final class AllureUtils { private static final Logger logger = LogManager.getLogger(AllureUtils.class); + private static final String CATEGORIES_JSON = "categories.json"; + + private AllureUtils() {} /** - * Copy Allure categorisation .json file to report directory - * https://docs.qameta.io/allure/#_categories_2 + * Copies the Allure defects categorization JSON file to the Allure report directory. * - * @throws IOException + * @param allureReportPath The path to the Allure report directory. + * @param allureDefectsCategorisationFilePath The path to the Allure defects categorization JSON + * file. + * @throws IOException If an I/O error occurs. */ public static void addAllureDefectsCategoriesConfiguration( - String allureReportPath, String allureDefectsCategorisationFilePath) { - try { - logger.info("Copy defects configs to categories.json file for Allure"); - File categoriesAllureFile = new File(allureReportPath + "/categories.json"); - if (!categoriesAllureFile.exists()) { - categoriesAllureFile.createNewFile(); - } - categoriesAllureFile.setReadable(true); - categoriesAllureFile.setWritable(true); - FileUtils.copyFile(new File(allureDefectsCategorisationFilePath), categoriesAllureFile); - - } catch (Exception e) { - logger.error("Error creating allure categories.json file " + e.getMessage()); + @NonNull final String allureReportPath, + @NonNull final String allureDefectsCategorisationFilePath) + throws IOException { + + logger.info("Copying defects configs to {} file for Allure", CATEGORIES_JSON); + + final Path categoriesAllureFile = Path.of(allureReportPath, CATEGORIES_JSON); + + if (!Files.exists(categoriesAllureFile)) { + Files.createFile(categoriesAllureFile); } + + Files.copy(Path.of(allureDefectsCategorisationFilePath), categoriesAllureFile); } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/appenders/AllureLogsToStepAppender.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/appenders/AllureLogsToStepAppender.java index 518c1c5..57cec38 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/appenders/AllureLogsToStepAppender.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/allure/appenders/AllureLogsToStepAppender.java @@ -25,78 +25,79 @@ import java.util.concurrent.ConcurrentSkipListSet; import org.apache.logging.log4j.core.*; import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Node; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.layout.PatternLayout; -/** Log4j2 logs to Allure step definition appender */ +/** Appender for logging to Allure steps. */ @Plugin( name = "AllureLogsToStepAppender", - category = Core.CATEGORY_NAME, + category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE) -public class AllureLogsToStepAppender extends AbstractAppender { +public final class AllureLogsToStepAppender extends AbstractAppender { - /** storage for filtered packages */ - private static Set filteredPackagesToAppendSet; + private static final String COMMA = ","; + private static final Set FILTERED_PACKAGES_TO_APPEND_SET = new ConcurrentSkipListSet<>(); - protected AllureLogsToStepAppender( - String name, Filter filter, Layout layout, boolean ignoreExceptions) { + private AllureLogsToStepAppender( + final String name, + final Filter filter, + final Layout layout, + final boolean ignoreExceptions) { super(name, filter, layout, ignoreExceptions, null); } /** - * Create log4j2 AllureLogsToStepAppender appender method + * Creates an AllureLogsToStepAppender. * - * @param name appender name - * @param layout appender layout - * @param filter appender filter - * @param filteredPackagesToAppend filter packages to append appender, should be provided as comma - * separated list - * @return + * @param name The name of the appender. + * @param layout The layout to use for the appender. + * @param filter The filter to use for the appender. + * @param filteredPackagesToAppend A comma-separated list of packages to filter. + * @return A new AllureLogsToStepAppender instance, or null if the name is null. */ @PluginFactory public static AllureLogsToStepAppender createAppender( - @PluginAttribute("name") String name, - @PluginElement("Layout") Layout layout, + @PluginAttribute("name") final String name, + @PluginElement("Layout") final Layout layout, @PluginElement("Filter") final Filter filter, - @PluginAttribute("filteredPackagesToAppend") String filteredPackagesToAppend) { - if (Objects.isNull(name)) { + @PluginAttribute("filteredPackagesToAppend") final String filteredPackagesToAppend) { + + if (name == null) { return null; } - if (Objects.isNull(layout)) { - layout = PatternLayout.createDefaultLayout(); - } - if (Objects.nonNull(filteredPackagesToAppend)) { - filteredPackagesToAppendSet = - new ConcurrentSkipListSet<>(Arrays.asList(filteredPackagesToAppend.split(","))); + + final var usedLayout = Objects.requireNonNullElse(layout, PatternLayout.createDefaultLayout()); + + if (filteredPackagesToAppend != null) { + FILTERED_PACKAGES_TO_APPEND_SET.addAll(Arrays.asList(filteredPackagesToAppend.split(COMMA))); } - return new AllureLogsToStepAppender(name, filter, layout, true); + + return new AllureLogsToStepAppender(name, filter, usedLayout, true); } /** - * log4j2 append method for Allure step appender + * Appends a log event to Allure step. * - * @param event logging log4j2 intercepted event + * @param event The log event to append. */ @Override - public void append(LogEvent event) { + public void append(final LogEvent event) { if (isSourcePackageValidForAppender(event)) { Allure.step(event.getMessage().getFormattedMessage()); } } - private boolean isSourcePackageValidForAppender(LogEvent event) { - if (filteredPackagesToAppendSet.isEmpty()) { + private boolean isSourcePackageValidForAppender(final LogEvent event) { + if (FILTERED_PACKAGES_TO_APPEND_SET.isEmpty()) { return true; } - String sourceClassName = event.getSource().getClassName(); - return filteredPackagesToAppendSet.stream() - .filter( - filteredPackagesToAppendSetItem -> - sourceClassName.contains(filteredPackagesToAppendSetItem)) - .findAny() - .isPresent(); + + final var sourceClassName = event.getSource().getClassName(); + + return FILTERED_PACKAGES_TO_APPEND_SET.stream().anyMatch(sourceClassName::contains); } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/analytics/AnalyticsEntry.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/analytics/AnalyticsEntry.java index 6b7e86e..b161753 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/analytics/AnalyticsEntry.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/analytics/AnalyticsEntry.java @@ -36,11 +36,23 @@ public class AnalyticsEntry extends LogEntry { private final JsonObject parsedMessage; + /** + * Constructor for AnalyticsEntry. + * + * @param level The log level. + * @param timestamp The timestamp of the log entry. + * @param message The log message. + */ public AnalyticsEntry(final Level level, final long timestamp, final String message) { super(level, timestamp, message); this.parsedMessage = new Gson().fromJson(message, JsonObject.class); } + /** + * Gets the webview GUID. + * + * @return The webview GUID, or null if not found. + */ public String getWebView() { return Optional.ofNullable(parsedMessage) .map(body -> body.get("webview")) @@ -48,6 +60,11 @@ public String getWebView() { .orElse(null); } + /** + * Gets the method of the analytics call. + * + * @return The method, or null if not found. + */ public String getMethod() { return Optional.ofNullable(parsedMessage) .map(body -> body.getAsJsonObject("message")) @@ -56,6 +73,11 @@ public String getMethod() { .orElse(null); } + /** + * Gets the parameters of the analytics call. + * + * @return The parameters as a JsonObject, or null if not found. + */ public JsonObject getParams() { return Optional.ofNullable(parsedMessage) .map(body -> body.getAsJsonObject("message")) diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/analytics/AnalyticsInterceptor.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/analytics/AnalyticsInterceptor.java index 2faf3b2..6ed2b50 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/analytics/AnalyticsInterceptor.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/analytics/AnalyticsInterceptor.java @@ -21,7 +21,6 @@ import com.applause.auto.pageobjectmodel.base.ComponentInterceptor; import java.lang.reflect.Method; import java.util.concurrent.Callable; -import lombok.AllArgsConstructor; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.implementation.bind.annotation.AllArguments; import net.bytebuddy.implementation.bind.annotation.Origin; @@ -34,10 +33,18 @@ * This interceptor is added to classes in the PageObjectFactory to facilitate running code before * and after methods. Currently, the only use case is for the @AnalyticsCall annotation. */ -@AllArgsConstructor @SuppressWarnings("PMD.SignatureDeclareThrowsException") // since we're intercepting this is okay public class AnalyticsInterceptor extends ComponentInterceptor { - private AnalyticsHelper analyticsHelper; + private final AnalyticsHelper analyticsHelper; + + /** + * Constructs a new AnalyticsInterceptor. + * + * @param analyticsHelper The AnalyticsHelper instance to use for analytics reporting. + */ + public AnalyticsInterceptor(final AnalyticsHelper analyticsHelper) { + this.analyticsHelper = analyticsHelper; + } /** * Methods in a page object class that meet the criteria in match() are subject to the extra logic diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/control/BrowserControl.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/control/BrowserControl.java index 611745d..7552af4 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/control/BrowserControl.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/control/BrowserControl.java @@ -24,7 +24,6 @@ import java.io.IOException; import java.net.URL; import java.nio.charset.StandardCharsets; -import lombok.AllArgsConstructor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.openqa.selenium.Dimension; @@ -35,11 +34,19 @@ * mouse-downs. */ @SuppressWarnings({"checkstyle:ParameterName", "checkstyle:AbbreviationAsWordInName"}) -@AllArgsConstructor public class BrowserControl implements IPageObjectExtension { private static final Logger logger = LogManager.getLogger(); private final IPageObjectContext context; + /** + * Constructor for BrowserControl. + * + * @param context The {@link IPageObjectContext} to use. + */ + public BrowserControl(final IPageObjectContext context) { + this.context = context; + } + /** * Gets the current driver as a JavascriptExecutor. * diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/control/DeviceControl.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/control/DeviceControl.java index 06891e4..2866139 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/control/DeviceControl.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/control/DeviceControl.java @@ -32,7 +32,6 @@ import java.util.List; import java.util.Objects; import java.util.Set; -import lombok.AllArgsConstructor; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -53,11 +52,19 @@ "checkstyle:MultipleStringLiterals", "checkstyle:LocalVariableName" }) -@AllArgsConstructor public class DeviceControl implements IPageObjectExtension { private static final Logger logger = LogManager.getLogger(); private final IPageObjectContext context; + /** + * Constructor for DeviceControl. + * + * @param context The {@link IPageObjectContext} to use. + */ + public DeviceControl(final IPageObjectContext context) { + this.context = context; + } + /** * Checks if the current driver is an Android driver. * diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/CommonMailClient.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/CommonMailClient.java index d373a9f..33e27c7 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/CommonMailClient.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/CommonMailClient.java @@ -24,27 +24,37 @@ import java.io.IOException; import java.io.InputStream; import java.util.Arrays; +import java.util.Locale; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; +import lombok.NonNull; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** Provides some common methods between most email clients. */ -public abstract class CommonMailClient { +public class CommonMailClient { private String host; - private Properties props = new Properties(); - private Store store; + private final Properties props = new Properties(); + private final Store store; private String protocol; private String userName; private String password; private static final Logger logger = LogManager.getLogger(CommonMailClient.class); /** - * Default constructor with params Example: username = some@email.com password = xxxx protocol = - * imaps host = imap.gmail.com + * Constructs a new CommonMailClient with the specified username, password, protocol, and host. + * Example: username = some@email.com, password = xxxx, protocol = imaps, host = imap.gmail.com + * + * @param userName The username for the mail account. + * @param password The password for the mail account. + * @param protocol The protocol to use for connecting to the mail server. + * @param host The hostname of the mail server. + * @throws MessagingException If an error occurs during the setup of mail credentials. */ - public CommonMailClient(String userName, String password, Protocol protocol, String host) { + public CommonMailClient( + final String userName, final String password, final Protocol protocol, final String host) + throws MessagingException { this.userName = userName.trim(); this.password = password; this.protocol = protocol.getValue(); @@ -59,17 +69,17 @@ public CommonMailClient(String userName, String password, Protocol protocol, Str * @param regex The regular expression to use * @return The extracted text after applying the regex */ - public String extractWithRegexFromString(String text, String regex) { + public String extractWithRegexFromString(final String text, @NonNull final String regex) { if (text.isEmpty()) { logger.info("Text is empty"); return null; } - logger.info("Extracting with regex = [" + regex + "..."); + logger.info("Extracting with regex = [{}...", regex); Pattern pattern = Pattern.compile(regex, Pattern.DOTALL); Matcher matcher = pattern.matcher(text); if (matcher.find()) { - logger.info("Extracted text is:: " + matcher.group(1).trim()); + logger.info("Extracted text is:: {}", matcher.group(1).trim()); return matcher.group(1).trim(); } throw new RuntimeException("No matches were found in the text."); @@ -82,33 +92,18 @@ public String extractWithRegexFromString(String text, String regex) { * * @param resourceFile an InputStream that represents a .properties files that includes all the * above-mentioned properties. + * @throws MessagingException If an error occurs during mail session setup. */ - public CommonMailClient(InputStream resourceFile) { + public CommonMailClient(@NonNull final InputStream resourceFile) throws MessagingException { store = setMailCredentials(resourceFile); } - /** - * Initialize properties for current mailbox example setMailCredentials(new - * InputStream("/xxx/xxx/propertyFile")) - * - * @param resourceStream an InputStream that represents a .properties files that includes all the - * above-mentioned properties. - * @return - */ - private Store setMailCredentials(InputStream resourceStream) { - if (props.isEmpty()) { - setProperties(resourceStream); - } - Session session = Session.getDefaultInstance(props, null); - return getStore(session); - } - /** * Deletes all the read emails from the mentioned folder of the email account. * * @param emailFolder The email folder to use. */ - public void emptyAllReadEmailsFromInbox(Folder emailFolder) { + public void emptyAllReadEmailsFromInbox(@NonNull final Folder emailFolder) { deleteReadEmails(emailFolder); } @@ -141,35 +136,38 @@ public Folder markAllEmailsAsRead() { * @throws MessagingException Thrown in case an error happens when getting any details from the * email. */ - protected boolean doesEmailMatchCriteria(Message email, SearchCriteria criteria) + protected boolean doesEmailMatchCriteria( + @NonNull final Message email, @NonNull final SearchCriteria criteria) throws MessagingException { - if (criteria.getEmailSubject() != null && !criteria.getEmailSubject().isEmpty()) { + if (criteria.emailSubject() != null && !criteria.emailSubject().isEmpty()) { // check email subject - boolean subjectMatch = email.getSubject().matches(criteria.getEmailSubject()); + boolean subjectMatch = email.getSubject().matches(criteria.emailSubject()); // return if false, otherwise continue if (!subjectMatch) { return false; } } - if (criteria.getSentFrom() != null && !criteria.getSentFrom().isEmpty()) { + if (criteria.sentFrom() != null && !criteria.sentFrom().isEmpty()) { // check sentFrom boolean sentFromMatch = Arrays.stream(email.getFrom()) .sequential() - .anyMatch(from -> from.toString().contains(criteria.getSentFrom().toLowerCase())); + .anyMatch( + from -> + from.toString().contains(criteria.sentFrom().toLowerCase(Locale.ENGLISH))); // return if false, otherwise continue if (!sentFromMatch) { return false; } } - if (criteria.getSentTo() != null && !criteria.getSentTo().isEmpty()) { + if (criteria.sentTo() != null && !criteria.sentTo().isEmpty()) { // check sentTo // return if false, otherwise continue return Arrays.stream(email.getAllRecipients()) .sequential() - .anyMatch(to -> to.toString().contains(criteria.getSentTo().toLowerCase())); + .anyMatch(to -> to.toString().contains(criteria.sentTo().toLowerCase(Locale.ENGLISH))); } // email matches the whole criteria @@ -185,7 +183,7 @@ protected boolean doesEmailMatchCriteria(Message email, SearchCriteria criteria) * above-mentioned properties. * @return mailProperties */ - private Properties setProperties(InputStream resourceStream) { + private Properties setProperties(@NonNull final InputStream resourceStream) { try { props.load(resourceStream); this.userName = props.getProperty("username"); @@ -201,51 +199,79 @@ private Properties setProperties(InputStream resourceStream) { } /** - * @param folderName mail folder name example INBOX - * @param accessMode check Folder.to get which mode exists - * @return required Folder + * Opens a specified mail folder with the given access rights. + * + * @param folderName The name of the mail folder to open (e.g., "INBOX"). + * @param accessMode The access mode to use when opening the folder. See {@link + * jakarta.mail.Folder} for available modes. + * @return The opened {@link jakarta.mail.Folder} object, or null if an error occurred. + * @throws MessagingException If an error occurs while opening the folder. */ - public Folder openSelectedFolderWithRights(String folderName, int accessMode) { + public Folder openSelectedFolderWithRights(@NonNull final String folderName, final int accessMode) + throws MessagingException { Folder inbox = null; try { inbox = store.getFolder(folderName); + inbox.open(accessMode); // Open the folder immediately after retrieving it + } catch (MessagingException e1) { - e1.printStackTrace(); - } - try { - inbox.open(accessMode); - } catch (MessagingException e1) { - e1.printStackTrace(); + logger.error(e1); + throw e1; // Re-throw the exception after logging it. This is crucial for proper error + // handling. } return inbox; } /** * Initialize properties for current mailbox. Username, password, protocol, and host should be set - * in advance + * in advance. + * + * @return The initialized Store object. */ - private Store setMailCredentials() { + private Store setMailCredentials() throws MessagingException { Session session = Session.getDefaultInstance(getProperties(), null); return getStore(session); } - private Store getStore(Session session) { - Store store = null; + /** + * Initialize properties for current mailbox example setMailCredentials(new + * InputStream("/xxx/xxx/propertyFile")) + * + * @param resourceStream an InputStream that represents a .properties files that includes all the + * above-mentioned properties. + * @return mail credential store + */ + private Store setMailCredentials(@NonNull final InputStream resourceStream) + throws MessagingException { + if (props.isEmpty()) { + setProperties(resourceStream); + } + Session session = Session.getDefaultInstance(props, null); + return getStore(session); + } + + private Store getStore(@NonNull final Session session) throws MessagingException { + Store storeLocal = null; try { - store = session.getStore(protocol); + storeLocal = session.getStore(protocol); } catch (NoSuchProviderException e1) { - e1.printStackTrace(); + logger.error(e1); + throw e1; } try { - store.connect(host, userName, password); + storeLocal.connect(host, userName, password); } catch (MessagingException e1) { - e1.printStackTrace(); + logger.error(e1); + throw e1; } - return store; + return storeLocal; } /** - * Creating properties instance Username, password, protocol, and host should be set in advance + * Creates a Properties instance with pre-set username, password, protocol, and host. Username, + * password, protocol, and host should be set in advance. + * + * @return A Properties instance containing the username, password, protocol, and host. */ private Properties getProperties() { Properties properties = new Properties(); @@ -264,14 +290,14 @@ private Properties getProperties() { * @param folder The email folder to check. * @return unread emails for selected folder */ - public Message[] getUnreadEmails(Folder folder) { + public Message[] getUnreadEmails(@NonNull final Folder folder) { Message[] emails = new Message[0]; try { Flags seen = new Flags(Flags.Flag.SEEN); FlagTerm unseenFlagTerm = new FlagTerm(seen, false); emails = folder.search(unseenFlagTerm); } catch (MessagingException e1) { - e1.printStackTrace(); + logger.error(e1); } return emails; @@ -282,7 +308,7 @@ public Message[] getUnreadEmails(Folder folder) { * * @param folder The email folder to check. */ - public void deleteReadEmails(Folder folder) { + public void deleteReadEmails(@NonNull final Folder folder) { Message[] emails; try { Flags seen = new Flags(Flags.Flag.SEEN); @@ -290,7 +316,7 @@ public void deleteReadEmails(Folder folder) { emails = folder.search(seenFlagTerm); folder.setFlags(emails, new Flags(Flags.Flag.DELETED), true); } catch (MessagingException e1) { - e1.printStackTrace(); + logger.error(e1); } } @@ -300,7 +326,7 @@ public void closeConnection() { store.close(); } catch (MessagingException e) { logger.error("Error occurred when closing connection."); - e.printStackTrace(); + logger.error(e); } } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/GmailHelper.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/GmailHelper.java index 24135ff..5fd9bd1 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/GmailHelper.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/GmailHelper.java @@ -25,7 +25,11 @@ import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMultipart; import java.io.IOException; +import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; +import lombok.NonNull; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.openqa.selenium.WebDriver; @@ -43,18 +47,23 @@ public class GmailHelper extends CommonMailClient { * *

PLEASE NOTE: * - *

To make this work you should use gmail appPassword. + *

To make this work you should use a Gmail app password. * - *

For more information how to set it, please check + *

For more information on how to set it, please check * https://support.google.com/accounts/answer/185833?hl=en * *

Example: username = some@email.com password = xxxx + * + * @param username The Gmail username (email address). Must not be null. + * @param appPassword The Gmail app password. */ - public GmailHelper(String username, String appPassword) { + public GmailHelper(final String username, final String appPassword) throws MessagingException { super(username.trim(), appPassword, Protocol.IMAP, "imap.gmail.com"); } - public GmailHelper(String username, String appPassword, Protocol protocol, String host) { + public GmailHelper( + final String username, final String appPassword, final Protocol protocol, final String host) + throws MessagingException { super(username, appPassword, protocol, host); } @@ -76,11 +85,11 @@ public void emptyEmailBox() { * @param markEmailAsSeen If true, the email would be marked as seen after checking it. * @return A message array that contains all the emails that match the mentioned criteria. */ - public Message[] waitForEmailToArrive( - WebDriver driver, - SearchCriteria criteria, - boolean checkOnlyUnreadEmails, - boolean markEmailAsSeen) { + public List waitForEmailToArrive( + @NonNull final WebDriver driver, + @NonNull final SearchCriteria criteria, + final boolean checkOnlyUnreadEmails, + final boolean markEmailAsSeen) { return waitForEmailToArrive( driver, criteria, checkOnlyUnreadEmails, markEmailAsSeen, TEN_MINUTES_TIMEOUT_WAIT_MILLI); } @@ -98,27 +107,25 @@ public Message[] waitForEmailToArrive( * find the email. * @return A message array that contains all the emails that match the mentioned criteria. */ - public Message[] waitForEmailToArrive( - WebDriver driver, - SearchCriteria criteria, - boolean checkOnlyUnreadEmails, - boolean markEmailAsSeen, - long timeOutInMillis) { + public List waitForEmailToArrive( + @Nullable final WebDriver driver, + @NonNull final SearchCriteria criteria, + final boolean checkOnlyUnreadEmails, + final boolean markEmailAsSeen, + final long timeOutInMillis) { logger.info("Waiting for email to arrive..."); int timeElapsed = 0; - Message[] foundEmails = null; + List foundEmails = null; while (timeElapsed < timeOutInMillis) { foundEmails = isEmailReceived(criteria, checkOnlyUnreadEmails, markEmailAsSeen); - if (foundEmails.length == 0) { + if (foundEmails.isEmpty()) { logger.info( - "Email not received after " - + timeElapsed / 1000 - + " seconds. Retrying after 5 seconds..."); + "Email not received after {} seconds. Retrying after 5 seconds...", timeElapsed / 1000); ThreadHelper.sleep(FIVE_SECOND_WAIT_MILLI); timeElapsed += FIVE_SECOND_WAIT_MILLI; - /** + /* * Get the session ID to prevent remote Cloud provider from closing the connection for being * idle (Usually timeouts after 90 seconds). */ @@ -132,7 +139,7 @@ public Message[] waitForEmailToArrive( } } - /** + /* * Shutdown hook to make sure the email server connection is terminated correctly before the * application terminates. */ @@ -155,7 +162,7 @@ public Message[] waitForEmailToArrive( * @param email The email object to check * @return A string representing the content of the concatenated parts of the email object. */ - public String parseEmailParts(Message email) { + public String parseEmailParts(@NonNull final Message email) { String result = ""; try { if (email.isMimeType("text/plain")) { @@ -179,40 +186,40 @@ public String parseEmailParts(Message email) { * @param email The email object to check * @return A string representation to the email's input stream. */ - public String parseEmailFromInputStream(Message email) { + public String parseEmailFromInputStream(@NonNull final Message email) { try { - return new String(email.getInputStream().readAllBytes()); + return new String(email.getInputStream().readAllBytes(), Charset.defaultCharset()); } catch (Exception e) { - e.printStackTrace(); + logger.error(e); } return null; } /** - * Extracts the text from multipart emails + * Extracts the text from multipart emails. * * @param mimeMultipart The multipart email to get the content from. - * @return A string representing the email content - * @throws MessagingException - * @throws IOException + * @return A string representing the email content. + * @throws MessagingException If a messaging exception occurs. + * @throws IOException If an I/O exception occurs. */ - private String getTextFromMimeMultipart(MimeMultipart mimeMultipart) + private String getTextFromMimeMultipart(@NonNull final MimeMultipart mimeMultipart) throws MessagingException, IOException { - String result = ""; + StringBuilder result = new StringBuilder(); int count = mimeMultipart.getCount(); for (int i = 0; i < count; i++) { BodyPart bodyPart = mimeMultipart.getBodyPart(i); if (bodyPart.isMimeType("text/plain")) { - result += "\n" + bodyPart.getContent(); + result.append('\n').append(bodyPart.getContent()); break; // without break same text appears twice in my tests } else if (bodyPart.isMimeType("text/html")) { String html = (String) bodyPart.getContent(); - result += "\n" + org.jsoup.Jsoup.parse(html).text(); + result.append('\n').append(org.jsoup.Jsoup.parse(html).text()); } else if (bodyPart.getContent() instanceof MimeMultipart) { - result += getTextFromMimeMultipart((MimeMultipart) bodyPart.getContent()); + result.append(getTextFromMimeMultipart((MimeMultipart) bodyPart.getContent())); } } - return result; + return result.toString(); } /** @@ -225,9 +232,11 @@ private String getTextFromMimeMultipart(MimeMultipart mimeMultipart) * @param markEmailAsSeen If true, the email would be marked as seen after checking it. * @return A message array that contains all the emails that match the mentioned criteria. */ - public Message[] isEmailReceived( - SearchCriteria criteria, boolean checkOnlyUnreadEmails, boolean markEmailAsSeen) { - ArrayList matchedEmails = new ArrayList<>(); + public List isEmailReceived( + @NonNull final SearchCriteria criteria, + final boolean checkOnlyUnreadEmails, + final boolean markEmailAsSeen) { + final var matchedEmails = new ArrayList(); try { Message[] emails; if (checkOnlyUnreadEmails) { @@ -238,7 +247,7 @@ public Message[] isEmailReceived( if (emails.length == 0) { logger.info("No emails found, moving on..."); - return null; + return List.of(); } logger.info("Found emails: " + emails.length); @@ -247,7 +256,7 @@ public Message[] isEmailReceived( logger.info("---------------------------------"); logger.info("Email Number: [{}]", i + 1); logger.info("Subject: [{}]", email.getSubject()); - logger.info("From: [{}]", email.getFrom()); + logger.info("From: [{}]", (Object) email.getFrom()); // check if the email matches the criteria if (doesEmailMatchCriteria(email, criteria)) { @@ -258,10 +267,11 @@ public Message[] isEmailReceived( email.setFlag(Flag.SEEN, true); } } + return matchedEmails; } catch (Exception e) { - logger.info("An exception was thrown. Message = " + e.getMessage()); + logger.info("An exception was thrown.", e); } - return matchedEmails.toArray(new Message[matchedEmails.size()]); + return List.of(new Message[0]); } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/SearchCriteria.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/SearchCriteria.java index 8fbb33f..8e69861 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/SearchCriteria.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/email/SearchCriteria.java @@ -17,15 +17,11 @@ */ package com.applause.auto.helpers.email; -import lombok.AllArgsConstructor; -import lombok.Data; - -/** Simple POJO that represents the search criteria when searching for emails. */ -@AllArgsConstructor -@Data -public class SearchCriteria { - - private String emailSubject; - private String sentFrom; - private String sentTo; -} +/** + * Simple record that represents the search criteria when searching for emails. + * + * @param emailSubject The subject of the email to search for. + * @param sentFrom The sender of the email to search for. + * @param sentTo The recipient of the email to search for. + */ +public record SearchCriteria(String emailSubject, String sentFrom, String sentTo) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParser.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParser.java index 66a8937..171f96f 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParser.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParser.java @@ -28,180 +28,156 @@ import com.google.api.services.sheets.v4.model.*; import java.io.IOException; import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.util.*; -import java.util.stream.Collectors; +import java.util.Collections; +import java.util.List; +import java.util.Objects; import lombok.Getter; +import lombok.NonNull; import lombok.SneakyThrows; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** - * Google Sheet parser (from Google Drive) [Requires some configuration management from Google Cloud - * side] Some common tutorials: + * Utility class for parsing Google Sheets. Requires configuration management on the Google Cloud + * side. */ -public class GoogleSheetParser { +@SuppressWarnings("PMD.LooseCoupling") +public final class GoogleSheetParser { private static final Logger logger = LogManager.getLogger(GoogleSheetParser.class); + private static final String KEY_PARAMETER = "key"; - @Getter private String sheetId; - - @Getter private String googleSheetParsingCloudConfiguredApplicationName; - - private Sheets service; + @Getter private final String sheetId; + @Getter private final String applicationName; + private final Sheets service; /** - * Create sheet parser using API key access That will work once Google sheet document is available - * for sharing with "Everyone with a link" option + * Constructs a GoogleSheetParser using an API key. * - * @param apiKey - * @param sheetId + * @param apiKey The API key. + * @param sheetId The Google Sheet ID. + * @param applicationName The application name. */ public GoogleSheetParser( - String apiKey, String sheetId, String googleSheetParsingCloudConfiguredApplicationName) { + @NonNull final String apiKey, + @NonNull final String sheetId, + @NonNull final String applicationName) { this.sheetId = sheetId; - this.googleSheetParsingCloudConfiguredApplicationName = - googleSheetParsingCloudConfiguredApplicationName; - NetHttpTransport transport = new NetHttpTransport.Builder().build(); - HttpRequestInitializer httpRequestInitializer = - request -> { - request.setInterceptor(intercepted -> intercepted.getUrl().set("key", apiKey)); - }; + this.applicationName = applicationName; + + final HttpTransport transport = new NetHttpTransport.Builder().build(); + final HttpRequestInitializer httpRequestInitializer = + request -> + request.setInterceptor(intercepted -> intercepted.getUrl().set(KEY_PARAMETER, apiKey)); + this.service = new Sheets.Builder(transport, GsonFactory.getDefaultInstance(), httpRequestInitializer) - .setApplicationName(googleSheetParsingCloudConfiguredApplicationName) + .setApplicationName(applicationName) .build(); } /** - * Create parser using service account .json file + * Constructs a GoogleSheetParser using a service account JSON file. * - * @param serviceAccountJsonFileInputStream input stream for .json file with configs - * @param sheetId Google sheet id value (unique part of access link) - * @throws IOException - * @throws GeneralSecurityException + * @param serviceAccountJsonFileInputStream The input stream for the service account JSON file. + * @param sheetId The Google Sheet ID. + * @param applicationName The application name. */ + @SuppressWarnings("deprecation") @SneakyThrows public GoogleSheetParser( - InputStream serviceAccountJsonFileInputStream, - String sheetId, - String googleSheetParsingCloudConfiguredApplicationName) { + @NonNull final InputStream serviceAccountJsonFileInputStream, + @NonNull final String sheetId, + @NonNull final String applicationName) { this.sheetId = sheetId; - this.googleSheetParsingCloudConfiguredApplicationName = - googleSheetParsingCloudConfiguredApplicationName; - HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport(); - GoogleCredential googleCredentials = + this.applicationName = applicationName; + + final HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport(); + final GoogleCredential credentials = GoogleCredential.fromStream(serviceAccountJsonFileInputStream) .createScoped(Collections.singletonList(SheetsScopes.SPREADSHEETS)); this.service = - new Sheets.Builder(httpTransport, GsonFactory.getDefaultInstance(), googleCredentials) - .setApplicationName(googleSheetParsingCloudConfiguredApplicationName) + new Sheets.Builder(httpTransport, GsonFactory.getDefaultInstance(), credentials) + .setApplicationName(applicationName) .build(); } /** - * Get all sheet cell records + * Retrieves all cell records from a sheet. * - * @param sheetTitle sheet title - * @return kind of a matrix values from Google sheet file per sheet title from a document - * @throws IOException + * @param sheetTitle The title of the sheet. + * @return A list of lists representing the cell values. + * @throws IOException If an I/O error occurs. + * @throws GoogleSheetParserException If the sheet is not found or no values are present. */ - public List> getAllSheetCellRecords(String sheetTitle) throws IOException { - SheetProperties sheetProperties = getSheetObjectBySheetTitle(sheetTitle).getProperties(); - List dataFilters = new ArrayList<>(); - DataFilter dataFilterForWholeDocumentCells = getAllSheetCellsDataFilter(sheetProperties); - dataFilters.add(dataFilterForWholeDocumentCells); - - BatchGetValuesByDataFilterRequest batchGetValuesByDataFilterRequest = - getBatchFilterRequest(dataFilters); - BatchGetValuesByDataFilterResponse batchGetValuesByDataFilterResponse = - service - .spreadsheets() - .values() - .batchGetByDataFilter(sheetId, batchGetValuesByDataFilterRequest) - .execute(); - List matchedValueRanges = - batchGetValuesByDataFilterResponse.getValueRanges(); - List> values = getCellMatrixFromValueRanges(dataFilters, matchedValueRanges); - logger.info("Lines loaded from Sheet: " + values.size()); + public List> getAllSheetCellRecords(@NonNull final String sheetTitle) + throws IOException { + final var sheetProperties = getSheetObjectBySheetTitle(sheetTitle).getProperties(); + final var dataFilter = getAllSheetCellsDataFilter(sheetProperties); + + final var request = new BatchGetValuesByDataFilterRequest(); + request.setDataFilters(List.of(dataFilter)); + + final var response = + service.spreadsheets().values().batchGetByDataFilter(sheetId, request).execute(); + + final var valueRanges = response.getValueRanges(); + final var values = getCellMatrixFromValueRanges(List.of(dataFilter), valueRanges); + + logger.info("Lines loaded from Sheet: {}", values.size()); return values; } @SneakyThrows - private Sheet getSheetObjectBySheetTitle(String sheetName) { - Spreadsheet spreadsheetDocument = service.spreadsheets().get(sheetId).execute(); + private Sheet getSheetObjectBySheetTitle(@NonNull final String sheetName) { + final var spreadsheetDocument = service.spreadsheets().get(sheetId).execute(); return spreadsheetDocument.getSheets().stream() .filter(sheet -> sheet.getProperties().getTitle().equals(sheetName)) .findFirst() .orElseThrow( - () -> - new GoogleSheetParserException( - "Sheet with title: " + sheetName + " not " + "found")); + () -> new GoogleSheetParserException("Sheet with title: " + sheetName + " not found")); } - private DataFilter getAllSheetCellsDataFilter(SheetProperties sheetProperties) { - DataFilter dataFilter = new DataFilter(); - GridRange gridRange = new GridRange(); - GridProperties gridProperties = sheetProperties.getGridProperties(); + private DataFilter getAllSheetCellsDataFilter(@NonNull final SheetProperties sheetProperties) { + final var dataFilter = new DataFilter(); + final var gridRange = new GridRange(); + final var gridProperties = sheetProperties.getGridProperties(); + gridRange.setSheetId(sheetProperties.getSheetId()); - int startIndex = 0; - int endRowIndex = gridProperties.getRowCount() - 1; - int startColumnIndex = 0; - int endColumnIndex = gridProperties.getColumnCount() - 1; + gridRange.setStartRowIndex(0); + gridRange.setEndRowIndex(gridProperties.getRowCount() - 1); + gridRange.setStartColumnIndex(0); + gridRange.setEndColumnIndex(gridProperties.getColumnCount() - 1); + logger.info( - "Creating data filter for sheet: {} with row range [{} , {}] and column range [{} , {}]", + "Creating data filter for sheet: {} with row range [{}, {}] and column range [{}, {}]", sheetProperties.getTitle(), - startIndex, - endRowIndex, - startColumnIndex, - endColumnIndex); - gridRange.setStartRowIndex(startIndex); - gridRange.setEndRowIndex(endRowIndex); - gridRange.setStartColumnIndex(startColumnIndex); - gridRange.setEndColumnIndex(endColumnIndex); + gridRange.getStartRowIndex(), + gridRange.getEndRowIndex(), + gridRange.getStartColumnIndex(), + gridRange.getEndColumnIndex()); dataFilter.setGridRange(gridRange); return dataFilter; } - private BatchGetValuesByDataFilterRequest getBatchFilterRequest(List dataFilters) { - BatchGetValuesByDataFilterRequest batchGetValuesByDataFilterRequest = - new BatchGetValuesByDataFilterRequest(); - batchGetValuesByDataFilterRequest.setDataFilters(dataFilters); - return batchGetValuesByDataFilterRequest; - } - private List> getCellMatrixFromValueRanges( - List dataFilters, List matchedValueRanges) { - int dataFiltersCount = dataFilters.size(); - if (matchedValueRanges.size() != dataFiltersCount) { - throw new GoogleSheetParserException( - "No value ranges data present for " - + dataFilters.stream() - .map(filter -> filter.getGridRange()) - .collect(Collectors.toList())); - } + @NonNull final List dataFilters, + @NonNull final List matchedValueRanges) { - List> values = - (List>) - matchedValueRanges - // TODO Implement dynamic filtering approach with passing List as param - .get(dataFiltersCount - 1) - .getValueRange() - .values() - .stream() - .filter(valueObject -> Collection.class.isAssignableFrom(valueObject.getClass())) - .findFirst() - .orElseThrow( - () -> - new GoogleSheetParserException( - "No Collection value object found in value range result response")); - if (Objects.nonNull(values) && !values.isEmpty()) { - return values; - } else { - logger.error("No data 'matrix' found in range"); - return List.of(Collections.emptyList()); + if (matchedValueRanges.size() != dataFilters.size()) { + final String filters = dataFilters.stream().map(DataFilter::getGridRange).toList().toString(); + throw new GoogleSheetParserException("No value ranges data present for " + filters); } + + return Collections.singletonList( + matchedValueRanges.getFirst().getValueRange().getValues().stream() + .filter(Objects::nonNull) + .findFirst() + .orElseThrow( + () -> + new GoogleSheetParserException( + "No Collection value object found in value range result response"))); } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParserException.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParserException.java index b3e7df9..1de14e3 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParserException.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/google/GoogleSheetParserException.java @@ -17,9 +17,11 @@ */ package com.applause.auto.helpers.google; +import lombok.NonNull; + public class GoogleSheetParserException extends RuntimeException { - public GoogleSheetParserException(String message) { + public GoogleSheetParserException(@NonNull final String message) { super(message); } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/mapping/JacksonJSONRestObjectMapping.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/mapping/JacksonJSONRestObjectMapping.java index a41ac4c..a1dd762 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/mapping/JacksonJSONRestObjectMapping.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/mapping/JacksonJSONRestObjectMapping.java @@ -22,19 +22,19 @@ /** Jackson library json REST API typical object mapper instance */ public class JacksonJSONRestObjectMapping implements IRestObjectMapper { + private static final ObjectMapper objectMapper = + new ObjectMapper() + .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false); /** * Object mapper for REST JSON Jackson mapping * - * @return + * @return shared ObjectMapper */ @Override public ObjectMapper restJsonObjectMapper() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - objectMapper.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false); - return objectMapper; } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/RestApiDefaultRestAssuredApiHelper.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/RestApiDefaultRestAssuredApiHelper.java index 87f615b..f7d2f8b 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/RestApiDefaultRestAssuredApiHelper.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/RestApiDefaultRestAssuredApiHelper.java @@ -31,7 +31,7 @@ @Getter public class RestApiDefaultRestAssuredApiHelper { - private RestAssuredApiClient restAssuredApiClient; + private final RestAssuredApiClient restAssuredApiClient; public RestApiDefaultRestAssuredApiHelper() { restAssuredApiClient = new RestApiDefaultRestAssuredApiClient(Duration.ofSeconds(60)); @@ -40,7 +40,7 @@ public RestApiDefaultRestAssuredApiHelper() { /** * 'with' describing default RestAssuredConfig * - * @return + * @return request specification */ public RequestSpecification withDefaultRestHttpClientConfigsSpecification() { return restAssuredApiClient.restAssuredRequestSpecification(new JacksonJSONRestObjectMapping()); diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestApiDefaultRestAssuredApiClient.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestApiDefaultRestAssuredApiClient.java index d9bcacc..8620aba 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestApiDefaultRestAssuredApiClient.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestApiDefaultRestAssuredApiClient.java @@ -27,21 +27,23 @@ import io.restassured.specification.RequestSpecification; import java.time.Duration; import java.util.concurrent.TimeUnit; +import lombok.NonNull; /** Default rest assured REST API client impl. */ public class RestApiDefaultRestAssuredApiClient extends RestAssuredApiClient { - public RestApiDefaultRestAssuredApiClient(Duration apiRequestWaitTimeoutDuration) { + public RestApiDefaultRestAssuredApiClient(final Duration apiRequestWaitTimeoutDuration) { super(apiRequestWaitTimeoutDuration); } /** * get rest assured request specification object for default client * - * @return + * @return request specification */ @Override - public RequestSpecification restAssuredRequestSpecification(IRestObjectMapper restObjectMapper) { + public RequestSpecification restAssuredRequestSpecification( + @NonNull final IRestObjectMapper restObjectMapper) { return super.restAssuredRequestSpecification(restObjectMapper) .filter(new RequestLoggingFilter(LogDetail.URI)) .response() @@ -52,11 +54,11 @@ public RequestSpecification restAssuredRequestSpecification(IRestObjectMapper re /** * rest assured config with object mapper for default client * - * @param restObjectMapper - * @return + * @param restObjectMapper the object mapper + * @return configured RestAssured client */ @Override - protected RestAssuredConfig restAssuredConfig(IRestObjectMapper restObjectMapper) { + protected RestAssuredConfig restAssuredConfig(@NonNull final IRestObjectMapper restObjectMapper) { RestAssuredConfig restAssuredConfig = new RestAssuredConfig(); restAssuredConfig = restAssuredConfig diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestAssuredApiClient.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestAssuredApiClient.java index 8fd2b84..8f0b94c 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestAssuredApiClient.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/http/restassured/client/RestAssuredApiClient.java @@ -24,30 +24,39 @@ import io.restassured.specification.RequestSpecification; import java.time.Duration; import lombok.Getter; +import lombok.NonNull; /** Rest assured api client */ @Getter public abstract class RestAssuredApiClient { - private Duration apiRequestWaitTimeoutDuration; + private final Duration apiRequestWaitTimeoutDuration; - public RestAssuredApiClient(Duration apiRequestWaitTimeoutDuration) { + /** + * Constructs a new RestAssuredApiClient. + * + * @param apiRequestWaitTimeoutDuration The timeout duration for API requests. + */ + public RestAssuredApiClient(@NonNull final Duration apiRequestWaitTimeoutDuration) { this.apiRequestWaitTimeoutDuration = apiRequestWaitTimeoutDuration; } /** - * get rest assured request specification object + * Gets a Rest Assured request specification object. * - * @return + * @param restObjectMapper The REST object mapper to use. + * @return A Rest Assured request specification. */ - public RequestSpecification restAssuredRequestSpecification(IRestObjectMapper restObjectMapper) { + public RequestSpecification restAssuredRequestSpecification( + final IRestObjectMapper restObjectMapper) { return given().when().request().config(restAssuredConfig(restObjectMapper)); } /** - * abstract rest assured config object + * Creates a Rest Assured config object. * - * @return + * @param restObjectMapper The REST object mapper to use. + * @return A Rest Assured config object. */ protected abstract RestAssuredConfig restAssuredConfig(IRestObjectMapper restObjectMapper); } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/scanner/JiraAnnotationsScanner.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/scanner/JiraAnnotationsScanner.java index 1fc1dd6..7d8943a 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/scanner/JiraAnnotationsScanner.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/annotations/scanner/JiraAnnotationsScanner.java @@ -21,50 +21,34 @@ import com.applause.auto.helpers.jira.annotations.JiraID; import com.applause.auto.helpers.jira.exceptions.JiraAnnotationException; import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Objects; -import org.testng.ITestResult; +import lombok.NonNull; -public class JiraAnnotationsScanner { +public final class JiraAnnotationsScanner { + + private JiraAnnotationsScanner() { + // utility class + } /** * Scan test and get declared JiraId value Annotation is mandatory to be declared, otherwise * exception is thrown * - * @param result + * @param result the test result * @return JiraId identifier - * @throws JiraAnnotationException + * @throws JiraAnnotationException JIRA library error */ - public static String getJiraIdentifier(ITestResult result) throws JiraAnnotationException { - Method[] methods = result.getTestClass().getRealClass().getMethods(); - try { - return Objects.requireNonNull( - Arrays.stream(methods) - .filter(method -> method.getName().equals(result.getMethod().getMethodName())) - .findFirst() - .get() - .getAnnotation(JiraID.class) - .identifier()); - } catch (NullPointerException nullPointerException) { - throw new JiraAnnotationException( - String.format("Missing JiraId annotation for test [%s]", result.getName())); - } + public static String getJiraIdentifier(@NonNull final Method result) + throws JiraAnnotationException { + return result.getAnnotation(JiraID.class).identifier(); } /** * Scan test and get declared JiraDefect value * - * @param result + * @param result test result * @return JiraDefect identifier */ - public static String getJiraDefect(ITestResult result) { - Method[] methods = result.getTestClass().getRealClass().getMethods(); - return Objects.requireNonNull( - Arrays.stream(methods) - .filter(method -> method.getName().equals(result.getMethod().getMethodName())) - .findFirst() - .orElse(null)) - .getAnnotation(JiraDefect.class) - .identifier(); + public static String getJiraDefect(@NonNull final Method result) { + return result.getAnnotation(JiraDefect.class).identifier(); } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/JiraXrayClient.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/JiraXrayClient.java index fe1f9f3..3ec4783 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/JiraXrayClient.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/JiraXrayClient.java @@ -24,19 +24,16 @@ import com.applause.auto.helpers.jira.clients.modules.xray.StepsAPI; import com.applause.auto.helpers.jira.clients.modules.xray.TestrunAPI; import java.util.Objects; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; /** * Class for communicating with JIRA/X-Ray Server + DC instance API end-points. * *

Cannot be used with Cloud instances, use GraphQL instead. * - *

X-Ray documentation: https://docs.getxray.app/display/XRAY/REST+API + *

X-Ray documentation: ... */ -public class JiraXrayClient { - - private static final Logger logger = LogManager.getLogger(JiraXrayClient.class); +@SuppressWarnings("PMD.DataClass") +public final class JiraXrayClient { private JiraProjectAPI jiraProjectAPI; private SearchAPI searchAPI; diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/JiraProjectAPI.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/JiraProjectAPI.java index 482c096..a292a19 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/JiraProjectAPI.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/JiraProjectAPI.java @@ -19,8 +19,6 @@ import static com.applause.auto.helpers.jira.constants.XrayEndpoints.*; import static com.applause.auto.helpers.jira.helper.ResponseValidator.checkResponseInRange; -import static com.applause.auto.helpers.jira.requestData.XrayRequestHeaders.getAtlassianNoCheckHeader; -import static com.applause.auto.helpers.jira.requestData.XrayRequestHeaders.getContentTypeMultipartFormDataHeader; import static com.applause.auto.helpers.jira.restclient.XrayRestAssuredClient.getRestClient; import com.applause.auto.helpers.jira.dto.requestmappers.JiraCreateTicketRequest; @@ -29,39 +27,42 @@ import com.applause.auto.helpers.jira.dto.responsemappers.JiraCreateTicketResponse; import com.applause.auto.helpers.jira.dto.responsemappers.steps.Steps; import com.applause.auto.helpers.jira.dto.shared.Issuetype; +import com.applause.auto.helpers.util.GenericObjectMapper; +import com.applause.auto.helpers.util.XrayRequestHeaders; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import io.restassured.response.Response; import java.io.File; import java.io.FileNotFoundException; import java.util.Arrays; import java.util.List; +import lombok.NonNull; import org.apache.commons.lang3.Range; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +@SuppressWarnings("checkstyle:MultipleStringLiterals") public class JiraProjectAPI { private static final Logger logger = LogManager.getLogger(JiraProjectAPI.class); - private ObjectMapper mapper = new ObjectMapper(); /** - * Creates a simple JIRA ticket, additional fields can be created as per project's needs. This - * method can be used to create a new test case, test plan, test execution etc. - * https://developer.atlassian.com/server/jira/platform/jira-rest-api-examples/ + * Creates a simple JIRA ticket. Additional fields can be created as per project's needs. This + * method can be used to create a new test case, test plan, test execution, etc. ... * - * @param jiraCreateTicketRequestMapping, Object of JiraCreateTicketRequestMapping ticket's title - * @return ticket key extracted from response: {"id":"", "key":"", "self":""} - * @throws JsonProcessingException + * @param jiraCreateTicketRequest The request object containing the ticket details. + * @return The ticket key extracted from the response (e.g., "PROJECT-123"). + * @throws JsonProcessingException If an error occurs during JSON processing. */ - public String createTicket(JiraCreateTicketRequest jiraCreateTicketRequestMapping) + public String createTicket(@NonNull final JiraCreateTicketRequest jiraCreateTicketRequest) throws JsonProcessingException { - logger.info("Creating Jira ticket: {}", jiraCreateTicketRequestMapping.toString()); - Response response = postTicket(jiraCreateTicketRequestMapping); - checkResponseInRange(response, Range.between(200, 300), "Creating new Jira Ticket"); - JiraCreateTicketResponse jiraCreateTicketResponseMapping = - mapper.readValue(response.asString(), JiraCreateTicketResponse.class); - String createdJiraTicketId = jiraCreateTicketResponseMapping.getKey(); + logger.info("Creating Jira ticket: {}", jiraCreateTicketRequest.toString()); + Response response = postTicket(jiraCreateTicketRequest); + checkResponseInRange(response, Range.of(200, 300), "Creating new Jira Ticket"); + JiraCreateTicketResponse jiraCreateTicketResponse = + GenericObjectMapper.getObjectMapper() + .readValue(response.asString(), JiraCreateTicketResponse.class); + String createdJiraTicketId = jiraCreateTicketResponse.key(); logger.info("Created Jira ticket: {}", createdJiraTicketId); return createdJiraTicketId; } @@ -69,106 +70,121 @@ public String createTicket(JiraCreateTicketRequest jiraCreateTicketRequestMappin /** * Modify existing Jira Ticket by sending the short or full json data * - * @param jiraTicketID - * @param jsonAsString - * @example: { "fields" : { "labels": [ "my_new_label" ] } } + * @param jiraTicketID the Jira ticket ID + * @param jsonAsString JIRA ticket JSON contents {@code @example:} { "fields" : { "labels": [ + * "my_new_label" ] } } */ - public void updateTicket(String jiraTicketID, String jsonAsString) { + public void updateTicket(@NonNull final String jiraTicketID, @NonNull final String jsonAsString) { logger.info("Updating Jira ticket: {} with [ {} ]", jiraTicketID, jsonAsString); Response response = putUpdateTicket(jiraTicketID, jsonAsString); - checkResponseInRange(response, Range.between(200, 300), "Updating Jira Ticket"); + checkResponseInRange(response, Range.of(200, 300), "Updating Jira Ticket"); } /** * Delete existing Jira Ticket * - * @param jiraTicketID + * @param jiraTicketID JIRA ticket ID */ - public void deleteTicket(String jiraTicketID) { + public void deleteTicket(@NonNull final String jiraTicketID) { logger.info("Deleting Jira ticket: {}", jiraTicketID); Response response = deleteExistingTicket(jiraTicketID); - checkResponseInRange(response, Range.between(200, 300), "Deleting Jira Ticket"); + checkResponseInRange(response, Range.of(200, 300), "Deleting Jira Ticket"); } /** * Get Test Case steps * - * @param jiraTicketID + * @param jiraTicketID JIRA ticket ID * @return Steps object */ - public Steps getTestCaseSteps(String jiraTicketID) throws JsonProcessingException { + public Steps getTestCaseSteps(@NonNull final String jiraTicketID) throws JsonProcessingException { Response response = getJiraTestCaseSteps(jiraTicketID); - Steps steps = mapper.readValue(response.asString(), Steps.class); - checkResponseInRange(response, Range.between(200, 300), "Get Test Case Steps"); + Steps steps = GenericObjectMapper.getObjectMapper().readValue(response.asString(), Steps.class); + checkResponseInRange(response, Range.of(200, 300), "Get Test Case Steps"); return steps; } /** - * Upload file to specific jira ticket (issue, testcase, execution, plan etc) Checks performed: 1. - * If provided file exists 2. If response contains file name and if status code is in 200 range + * Upload file to specific jira ticket (issue, testcase, execution, plan etc). Checks performed: + * + *

    + *
  1. If provided file exists + *
  2. If response contains file name and if status code is in 200 range + *
* - * @param jiraTicketID, represents the jira ticket identifier - * @param pathToFile, full path to file with its extension - * @return void + * @param jiraTicketID represents the jira ticket identifier. + * @param pathToFile full path to file with its extension. + * @throws FileNotFoundException If the file specified by {@code pathToFile} does not exist. */ - public void uploadAttachment(String jiraTicketID, String pathToFile) + public void uploadAttachment(@NonNull final String jiraTicketID, @NonNull final String pathToFile) throws FileNotFoundException { Response response = postAttachment(jiraTicketID, pathToFile); String fileName = new File(pathToFile).getName(); if (!response.getBody().asString().contains(fileName)) { logger.error("Failed to upload attachment {}", fileName); } - checkResponseInRange(response, Range.between(200, 300), "Adding attachment"); + checkResponseInRange(response, Range.of(200, 300), "Adding attachment"); } /** - * @param projectKey, represents the jira project identifier Example CARQA-1234, where CARQA is - * the project identifier - * @return projectId + * Retrieves the project ID associated with a given project key. + * + * @param projectKey The Jira project identifier (e.g., "CARQA-1234", where "CARQA" is the project + * identifier). Must not be null. + * @return The project ID as a String. + * @throws JsonProcessingException If there is an error processing the JSON response. + * @throws NullPointerException If the provided projectKey is null. */ - public String getProjectId(String projectKey) throws JsonProcessingException { - StringBuilder apiEndpoint = new StringBuilder(); - apiEndpoint.append(LATEST_API).append("/").append(PROJECT); + public String getProjectId(@NonNull final String projectKey) throws JsonProcessingException { Response response = getProjectCode(projectKey); - checkResponseInRange(response, Range.between(200, 300), "Determine project Id"); + checkResponseInRange(response, Range.of(200, 300), "Determine project Id"); AvailableProjects[] availableProjects = - mapper.readValue(response.asString(), AvailableProjects[].class); + GenericObjectMapper.getObjectMapper() + .readValue(response.asString(), AvailableProjects[].class); return Arrays.stream(availableProjects) - .filter(project -> project.getKey().equalsIgnoreCase(projectKey)) + .filter(project -> project.key().equalsIgnoreCase(projectKey)) .findFirst() .get() - .getId(); + .id(); } /** - * @param projectId, represents the project identifier code - * @return list of Issuetype objects + * Retrieves a list of available issue types for a given project. + * + * @param projectId the identifier of the project. + * @return a list of Issuetype objects representing the available issue types. + * @throws JsonProcessingException if there is an error processing the JSON response. */ - public List getAvailableIssueTypes(String projectId) throws JsonProcessingException { + public List getAvailableIssueTypes(@NonNull final String projectId) + throws JsonProcessingException { Response response = getAvailableIssues(projectId); - checkResponseInRange(response, Range.between(200, 300), "Get project issue types"); - return mapper.readValue(response.asString(), AvailableIssueTypes.class).getValues(); + checkResponseInRange(response, Range.of(200, 300), "Get project issue types"); + return GenericObjectMapper.getObjectMapper() + .readValue(response.asString(), AvailableIssueTypes.class) + .values(); } /** * Attach label to Jira ticket * - * @param jiraTicketID - * @param labelName + * @param jiraTicketID JIRA ticket ID + * @param labelName Ticket label name */ - public void addLabel(String jiraTicketID, String labelName) { + public void addLabel(@NonNull final String jiraTicketID, @NonNull final String labelName) { logger.info("Updating Jira ticket: {} with [ {} ] label", jiraTicketID, labelName); Response response = putLabelToTicket(jiraTicketID, labelName); - checkResponseInRange(response, Range.between(200, 300), "Adding Jira Ticket label"); + checkResponseInRange(response, Range.of(200, 300), "Adding Jira Ticket label"); } - private Response postTicket(JiraCreateTicketRequest jiraCreateTicketRequestMapping) + private Response postTicket(@NonNull final JiraCreateTicketRequest jiraCreateTicketRequestMapping) throws JsonProcessingException { logger.info("Creating Jira ticket: {}", jiraCreateTicketRequestMapping.toString()); return getRestClient() .given() .and() - .body(mapper.writeValueAsString(jiraCreateTicketRequestMapping)) + .body( + GenericObjectMapper.getObjectMapper() + .writeValueAsString(jiraCreateTicketRequestMapping)) .when() .post(ISSUE_PATH) .then() @@ -176,93 +192,75 @@ private Response postTicket(JiraCreateTicketRequest jiraCreateTicketRequestMappi .response(); } - private Response putUpdateTicket(String jiraTicketID, String jsonAsString) { + private Response putUpdateTicket( + @NonNull final String jiraTicketID, @NonNull final String jsonAsString) { logger.info("Updating Jira Ticket {} with [ {} ]", jiraTicketID, jsonAsString); - StringBuilder apiEndpoint = new StringBuilder(ISSUE_PATH); - apiEndpoint.append("/").append(jiraTicketID); + final var apiEndpoint = ISSUE_PATH + "/" + jiraTicketID; return getRestClient() .given() .and() .body(jsonAsString) .when() - .put(apiEndpoint.toString()) + .put(apiEndpoint) .then() .extract() .response(); } - private Response getJiraTestCaseSteps(String jiraTicketID) { + private Response getJiraTestCaseSteps(@NonNull final String jiraTicketID) { logger.info("Getting X-Ray Test Case Steps response for case: {}", jiraTicketID); - StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); - apiEndpoint.append(TEST).append("/").append(jiraTicketID).append("/").append(STEPS); - return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + String apiEndpoint = XRAY_PATH + TEST + "/" + jiraTicketID + "/" + STEPS; + return getRestClient().given().when().get(apiEndpoint).then().extract().response(); } - private Response postAttachment(String jiraTicketID, String pathToFile) + private Response postAttachment( + @NonNull final String jiraTicketID, @NonNull final String pathToFile) throws FileNotFoundException { logger.info("Uploading attachment {} to Jira Ticket {}", pathToFile, jiraTicketID); File attachment = new File(pathToFile); if (!attachment.exists()) { throw new FileNotFoundException(String.format("Unable to find file %s", pathToFile)); } - StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); - apiEndpoint.append(ISSUE_PATH).append("/").append(jiraTicketID).append("/").append(ATTACHMENTS); + String apiEndpoint = XRAY_PATH + ISSUE_PATH + "/" + jiraTicketID + "/" + ATTACHMENTS; return getRestClient() .given() - .header(getContentTypeMultipartFormDataHeader()) - .header(getAtlassianNoCheckHeader()) + .header(XrayRequestHeaders.getContentTypeMultipartFormDataHeader()) + .header(XrayRequestHeaders.getAtlassianNoCheckHeader()) .multiPart("file", attachment) .and() .when() - .post(apiEndpoint.toString()) + .post(apiEndpoint) .then() .extract() .response(); } - private Response getProjectCode(String projectKey) { + private Response getProjectCode(@NonNull final String projectKey) { logger.info("Returning project code for project key {}", projectKey); - StringBuilder apiEndpoint = new StringBuilder(); - apiEndpoint.append(LATEST_API).append("/").append(PROJECT); - return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + String apiEndpoint = LATEST_API + "/" + PROJECT; + return getRestClient().given().when().get(apiEndpoint).then().extract().response(); } - private Response getAvailableIssues(String projectId) { + private Response getAvailableIssues(@NonNull final String projectId) { logger.info("Returning available issues for project {}", projectId); - StringBuilder apiEndpoint = new StringBuilder(); - apiEndpoint - .append(ISSUE_PATH) - .append("/") - .append(CREATEMETA) - .append("/") - .append(projectId) - .append("/") - .append(ISSUE_TYPES); - return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + String apiEndpoint = ISSUE_PATH + "/" + CREATEMETA + "/" + projectId + "/" + ISSUE_TYPES; + return getRestClient().given().when().get(apiEndpoint).then().extract().response(); } - private Response deleteExistingTicket(String jiraTicketID) { + private Response deleteExistingTicket(@NonNull final String jiraTicketID) { logger.info("Deleting issue {}", jiraTicketID); - StringBuilder apiEndpoint = new StringBuilder(ISSUE_PATH); - apiEndpoint.append("/").append(jiraTicketID); - return getRestClient() - .given() - .when() - .delete(apiEndpoint.toString()) - .then() - .extract() - .response(); + String apiEndpoint = ISSUE_PATH + "/" + jiraTicketID; + return getRestClient().given().when().delete(apiEndpoint).then().extract().response(); } - private Response putLabelToTicket(String jiraTicketID, String labelName) { - StringBuilder apiEndpoint = new StringBuilder(ISSUE_PATH); - apiEndpoint.append("/").append(jiraTicketID); + private Response putLabelToTicket(@NonNull final String jiraTicketID, final String labelName) { + String apiEndpoint = ISSUE_PATH + "/" + jiraTicketID; return getRestClient() .given() .and() .body(String.format("{\"update\":{\"labels\":[{\"add\":\"%s\"}]}}", labelName)) .when() - .put(apiEndpoint.toString()) + .put(apiEndpoint) .then() .extract() .response(); diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/SearchAPI.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/SearchAPI.java index 9d5e0e6..d08c1cb 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/SearchAPI.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/jira/SearchAPI.java @@ -22,10 +22,11 @@ import static com.applause.auto.helpers.jira.restclient.XrayRestAssuredClient.getRestClient; import com.applause.auto.helpers.jira.dto.jql.JqlFilteredResults; +import com.applause.auto.helpers.util.GenericObjectMapper; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import io.restassured.response.Response; import java.util.Objects; +import lombok.NonNull; import org.apache.commons.lang3.Range; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -33,39 +34,45 @@ public class SearchAPI { private static final Logger logger = LogManager.getLogger(SearchAPI.class); - private ObjectMapper mapper = new ObjectMapper(); /** - * @param jqlQuery, represents the JQL query used in Jira - * @param maxResults, overrides default limited 50 results. Can be set to null for default. - * @example : "labels IN ("xray_automation") AND issuetype=12106 AND summary~"Test Description"" - * @return JqlFilteredResults object containing number of findings and list of issues + * Filters Jira issues based on a provided JQL query. + * + * @param jqlQuery The JQL query used to filter issues. For example: \"labels IN + * (\"xray_automation\") AND issuetype=12106 AND summary~\"Test Description\"" + * @param maxResults The maximum number of results to return. If null, the Jira default limit + * (usually 50) is used. + * @return A {@link JqlFilteredResults} object containing the total number of issues found and a + * list of the retrieved issues. + * @throws JsonProcessingException If an error occurs during JSON processing of the Jira response. */ - public JqlFilteredResults filterIssues(String jqlQuery, Integer maxResults) + public JqlFilteredResults filterIssues(@NonNull final String jqlQuery, final Integer maxResults) throws JsonProcessingException { Response response = getIssuesByJqlFiltering(jqlQuery, maxResults); - checkResponseInRange(response, Range.between(200, 300), "Get issue by jql filter"); - JqlFilteredResults results = mapper.readValue(response.asString(), JqlFilteredResults.class); - if (results.getTotal() == 0) { + checkResponseInRange(response, Range.of(200, 300), "Get issue by jql filter"); + JqlFilteredResults results = + GenericObjectMapper.getObjectMapper() + .readValue(response.asString(), JqlFilteredResults.class); + if (results.total() == 0) { logger.warn("JQL search returned 0 results"); } return results; } - public Response getIssuesByJqlFiltering(String jqlQuery, Integer maxResults) { + public Response getIssuesByJqlFiltering( + @NonNull final String jqlQuery, final Integer maxResults) { logger.info("Searching issues by JQL query [ {} ] ", jqlQuery); StringBuilder apiEndpoint = new StringBuilder(); - apiEndpoint.append(LATEST_API).append("/").append(SEARCH).append("?jql=").append(jqlQuery); + apiEndpoint.append(LATEST_API).append('/').append(SEARCH).append("?jql=").append(jqlQuery); if (Objects.nonNull(maxResults)) { apiEndpoint.append("&maxResults=").append(maxResults); } return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); } - public Response getIssueResponseObject(String jiraTicketID) { + public Response getIssueResponseObject(@NonNull final String jiraTicketID) { logger.info("Returning issue response for {}", jiraTicketID); - StringBuilder apiEndpoint = new StringBuilder(); - apiEndpoint.append(ISSUE_PATH).append("/").append(jiraTicketID); - return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + String apiEndpoint = ISSUE_PATH + "/" + jiraTicketID; + return getRestClient().given().when().get(apiEndpoint).then().extract().response(); } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/ExecutionsAPI.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/ExecutionsAPI.java index 5eaafc5..41798c2 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/ExecutionsAPI.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/ExecutionsAPI.java @@ -23,90 +23,90 @@ import com.applause.auto.helpers.jira.dto.requestmappers.XrayAddTo; import com.applause.auto.helpers.jira.dto.responsemappers.JiraCreateTicketResponse; +import com.applause.auto.helpers.util.GenericObjectMapper; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import io.restassured.response.Response; +import lombok.NonNull; import org.apache.commons.lang3.Range; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +@SuppressWarnings("checkstyle:MultipleStringLiterals") public class ExecutionsAPI { private static final Logger logger = LogManager.getLogger(ExecutionsAPI.class); - private ObjectMapper mapper = new ObjectMapper(); /** - * Adds Test Execution to existing Test Plan, request example: "{"add":["testExecKey"]}", - * Response: 200 OK, if Test Execution was added successfully + * Adds Test Execution to existing Test Plan. * - * @param xrayAddToMapping, Test Execution you want to associate to a specific Test Plan - * @return statusCode - * @throws JsonProcessingException + *

Request example: `{"add":["testExecKey"]}` + * + *

Response: 200 OK, if Test Execution was added successfully + * + * @param xrayAddToMapping The Test Execution to associate with the specified Test Plan. + * @param testPlanKey The key of the Test Plan to which the Test Execution should be added. + * @throws JsonProcessingException If an error occurs during JSON processing. */ - public void addExecutionToTestPlan(XrayAddTo xrayAddToMapping, String testPlanKey) + public void addExecutionToTestPlan( + @NonNull final XrayAddTo xrayAddToMapping, @NonNull final String testPlanKey) throws JsonProcessingException { Response response = postTestExecutionToTestPlan(xrayAddToMapping, testPlanKey); - checkResponseInRange(response, Range.between(200, 300), "Add test execution to test plan"); + checkResponseInRange(response, Range.of(200, 300), "Add test execution to test plan"); } /** - * Adds Test to an existing Test Execution, request example: "{"add":["testKey"]}". Response: 200 - * OK, if test was added successfully + * Adds Test to an existing Test Execution. Request example: "{"add":["testKey"]}". Response: 200 + * OK, if test was added successfully. * - * @param jiraCreateTicketResponseMapping, to get Test Execution key from - * @param xrayAddToMapping, Test key/s you want to associate to a specific Test Execution - * @return statusCode - * @throws JsonProcessingException + * @param jiraCreateTicketResponseMapping The response containing the Test Execution key. + * @param xrayAddToMapping The Test key(s) to associate with the Test Execution. + * @throws JsonProcessingException If a JSON processing error occurs. */ public void addTestToTestExecution( - JiraCreateTicketResponse jiraCreateTicketResponseMapping, XrayAddTo xrayAddToMapping) + @NonNull final JiraCreateTicketResponse jiraCreateTicketResponseMapping, + @NonNull final XrayAddTo xrayAddToMapping) throws JsonProcessingException { Response response = postTestToTestExecution(jiraCreateTicketResponseMapping, xrayAddToMapping); - checkResponseInRange(response, Range.between(200, 300), "Add test to test execution"); + checkResponseInRange(response, Range.of(200, 300), "Add test to test execution"); } - private Response postTestExecutionToTestPlan(XrayAddTo xrayAddToMapping, String testPlanKey) + private Response postTestExecutionToTestPlan( + @NonNull final XrayAddTo xrayAddToMapping, @NonNull final String testPlanKey) throws JsonProcessingException { logger.info( "Adding X-Ray Test Execution {} to X-Ray Test Plan {}", xrayAddToMapping, testPlanKey); - StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); - apiEndpoint - .append(TEST_PLAN) - .append("/") - .append(testPlanKey) - .append("/") - .append(TEST_EXECUTION); + + String apiEndpoint = XRAY_PATH + TEST_PLAN + "/" + testPlanKey + "/" + TEST_EXECUTION; + return getRestClient() .given() .and() - .body(mapper.writeValueAsString(xrayAddToMapping)) + .body(GenericObjectMapper.getObjectMapper().writeValueAsString(xrayAddToMapping)) .when() - .post(apiEndpoint.toString()) + .post(apiEndpoint) .then() .extract() .response(); } private Response postTestToTestExecution( - JiraCreateTicketResponse jiraCreateTicketResponseMapping, XrayAddTo xrayAddToMapping) + @NonNull final JiraCreateTicketResponse jiraCreateTicketResponseMapping, + @NonNull final XrayAddTo xrayAddToMapping) throws JsonProcessingException { logger.info( "Adding X-Ray Test(s) [ {} ] to X-Ray Test Execution [ {} ]", xrayAddToMapping.toString(), - jiraCreateTicketResponseMapping.getKey()); - StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); - apiEndpoint - .append(TEST_EXEC) - .append("/") - .append(jiraCreateTicketResponseMapping.getKey()) - .append("/") - .append(TEST); + jiraCreateTicketResponseMapping.key()); + + String apiEndpoint = + XRAY_PATH + TEST_EXEC + "/" + jiraCreateTicketResponseMapping.key() + "/" + TEST; + return getRestClient() .given() .and() - .body(mapper.writeValueAsString(xrayAddToMapping)) + .body(GenericObjectMapper.getObjectMapper().writeValueAsString(xrayAddToMapping)) .when() - .post(apiEndpoint.toString()) + .post(apiEndpoint) .then() .extract() .response(); diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/IterationsAPI.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/IterationsAPI.java index 0b61bd1..1133d89 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/IterationsAPI.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/IterationsAPI.java @@ -22,48 +22,41 @@ import static com.applause.auto.helpers.jira.restclient.XrayRestAssuredClient.getRestClient; import com.applause.auto.helpers.jira.dto.responsemappers.steps.Step; +import com.applause.auto.helpers.util.GenericObjectMapper; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import io.restassured.response.Response; import org.apache.commons.lang3.Range; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +@SuppressWarnings("checkstyle:MultipleStringLiterals") public class IterationsAPI { private static final Logger logger = LogManager.getLogger(IterationsAPI.class); - private ObjectMapper mapper = new ObjectMapper(); /** - * Get Test Run Iteration Steps information Returned object will contain also each step ID which - * is needed for further step update request + * Get Test Run Iteration Steps information. Returned object will contain also each step ID which + * is needed for further step update request. * - * @param testRunId - * @param iterationId - * @return Step array object + * @param testRunId The ID of the test run. + * @param iterationId The ID of the iteration. + * @return An array of Step objects. + * @throws JsonProcessingException If there is an error processing the JSON response. */ - public Step[] getTestRunIterationStepsData(int testRunId, int iterationId) + public Step[] getTestRunIterationStepsData(final int testRunId, final int iterationId) throws JsonProcessingException { Response response = getTestRunIterationSteps(testRunId, iterationId); - Step[] steps = mapper.readValue(response.asString(), Step[].class); - checkResponseInRange(response, Range.between(200, 300), "Get Test Run Iteration Steps Data"); + Step[] steps = + GenericObjectMapper.getObjectMapper().readValue(response.asString(), Step[].class); + checkResponseInRange(response, Range.of(200, 300), "Get Test Run Iteration Steps Data"); return steps; } - private Response getTestRunIterationSteps(int testRunId, int iterationId) { + private Response getTestRunIterationSteps(final int testRunId, final int iterationId) { logger.info( "Getting X-Ray Test Run {} iteration steps response for ID: {}", testRunId, iterationId); - StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); - apiEndpoint - .append(TEST_RUN) - .append("/") - .append(testRunId) - .append("/") - .append(ITERATION) - .append("/") - .append(iterationId) - .append("/") - .append(STEP); - return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + String apiEndpoint = + XRAY_PATH + TEST_RUN + "/" + testRunId + "/" + ITERATION + "/" + iterationId + "/" + STEP; + return getRestClient().given().when().get(apiEndpoint).then().extract().response(); } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/StepsAPI.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/StepsAPI.java index d5ed721..509bce3 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/StepsAPI.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/StepsAPI.java @@ -24,174 +24,165 @@ import com.applause.auto.helpers.jira.dto.requestmappers.StepFieldsUpdate; import com.applause.auto.helpers.jira.dto.requestmappers.StepIterationAttachment; +import com.applause.auto.helpers.util.GenericObjectMapper; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import io.restassured.response.Response; import java.io.IOException; +import lombok.NonNull; import org.apache.commons.lang3.Range; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +@SuppressWarnings("checkstyle:MultipleStringLiterals") public class StepsAPI { private static final Logger logger = LogManager.getLogger(StepsAPI.class); - private ObjectMapper mapper = new ObjectMapper(); /** * Modify Test Run Step by using PUT API This method is used to update: status, comment, actual * result Should be used when test is NOT parametrised and dataset is NOT present * - * @param testRunId - * @param stepId + * @param testRunId the test run ID + * @param stepId test step ID * @param fields - fields which will be updated */ - public void updateTestRunStep(int testRunId, int stepId, StepFieldsUpdate fields) + public void updateTestRunStep( + final int testRunId, final int stepId, @NonNull final StepFieldsUpdate fields) throws JsonProcessingException { Response response = putTestRunStep(testRunId, stepId, fields); - checkResponseInRange(response, Range.between(200, 300), "Update Test Run Step"); + checkResponseInRange(response, Range.of(200, 300), "Update Test Run Step"); } /** * Modify Test Run Iteration Step by using PUT API This method is used to update: status, comment, * actual result Should be used when test is parametrised and dataset is present * - * @param testRunId - * @param iterationId - * @param stepId + * @param testRunId the test run ID + * @param iterationId test run iteration step + * @param stepId test step ID * @param fields - fields which will be updated */ public void updateTestRunIterationStep( - int testRunId, int iterationId, int stepId, StepFieldsUpdate fields) + int testRunId, int iterationId, int stepId, @NonNull final StepFieldsUpdate fields) throws JsonProcessingException { Response response = putTestRunIterationStep(testRunId, iterationId, stepId, fields); - checkResponseInRange(response, Range.between(200, 300), "Update Test Run Iteration Step"); + checkResponseInRange(response, Range.of(200, 300), "Update Test Run Iteration Step"); } /** * Upload Test Run Step attachment * - * @param testRunId - * @param stepId + * @param testRunId test run ID + * @param stepId test step ID * @param filePath - path to file */ - public void uploadTestRunStepAttachment(int testRunId, int stepId, String filePath) - throws IOException { + public void uploadTestRunStepAttachment( + final int testRunId, final int stepId, @NonNull final String filePath) throws IOException { Response response = postTestRunStepAttachment(testRunId, stepId, filePath); - checkResponseInRange(response, Range.between(200, 300), "Upload Test Run Step attachment"); + checkResponseInRange(response, Range.of(200, 300), "Upload Test Run Step attachment"); } /** * Upload Test Run Iteration Step attachment * - * @param testRunId - * @param iterationId - * @param stepId + * @param testRunId test run ID + * @param iterationId test step iteration ID + * @param stepId test step ID * @param filePath - path to file */ public void uploadTestRunIterationStepAttachment( - int testRunId, int iterationId, int stepId, String filePath) throws IOException { + final int testRunId, final int iterationId, final int stepId, @NonNull final String filePath) + throws IOException { Response response = postTestRunIterationStepAttachment(testRunId, iterationId, stepId, filePath); - checkResponseInRange( - response, Range.between(200, 300), "Upload Test Run Iteration Step attachment"); + checkResponseInRange(response, Range.of(200, 300), "Upload Test Run Iteration Step attachment"); } - private Response postTestRunStepAttachment(int testRunId, int stepId, String filePath) - throws IOException { + private Response postTestRunStepAttachment( + final int testRunId, final int stepId, @NonNull final String filePath) throws IOException { logger.info("Attaching {} to X-Ray Test Run {} step {}", filePath, testRunId, stepId); - StepIterationAttachment stepIterationAttachment = new StepIterationAttachment(); - stepIterationAttachment.setData(encodeBase64File(filePath)); - stepIterationAttachment.setFilename(getFileNameFromPath(filePath)); - stepIterationAttachment.setContentType(getFileType(filePath)); - StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); - apiEndpoint - .append(TEST_RUN) - .append("/") - .append(testRunId) - .append("/") - .append(STEP) - .append("/") - .append(stepId) - .append("/") - .append(ATTACHMENT); + StepIterationAttachment stepIterationAttachment = + new StepIterationAttachment( + encodeBase64File(filePath), getFileNameFromPath(filePath), getFileType(filePath)); + final var apiEndpoint = + XRAY_PATH + TEST_RUN + "/" + testRunId + "/" + STEP + "/" + stepId + "/" + ATTACHMENT; + return getRestClient() .given() .and() - .body(mapper.writeValueAsString(stepIterationAttachment)) + .body(GenericObjectMapper.getObjectMapper().writeValueAsString(stepIterationAttachment)) .when() - .post(apiEndpoint.toString()) + .post(apiEndpoint) .then() .extract() .response(); } private Response postTestRunIterationStepAttachment( - int testRunId, int iterationId, int stepId, String filePath) throws IOException { + final int testRunId, final int iterationId, final int stepId, @NonNull final String filePath) + throws IOException { logger.info( "Attaching {} to X-Ray Test Run {} iteration {} step {}", filePath, testRunId, iterationId, stepId); - StepIterationAttachment stepIterationAttachment = new StepIterationAttachment(); - stepIterationAttachment.setData(encodeBase64File(filePath)); - stepIterationAttachment.setFilename(getFileNameFromPath(filePath)); - stepIterationAttachment.setContentType(getFileType(filePath)); - StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); - apiEndpoint - .append(TEST_RUN) - .append("/") - .append(testRunId) - .append("/") - .append(ITERATION) - .append("/") - .append(iterationId) - .append("/") - .append(STEP) - .append("/") - .append(stepId) - .append("/") - .append(ATTACHMENT); + StepIterationAttachment stepIterationAttachment = + new StepIterationAttachment( + encodeBase64File(filePath), getFileNameFromPath(filePath), getFileType(filePath)); + String apiEndpoint = + XRAY_PATH + + TEST_RUN + + "/" + + testRunId + + "/" + + ITERATION + + "/" + + iterationId + + "/" + + STEP + + "/" + + stepId + + "/" + + ATTACHMENT; + return getRestClient() .given() .and() - .body(mapper.writeValueAsString(stepIterationAttachment)) + .body(GenericObjectMapper.getObjectMapper().writeValueAsString(stepIterationAttachment)) .when() - .post(apiEndpoint.toString()) + .post(apiEndpoint) .then() .extract() .response(); } - private Response putTestRunStep(int testRunId, int stepId, StepFieldsUpdate stepFieldsUpdate) + private Response putTestRunStep( + final int testRunId, final int stepId, @NonNull final StepFieldsUpdate stepFieldsUpdate) throws JsonProcessingException { logger.info( "Updating X-Ray Test Run {} step {} with {}", testRunId, stepId, stepFieldsUpdate.toString()); - StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); - apiEndpoint - .append(TEST_RUN) - .append("/") - .append(testRunId) - .append("/") - .append(STEP) - .append("/") - .append(stepId); + String apiEndpoint = XRAY_PATH + TEST_RUN + "/" + testRunId + "/" + STEP + "/" + stepId; + return getRestClient() .given() .and() - .body(mapper.writeValueAsString(stepFieldsUpdate)) + .body(GenericObjectMapper.getObjectMapper().writeValueAsString(stepFieldsUpdate)) .when() - .put(apiEndpoint.toString()) + .put(apiEndpoint) .then() .extract() .response(); } private Response putTestRunIterationStep( - int testRunId, int iterationId, int stepId, StepFieldsUpdate stepFieldsUpdate) + final int testRunId, + final int iterationId, + final int stepId, + @NonNull final StepFieldsUpdate stepFieldsUpdate) throws JsonProcessingException { logger.info( "Updating X-Ray Test Run {} iteration {} step {} with {}", @@ -199,25 +190,26 @@ private Response putTestRunIterationStep( iterationId, stepId, stepFieldsUpdate.toString()); - StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); - apiEndpoint - .append(TEST_RUN) - .append("/") - .append(testRunId) - .append("/") - .append(ITERATION) - .append("/") - .append(iterationId) - .append("/") - .append(STEP) - .append("/") - .append(stepId); + String apiEndpoint = + XRAY_PATH + + TEST_RUN + + "/" + + testRunId + + "/" + + ITERATION + + "/" + + iterationId + + "/" + + STEP + + "/" + + stepId; + return getRestClient() .given() .and() - .body(mapper.writeValueAsString(stepFieldsUpdate)) + .body(GenericObjectMapper.getObjectMapper().writeValueAsString(stepFieldsUpdate)) .when() - .put(apiEndpoint.toString()) + .put(apiEndpoint) .then() .extract() .response(); diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/TestrunAPI.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/TestrunAPI.java index f2fed01..be41c5b 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/TestrunAPI.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/clients/modules/xray/TestrunAPI.java @@ -26,184 +26,180 @@ import com.applause.auto.helpers.jira.dto.responsemappers.JiraCreateTicketResponse; import com.applause.auto.helpers.jira.dto.responsemappers.XrayTestRunDetails; import com.applause.auto.helpers.jira.dto.responsemappers.iteration.TestRunIteration; +import com.applause.auto.helpers.util.GenericObjectMapper; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import io.restassured.response.Response; import java.io.IOException; +import lombok.NonNull; import org.apache.commons.lang3.Range; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +@SuppressWarnings("checkstyle:MultipleStringLiterals") public class TestrunAPI { private static final Logger logger = LogManager.getLogger(TestrunAPI.class); - private ObjectMapper mapper = new ObjectMapper(); /** - * Get Test Run ID which can be used in updating Test status in a certain Test Execution + * Get Test Run ID which can be used in updating Test status in a certain Test Execution. * - * @param jiraCreateTicketResponseMapping, to get value of testExecKey, - * @param testKey, - * @return testRunID, - * @throws JsonProcessingException + * @param jiraCreateTicketResponseMapping The Jira create ticket response mapping, used to get the + * testExecKey. + * @param testKey The test issue key. + * @return The testRunID. + * @throws JsonProcessingException If the JSON response is invalid. */ - public int getTestRunID(JiraCreateTicketResponse jiraCreateTicketResponseMapping, String testKey) + public int getTestRunID( + @NonNull final JiraCreateTicketResponse jiraCreateTicketResponseMapping, + @NonNull final String testKey) throws JsonProcessingException { Response response = getTestRunIdOfTestFromExecution(jiraCreateTicketResponseMapping, testKey); XrayTestRunDetails xrayTestRunDetailsMapping = - mapper.readValue(response.asString(), XrayTestRunDetails.class); - checkResponseInRange(response, Range.between(200, 300), "Collecting Test Run ID"); - return xrayTestRunDetailsMapping.getId(); + GenericObjectMapper.getObjectMapper() + .readValue(response.asString(), XrayTestRunDetails.class); + checkResponseInRange(response, Range.of(200, 300), "Collecting Test Run ID"); + return xrayTestRunDetailsMapping.id(); } /** - * Updates Test Run status, + * Updates Test Run status. * - * @param testRunId, - * @param statusToUpdate: Can be extracted after test execution from ITestResult using its - * getStatus() and then converted into one of the following values accepted by X-Ray as per - * project needs: - *

EXECUTING – Test is being executed; this is a non-final status; - *

FAIL – Test failed - *

ABORTED – Test was aborted - *

PASS – Test passed successfully + * @param testRunId the test run ID + * @param statusToUpdate the status to update. Can be extracted after test execution from + * ITestResult using its getStatus() and then converted into one of the following values + * accepted by X-Ray as per project needs: + *

    + *
  • EXECUTING – Test is being executed; this is a non-final status; + *
  • FAIL – Test failed + *
  • ABORTED – Test was aborted + *
  • PASS – Test passed successfully + *
+ * + * @throws NullPointerException if statusToUpdate is null */ - public void updateTestRun(int testRunId, String statusToUpdate) { + public void updateTestRun(final int testRunId, @NonNull final String statusToUpdate) { Response response = putTestRunStatus(testRunId, statusToUpdate); - checkResponseInRange(response, Range.between(200, 300), "Update test run"); + checkResponseInRange(response, Range.of(200, 300), "Update test run"); } /** * Get Test Run information * - * @param testRunId + * @param testRunId test run ID * @return XrayTestRunDetails object */ - public XrayTestRunDetails getTestRunData(int testRunId) throws JsonProcessingException { + public XrayTestRunDetails getTestRunData(final int testRunId) throws JsonProcessingException { Response response = getTestRunBasedOnID(testRunId); XrayTestRunDetails xrayTestRunDetailsMapping = - mapper.readValue(response.asString(), XrayTestRunDetails.class); - checkResponseInRange(response, Range.between(200, 300), "Get Test Run Data"); + GenericObjectMapper.getObjectMapper() + .readValue(response.asString(), XrayTestRunDetails.class); + checkResponseInRange(response, Range.of(200, 300), "Get Test Run Data"); return xrayTestRunDetailsMapping; } /** * Get Test Run Iteration information * - * @param testRunId - * @param iterationId + * @param testRunId test run ID + * @param iterationId test run iteration ID * @return TestRunIteration object */ - public TestRunIteration getTestRunIterationData(int testRunId, int iterationId) + public TestRunIteration getTestRunIterationData(final int testRunId, final int iterationId) throws JsonProcessingException { Response response = getTestRunIterationBasedOnID(testRunId, iterationId); TestRunIteration testRunIteration = - mapper.readValue(response.asString(), TestRunIteration.class); - checkResponseInRange(response, Range.between(200, 300), "Get Test Run Iteration Data"); + GenericObjectMapper.getObjectMapper() + .readValue(response.asString(), TestRunIteration.class); + checkResponseInRange(response, Range.of(200, 300), "Get Test Run Iteration Data"); return testRunIteration; } /** * Upload Test Run attachment * - * @param testRunId + * @param testRunId test run ID * @param filePath - path to file */ - public void uploadTestRunAttachment(int testRunId, String filePath) throws IOException { + public void uploadTestRunAttachment(final int testRunId, @NonNull final String filePath) + throws IOException { Response response = postTestRunAttachment(testRunId, filePath); - checkResponseInRange(response, Range.between(200, 300), "Upload Test Run attachment"); + checkResponseInRange(response, Range.of(200, 300), "Upload Test Run attachment"); } /** * Post comment to Test Run * - * @param testRunId - * @param comment + * @param testRunId test run ID + * @param comment test run comment */ - public void postTestRunComment(int testRunId, String comment) { + public void postTestRunComment(final int testRunId, @NonNull final String comment) { Response response = postComment(testRunId, comment); - checkResponseInRange(response, Range.between(200, 300), "Posting Test Run comment"); + checkResponseInRange(response, Range.of(200, 300), "Posting Test Run comment"); } private Response getTestRunIdOfTestFromExecution( - JiraCreateTicketResponse jiraCreateTicketResponseMapping, String testKey) { + @NonNull final JiraCreateTicketResponse jiraCreateTicketResponseMapping, + @NonNull final String testKey) { logger.info("Getting X-Ray Test Run ID for test: {}", testKey); - StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); - apiEndpoint - .append(TEST_RUN) - .append("?") - .append(testExecIssueKeyParam) - .append(jiraCreateTicketResponseMapping.getKey()) - .append("&") - .append(testIssueKeyParam) - .append(testKey); - return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + String apiEndpoint = + XRAY_PATH + + TEST_RUN + + "?" + + testExecIssueKeyParam + + jiraCreateTicketResponseMapping.key() + + "&" + + testIssueKeyParam + + testKey; + return getRestClient().given().when().get(apiEndpoint).then().extract().response(); } - private Response putTestRunStatus(int testRunId, String statusToUpdate) { + private Response putTestRunStatus(final int testRunId, @NonNull final String statusToUpdate) { logger.info("Updating X-Ray Test Run: {} with status: {}", testRunId, statusToUpdate); - StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); - apiEndpoint - .append(TEST_RUN + "/") - .append(testRunId) - .append("/") - .append(STATUS) - .append("?") - .append(statusParam) - .append(statusToUpdate); - return getRestClient().given().when().put(apiEndpoint.toString()).then().extract().response(); + String apiEndpoint = + XRAY_PATH + TEST_RUN + "/" + testRunId + "/" + STATUS + "?" + statusParam + statusToUpdate; + return getRestClient().given().when().put(apiEndpoint).then().extract().response(); } - private Response getTestRunBasedOnID(int testRunId) { + private Response getTestRunBasedOnID(final int testRunId) { logger.info("Getting X-Ray Test Run response for ID: {}", testRunId); - StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); - apiEndpoint.append(TEST_RUN).append("/").append(testRunId); - return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + String apiEndpoint = XRAY_PATH + TEST_RUN + "/" + testRunId; + return getRestClient().given().when().get(apiEndpoint).then().extract().response(); } - private Response getTestRunIterationBasedOnID(int testRunId, int iterationId) { + private Response getTestRunIterationBasedOnID(final int testRunId, final int iterationId) { logger.info("Getting X-Ray Test Run {} iteration response for ID: {}", testRunId, iterationId); - StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); - apiEndpoint - .append(TEST_RUN) - .append("/") - .append(testRunId) - .append("/") - .append(ITERATION) - .append("/") - .append(iterationId); - return getRestClient().given().when().get(apiEndpoint.toString()).then().extract().response(); + String apiEndpoint = + XRAY_PATH + TEST_RUN + "/" + testRunId + "/" + ITERATION + "/" + iterationId; + return getRestClient().given().when().get(apiEndpoint).then().extract().response(); } - private Response postTestRunAttachment(int testRunId, String filePath) throws IOException { + private Response postTestRunAttachment(final int testRunId, @NonNull final String filePath) + throws IOException { logger.info("Attaching {} to X-Ray Test Run {}", filePath, testRunId); - StepIterationAttachment stepIterationAttachment = new StepIterationAttachment(); - stepIterationAttachment.setData(encodeBase64File(filePath)); - stepIterationAttachment.setFilename(getFileNameFromPath(filePath)); - stepIterationAttachment.setContentType(getFileType(filePath)); - StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); - apiEndpoint.append(TEST_RUN).append("/").append(testRunId).append("/").append(ATTACHMENT); + StepIterationAttachment stepIterationAttachment = + new StepIterationAttachment( + encodeBase64File(filePath), getFileNameFromPath(filePath), getFileType(filePath)); + String apiEndpoint = XRAY_PATH + TEST_RUN + "/" + testRunId + "/" + ATTACHMENT; return getRestClient() .given() .and() - .body(mapper.writeValueAsString(stepIterationAttachment)) + .body(GenericObjectMapper.getObjectMapper().writeValueAsString(stepIterationAttachment)) .when() - .post(apiEndpoint.toString()) + .post(apiEndpoint) .then() .extract() .response(); } - private Response postComment(int testRunId, String comment) { + private Response postComment(final int testRunId, @NonNull final String comment) { logger.info("Posting comment [{}] to X-Ray Test Run {}", comment, testRunId); - StringBuilder apiEndpoint = new StringBuilder(XRAY_PATH); - apiEndpoint.append(TEST_RUN).append("/").append(testRunId).append("/").append(COMMENT); + String apiEndpoint = XRAY_PATH + TEST_RUN + "/" + testRunId + "/" + COMMENT; return getRestClient() .given() .and() .body(comment) .when() - .put(apiEndpoint.toString()) + .put(apiEndpoint) .then() .extract() .response(); diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/Statuses.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/Statuses.java index e951409..3351858 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/Statuses.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/Statuses.java @@ -28,5 +28,5 @@ public enum Statuses { BLOCKED("BLOCKED"), TODO("TODO"); - @Getter private String value; + @Getter private final String value; } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/XrayEndpoints.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/XrayEndpoints.java index 842b999..c59c4c5 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/XrayEndpoints.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/constants/XrayEndpoints.java @@ -17,8 +17,8 @@ */ package com.applause.auto.helpers.jira.constants; -public class XrayEndpoints { - +@SuppressWarnings("PMD.DataClass") +public final class XrayEndpoints { public static final String LATEST_API = "/api/latest"; public static final String XRAY_PATH = "/raven/latest/api/"; @@ -46,4 +46,8 @@ public class XrayEndpoints { public static final String testIssueKeyParam = "testIssueKey="; public static final String statusParam = "status="; + + private XrayEndpoints() { + // utility class + } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Fields.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Fields.java index c1f3ef8..eb6a7ac 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Fields.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Fields.java @@ -19,14 +19,7 @@ import com.applause.auto.helpers.jira.dto.shared.Issuetype; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; -@Data @JsonIgnoreProperties(ignoreUnknown = true) -public class Fields { - private String summary; - private String[] labels; - private String created; - private Issuetype issuetype; - private String environment; -} +public record Fields( + String summary, String labels, String created, Issuetype issuetype, String environment) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Issues.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Issues.java index 7e1079f..f383430 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Issues.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/Issues.java @@ -18,14 +18,6 @@ package com.applause.auto.helpers.jira.dto.jql; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; -import lombok.Getter; -@Data -@Getter @JsonIgnoreProperties(ignoreUnknown = true) -public class Issues { - public String id; - public String key; - public Fields fields; -} +public record Issues(String id, String key, Fields fields) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/JqlFilteredResults.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/JqlFilteredResults.java index 0d52985..06c4d5a 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/JqlFilteredResults.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/jql/JqlFilteredResults.java @@ -19,14 +19,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.List; -import lombok.Data; -import lombok.Getter; -@Data -@Getter @JsonIgnoreProperties(ignoreUnknown = true) -public class JqlFilteredResults { - private int maxResults; - private int total; - private List issues; -} +public record JqlFilteredResults(int maxResults, int total, List issues) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/Fields.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/Fields.java index b084727..9caaeac 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/Fields.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/Fields.java @@ -19,15 +19,5 @@ import com.applause.auto.helpers.jira.dto.shared.Issuetype; import com.applause.auto.helpers.jira.dto.shared.Project; -import lombok.Data; -@Data -public class Fields { - // summary represents ticket's title. - private String summary; - // issueTypeId is different per project, and unique per ticket type (defect, task, test plan etc). - // Example: Test Plan: 12106, Xray Test: 10402. - private Issuetype issuetype; - // Project id is the identifier of the project. - private Project project; -} +public record Fields(String summary, Issuetype issuetype, Project project) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraCreateTicketRequest.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraCreateTicketRequest.java index c97d6c8..f658e58 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraCreateTicketRequest.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraCreateTicketRequest.java @@ -17,9 +17,4 @@ */ package com.applause.auto.helpers.jira.dto.requestmappers; -import lombok.Data; - -@Data -public class JiraCreateTicketRequest { - private Fields fields; -} +public record JiraCreateTicketRequest(Fields fields) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraFields.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraFields.java index f543a0c..1c529a0 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraFields.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/JiraFields.java @@ -22,23 +22,15 @@ public class JiraFields { - private Fields fields = null; + private final Fields fields; - public JiraFields(String issueType, String projectId, String summary) { - fields = new Fields(); - - Issuetype issuetype = new Issuetype(); - issuetype.setId(issueType); - fields.setIssuetype(issuetype); - - Project project = new Project(); - project.setId(projectId); - fields.setProject(project); - - fields.setSummary(summary); + public JiraFields(final String issueType, final String projectId, final String summary) { + fields = new Fields(new Issuetype(issueType, null), new Project(projectId), summary); } public Fields getFields() { return fields; } + + public record Fields(Issuetype issuetype, Project project, String summary) {} } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepFieldsUpdate.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepFieldsUpdate.java index e5d2b02..80bd2bf 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepFieldsUpdate.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepFieldsUpdate.java @@ -17,11 +17,4 @@ */ package com.applause.auto.helpers.jira.dto.requestmappers; -import lombok.Data; - -@Data -public class StepFieldsUpdate { - private String status; - private String comment; - private String actualResult; -} +public record StepFieldsUpdate(String status, String comment, String actualResult) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepIterationAttachment.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepIterationAttachment.java index 4aa9085..32d106a 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepIterationAttachment.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/StepIterationAttachment.java @@ -17,11 +17,4 @@ */ package com.applause.auto.helpers.jira.dto.requestmappers; -import lombok.Data; - -@Data -public class StepIterationAttachment { - private String data; - private String filename; - private String contentType; -} +public record StepIterationAttachment(String data, String filename, String contentType) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/XrayAddTo.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/XrayAddTo.java index d0346a1..2384d72 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/XrayAddTo.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/requestmappers/XrayAddTo.java @@ -18,9 +18,5 @@ package com.applause.auto.helpers.jira.dto.requestmappers; import java.util.List; -import lombok.Data; -@Data -public class XrayAddTo { - private List add; -} +public record XrayAddTo(List add) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableIssueTypes.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableIssueTypes.java index 1eb7380..42e2b77 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableIssueTypes.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableIssueTypes.java @@ -20,10 +20,11 @@ import com.applause.auto.helpers.jira.dto.shared.Issuetype; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.List; -import lombok.Data; -@Data +/** + * Represents a list of available issue types. + * + * @param values The list of {@link Issuetype} objects. + */ @JsonIgnoreProperties(ignoreUnknown = true) -public class AvailableIssueTypes { - private List values; -} +public record AvailableIssueTypes(List values) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableProjects.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableProjects.java index f1fa8f2..c1ee0ee 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableProjects.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/AvailableProjects.java @@ -18,13 +18,14 @@ package com.applause.auto.helpers.jira.dto.responsemappers; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; -@Data +/** + * A record representing the available projects in Jira. + * + * @param self The URL of the project. + * @param id The ID of the project. + * @param key The key of the project. + * @param name The name of the project. + */ @JsonIgnoreProperties(ignoreUnknown = true) -public class AvailableProjects { - private String self; - private String id; - private String key; - private String name; -} +public record AvailableProjects(String self, String id, String key, String name) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/JiraCreateTicketResponse.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/JiraCreateTicketResponse.java index 2e5e1e0..5e24173 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/JiraCreateTicketResponse.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/JiraCreateTicketResponse.java @@ -17,11 +17,4 @@ */ package com.applause.auto.helpers.jira.dto.responsemappers; -import lombok.Data; - -@Data -public class JiraCreateTicketResponse { - private String id; - private String key; - private String self; -} +public record JiraCreateTicketResponse(String id, String key, String self) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/XrayTestRunDetails.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/XrayTestRunDetails.java index f14e96d..b4f7e50 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/XrayTestRunDetails.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/XrayTestRunDetails.java @@ -20,28 +20,24 @@ import com.applause.auto.helpers.jira.dto.responsemappers.iteration.Iteration; import com.applause.auto.helpers.jira.dto.responsemappers.steps.Step; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import java.util.ArrayList; import java.util.List; -import lombok.Data; -@Data @JsonIgnoreProperties(ignoreUnknown = true) -public class XrayTestRunDetails { - private int id; - private String status; - private String color; - private String testKey; - private String testExecKey; - private String executedBy; - private String startedOn; - private String finishedOn; - private String startedOnIso; - private String finishedOnIso; - private int duration; - private List iterations; - private List defects; - private List evidences; - private List testEnvironments; - private List fixVersions; - private ArrayList steps; -} +public record XrayTestRunDetails( + int id, + String status, + String color, + String testKey, + String testExecKey, + String executedBy, + String startedOn, + String finishedOn, + String startedOnIso, + String finishedOnIso, + int duration, + List iterations, + List defects, + List evidences, + List testEnvironments, + List fixVersions, + List steps) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/Iteration.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/Iteration.java index 71eade7..f519c2f 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/Iteration.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/Iteration.java @@ -19,12 +19,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.List; -import lombok.Data; -@Data @JsonIgnoreProperties(ignoreUnknown = true) -public class Iteration { - private int id; - private String status; - private List parameters; -} +public record Iteration(int id, String status, List parameters) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/IterationParameters.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/IterationParameters.java index 8f79d2c..1249206 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/IterationParameters.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/IterationParameters.java @@ -18,11 +18,6 @@ package com.applause.auto.helpers.jira.dto.responsemappers.iteration; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; -@Data @JsonIgnoreProperties(ignoreUnknown = true) -public class IterationParameters { - private String name; - private String value; -} +public record IterationParameters(String name, String value) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/TestRunIteration.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/TestRunIteration.java index 381b352..1330386 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/TestRunIteration.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/iteration/TestRunIteration.java @@ -19,16 +19,12 @@ import com.applause.auto.helpers.jira.dto.responsemappers.steps.Step; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import java.util.ArrayList; import java.util.List; -import lombok.Data; -@Data @JsonIgnoreProperties(ignoreUnknown = true) -public class TestRunIteration { - private int id; - private String testRunId; - private String status; - private List parameters; - private ArrayList steps; -} +public record TestRunIteration( + int id, + String testRunId, + String status, + List parameters, + List steps) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Action.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Action.java index 3bad947..d7de7b6 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Action.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Action.java @@ -18,10 +18,11 @@ package com.applause.auto.helpers.jira.dto.responsemappers.steps; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; -@Data +/** + * Represents an action. + * + * @param value The value associated with the action. + */ @JsonIgnoreProperties(ignoreUnknown = true) -public class Action { - private Value value; -} +public record Action(Value value) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Attachments.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Attachments.java index 8a14518..cc5ffed 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Attachments.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Attachments.java @@ -18,13 +18,14 @@ package com.applause.auto.helpers.jira.dto.responsemappers.steps; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; -@Data +/** + * A record representing an attachment. + * + * @param id The ID of the attachment. + * @param fileName The name of the file. + * @param fileURL The URL of the file. + * @param filePath The path to the file. + */ @JsonIgnoreProperties(ignoreUnknown = true) -public class Attachments { - private int id; - private String fileName; - private String fileURL; - private String filePath; -} +public record Attachments(int id, String fileName, String fileURL, String filePath) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Comment.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Comment.java index 82d213f..9fb0201 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Comment.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Comment.java @@ -18,10 +18,11 @@ package com.applause.auto.helpers.jira.dto.responsemappers.steps; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; -@Data +/** + * A record representing a comment in Jira. + * + * @param rendered The rendered content of the comment. + */ @JsonIgnoreProperties(ignoreUnknown = true) -public class Comment { - private String rendered; -} +public record Comment(String rendered) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Data.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Data.java index 729bb37..4727a87 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Data.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Data.java @@ -19,9 +19,11 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -@lombok.Data +/** + * Represents data with a type and value. + * + * @param type The type of the data. + * @param value The value of the data. + */ @JsonIgnoreProperties(ignoreUnknown = true) -public class Data { - private String type; - private Value value; -} +public record Data(String type, Value value) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Fields.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Fields.java index e2f8d9d..f04f89c 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Fields.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Fields.java @@ -18,10 +18,11 @@ package com.applause.auto.helpers.jira.dto.responsemappers.steps; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; -@Data +/** + * Represents the fields of a Jira issue. + * + * @param action The action associated with the fields. + */ @JsonIgnoreProperties(ignoreUnknown = true) -public class Fields { - public Action Action; -} +public record Fields(Action action) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Step.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Step.java index 40fd9c4..c025928 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Step.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Step.java @@ -19,17 +19,14 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.List; -import lombok.Data; -@Data @JsonIgnoreProperties(ignoreUnknown = true) -public class Step { - private int id; - private int index; - private String status; - private Fields fields; - private List attachments; - private Comment comment; - private List evidences; - private Comment actualResult; -} +public record Step( + int id, + int index, + String status, + Fields fields, + List attachments, + Comment comment, + List evidences, + Comment actualResult) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Steps.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Steps.java index a3c43e0..ffccf54 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Steps.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Steps.java @@ -18,11 +18,7 @@ package com.applause.auto.helpers.jira.dto.responsemappers.steps; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import java.util.ArrayList; -import lombok.Data; +import java.util.List; -@Data @JsonIgnoreProperties(ignoreUnknown = true) -public class Steps { - private ArrayList steps; -} +public record Steps(List steps) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Value.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Value.java index fb537b8..8ce6c0e 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Value.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/responsemappers/steps/Value.java @@ -18,10 +18,6 @@ package com.applause.auto.helpers.jira.dto.responsemappers.steps; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; -@Data @JsonIgnoreProperties(ignoreUnknown = true) -public class Value { - private String raw; -} +public record Value(String raw) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Issuetype.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Issuetype.java index a05194f..49f57a5 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Issuetype.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Issuetype.java @@ -18,11 +18,6 @@ package com.applause.auto.helpers.jira.dto.shared; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; -@Data @JsonIgnoreProperties(ignoreUnknown = true) -public class Issuetype { - private String id; - private String name; -} +public record Issuetype(String id, String name) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Project.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Project.java index a845fca..3146f07 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Project.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/dto/shared/Project.java @@ -17,9 +17,4 @@ */ package com.applause.auto.helpers.jira.dto.shared; -import lombok.Data; - -@Data -public class Project { - private String id; -} +public record Project(String id) {} diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraAnnotationException.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraAnnotationException.java index a61ead3..ff5a9bf 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraAnnotationException.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraAnnotationException.java @@ -19,7 +19,8 @@ public class JiraAnnotationException extends RuntimeException { - public JiraAnnotationException(String message) { - super(message); + public JiraAnnotationException( + final String message, final NullPointerException nullPointerException) { + super(message, nullPointerException); } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraPropertiesFileException.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraPropertiesFileException.java index 6ae228a..e57620e 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraPropertiesFileException.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/JiraPropertiesFileException.java @@ -19,7 +19,7 @@ public class JiraPropertiesFileException extends RuntimeException { - public JiraPropertiesFileException(String message) { + public JiraPropertiesFileException(final String message) { super(message); } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/UnidentifiedExecutionStatusException.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/UnidentifiedExecutionStatusException.java index 8753298..cbc54aa 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/UnidentifiedExecutionStatusException.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/exceptions/UnidentifiedExecutionStatusException.java @@ -19,7 +19,7 @@ public class UnidentifiedExecutionStatusException extends RuntimeException { - public UnidentifiedExecutionStatusException(String message) { + public UnidentifiedExecutionStatusException(final String message) { super(message); } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/FilesHelper.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/FilesHelper.java index f413bf9..7cc463c 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/FilesHelper.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/FilesHelper.java @@ -21,30 +21,57 @@ import java.io.IOException; import java.nio.file.Files; import java.util.Base64; +import lombok.NonNull; import lombok.SneakyThrows; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -public class FilesHelper { - +public final class FilesHelper { private static final Logger logger = LogManager.getLogger(FilesHelper.class); - public static String encodeBase64File(String filePath) throws IOException { + private FilesHelper() { + // utility class + } + + /** + * Encodes a file to Base64. + * + * @param filePath The path to the file to encode. + * @return The Base64 encoded string of the file. + * @throws IOException If an I/O error occurs while reading the file. + * @throws NullPointerException If the provided filePath is null. + */ + public static String encodeBase64File(@NonNull final String filePath) throws IOException { logger.info("Encoding file: {} to Base64", filePath); byte[] fileContent = Files.readAllBytes(getFile(filePath).toPath()); return Base64.getEncoder().encodeToString(fileContent); } - public static String getFileNameFromPath(String filePath) { + /** + * Gets the file name from a file path. + * + * @param filePath The path to the file. + * @return The name of the file. + * @throws NullPointerException If the provided filePath is null. + */ + public static String getFileNameFromPath(@NonNull final String filePath) { return getFile(filePath).getName(); } - public static String getFileType(String filePath) throws IOException { + /** + * Gets the file type from a file path. + * + * @param filePath The path to the file. + * @return The type of the file, or null if it cannot be determined. + * @throws IOException If an I/O error occurs while determining the file type. + * @throws NullPointerException If the provided filePath is null. + */ + public static String getFileType(@NonNull final String filePath) throws IOException { return Files.probeContentType(getFile(filePath).toPath()); } @SneakyThrows - private static File getFile(String filePath) { + private static File getFile(@NonNull final String filePath) { logger.info("Returning file from path: {}", filePath); return new File(filePath); } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/ResponseValidator.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/ResponseValidator.java index 8053db6..c80ae17 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/ResponseValidator.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/helper/ResponseValidator.java @@ -18,16 +18,23 @@ package com.applause.auto.helpers.jira.helper; import io.restassured.response.Response; +import lombok.NonNull; import org.apache.commons.lang3.Range; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -public class ResponseValidator { +public final class ResponseValidator { private static final Logger logger = LogManager.getLogger(ResponseValidator.class); + private ResponseValidator() { + // utility class + } + public static void checkResponseInRange( - Response response, Range expectedRange, String action) { + @NonNull final Response response, + @NonNull final Range expectedRange, + final String action) { int statusCode = response.statusCode(); if (expectedRange.contains(statusCode)) { logger.info("{} was successfully performed", action); diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/restclient/XrayRestAssuredClient.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/restclient/XrayRestAssuredClient.java index e6747b6..00ffe61 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/restclient/XrayRestAssuredClient.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/restclient/XrayRestAssuredClient.java @@ -17,24 +17,28 @@ */ package com.applause.auto.helpers.jira.restclient; -import static com.applause.auto.helpers.jira.requestData.XrayRequestHeaders.*; - import com.applause.auto.helpers.http.restassured.RestApiDefaultRestAssuredApiHelper; import com.applause.auto.helpers.jira.exceptions.JiraPropertiesFileException; +import com.applause.auto.helpers.util.XrayRequestHeaders; import io.restassured.specification.RequestSpecification; import java.io.File; +import lombok.NonNull; import org.aeonbits.owner.ConfigFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -public class XrayRestAssuredClient { +public final class XrayRestAssuredClient { private static final Logger logger = LogManager.getLogger(XrayRestAssuredClient.class); private static final String JIRA_PROPERTIES_FILE_PATH = "src/main/resources/jira.properties"; private static String jiraUrl; private static String token; - private static IJiraAppConfig configApp = ConfigFactory.create(IJiraAppConfig.class); + private static final IJiraAppConfig configApp = ConfigFactory.create(IJiraAppConfig.class); + + private XrayRestAssuredClient() { + // utility class + } private static void loadProperties() { if (!new File(JIRA_PROPERTIES_FILE_PATH).exists()) { @@ -53,13 +57,13 @@ public static RequestSpecification getRestClient() { return restApiDefaultRestAssuredApiHelper .withDefaultRestHttpClientConfigsSpecification() .baseUri(jiraUrl) - .header(getBearerAuthorizationHeader(token)) - .header(getAcceptApplicationJsonHeader()) - .header(getJSONContentTypeHeader()); + .header(XrayRequestHeaders.getBearerAuthorizationHeader(token)) + .header(XrayRequestHeaders.getAcceptApplicationJsonHeader()) + .header(XrayRequestHeaders.getJSONContentTypeHeader()); } - private static void checkJiraUrl(String jiraUrl) { - if (!jiraUrl.startsWith("https")) { + private static void checkJiraUrl(@NonNull final String jiraUrl2) { + if (!jiraUrl2.startsWith("https")) { logger.warn("API calls might return 415 because provided Jira url is not https!"); } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileContextsUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileContextsUtils.java index f454324..ae053af 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileContextsUtils.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileContextsUtils.java @@ -22,19 +22,24 @@ import io.appium.java_client.remote.SupportsContextSwitching; import java.util.Set; import java.util.concurrent.Callable; +import lombok.NonNull; import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** Helper class to work with mobile web and native contexts */ -public class MobileContextsUtils { +public final class MobileContextsUtils { private static final Logger logger = LogManager.getLogger(MobileContextsUtils.class); private static final String WEB_CONTEXT_PART_NAME = "web"; private static final String NATIVE_CONTEXT_PART_NAME = "native"; + private MobileContextsUtils() { + // utility class + } + /** * Perform actions in a web context, return result and switch back to native context * @@ -47,10 +52,10 @@ public class MobileContextsUtils { */ @SneakyThrows public static A performActionsInWebView( - SupportsContextSwitching driver, - Callable actions, - int waitForContextTimeout, - int waitPollingInterval) { + @NonNull final SupportsContextSwitching driver, + @NonNull final Callable actions, + final int waitForContextTimeout, + final int waitPollingInterval) { switchToFirstAvailableWebContext(driver, waitForContextTimeout, waitPollingInterval); try { return actions.call(); @@ -67,7 +72,9 @@ public static A performActionsInWebView( * @param waitPollingInterval - polling interval timeout for wait for context to appear */ public static void switchToFirstAvailableWebContext( - SupportsContextSwitching driver, int waitForContextTimeout, int waitPollingInterval) { + @NonNull final SupportsContextSwitching driver, + final int waitForContextTimeout, + final int waitPollingInterval) { waitForContextAndSwitchToIt( driver, WEB_CONTEXT_PART_NAME, waitForContextTimeout, waitPollingInterval); } @@ -80,7 +87,9 @@ public static void switchToFirstAvailableWebContext( * @param waitPollingInterval - polling interval timeout for wait for context to appear */ public static void switchToFirstAvailableNativeContext( - SupportsContextSwitching driver, int waitForContextTimeout, int waitPollingInterval) { + @NonNull final SupportsContextSwitching driver, + final int waitForContextTimeout, + final int waitPollingInterval) { waitForContextAndSwitchToIt( driver, NATIVE_CONTEXT_PART_NAME, waitForContextTimeout, waitPollingInterval); } @@ -94,10 +103,10 @@ public static void switchToFirstAvailableNativeContext( * @param waitPollingInterval - polling interval timeout for wait for context to appear */ public static void waitForContextAndSwitchToIt( - SupportsContextSwitching driver, - String contextStringPart, - int waitForContextTimeout, - int waitPollingInterval) { + @NonNull final SupportsContextSwitching driver, + @NonNull final String contextStringPart, + final int waitForContextTimeout, + final int waitPollingInterval) { logger.info("Looking for context containing: " + contextStringPart); waitForCondition( () -> { diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileElementUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileElementUtils.java index eca9082..4d47937 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileElementUtils.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileElementUtils.java @@ -35,17 +35,22 @@ import org.openqa.selenium.interactions.Sequence; /** Helper for mobile elements on native mobile */ -public class MobileElementUtils { +public final class MobileElementUtils { private static final Logger logger = LogManager.getLogger(MobileElementUtils.class); + private MobileElementUtils() { + // utility class + } + /** * Tap element center * * @param driver - automation driver * @param element - mobile element for tap on */ - public static void tapElementCenter(AppiumDriver driver, WebElement element) { + public static void tapElementCenter( + @NonNull final AppiumDriver driver, @NonNull final WebElement element) { Point centerOfElement = getCenter(element); tapElementByCoordinates(driver, centerOfElement); } @@ -56,7 +61,8 @@ public static void tapElementCenter(AppiumDriver driver, WebElement element) { * @param driver - automation driver * @param elementCoordinates - coordinates point for tap */ - public static void tapElementByCoordinates(AppiumDriver driver, Point elementCoordinates) { + public static void tapElementByCoordinates( + @NonNull final AppiumDriver driver, @NonNull final Point elementCoordinates) { PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger"); Sequence tap = new Sequence(finger, 1); tap.addAction( @@ -76,7 +82,8 @@ public static void tapElementByCoordinates(AppiumDriver driver, Point elementCoo * @param driver - automation driver * @param element - mobile element for long press on */ - public static void longPressOnElementCenter(AppiumDriver driver, WebElement element) { + public static void longPressOnElementCenter( + @NonNull final AppiumDriver driver, @NonNull final WebElement element) { Point centerOfElement = getCenter(element); PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger"); @@ -111,7 +118,11 @@ public static void longPressOnElementCenter(AppiumDriver driver, WebElement elem * @param duration - duration for tap */ public static void clickElementWithExecuteScript( - WebDriver driver, WebElement element, int tapCount, int touchCount, int duration) { + @NonNull final WebDriver driver, + @NonNull final WebElement element, + final int tapCount, + final int touchCount, + final int duration) { Point elementCenter = getCenter(element); logger.info("Element coords: " + elementCenter); Map tap = new HashMap<>(); diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileUtils.java index f863a94..7ce67e1 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileUtils.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/MobileUtils.java @@ -26,6 +26,7 @@ import java.time.Duration; import java.util.List; import java.util.stream.IntStream; +import lombok.NonNull; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.openqa.selenium.Dimension; @@ -35,12 +36,16 @@ import org.openqa.selenium.remote.RemoteWebDriver; /** Common mobile native utils */ -public class MobileUtils { +public final class MobileUtils { private static final Logger logger = LogManager.getLogger(MobileUtils.class); private static final String CF_BUNDLE_IDENTIFIER = "CFBundleIdentifier"; + private MobileUtils() { + // utility class + } + /** * Scroll down scroll view X times * @@ -53,13 +58,13 @@ public class MobileUtils { * @param scrollTimes - how many times to scroll */ public static void scrollVerticalSeveralTimes( - AppiumDriver driver, - double pStartXCoef, - double pStartYCoef, - double pEndYCoef, - int waitOption1, - int waitOption2, - int scrollTimes) { + @NonNull final AppiumDriver driver, + final double pStartXCoef, + final double pStartYCoef, + final double pEndYCoef, + final int waitOption1, + final int waitOption2, + final int scrollTimes) { IntStream.range(0, scrollTimes) .forEach( action -> @@ -78,12 +83,12 @@ public static void scrollVerticalSeveralTimes( * @param waitOption2 - wait action after move to */ public static void scrollVertical( - AppiumDriver driver, - double pStartXCoef, - double pStartYCoef, - double pEndYCoef, - int waitOption1, - int waitOption2) { + @NonNull final AppiumDriver driver, + final double pStartXCoef, + final double pStartYCoef, + final double pEndYCoef, + final int waitOption1, + final int waitOption2) { Dimension size = driver.manage().window().getSize(); @@ -129,7 +134,7 @@ public static void clearAndroidChromeCacheForLocalExecution() { * * @param driver - automation driver */ - public static void hideKeyboard(HidesKeyboard driver) { + public static void hideKeyboard(@NonNull final HidesKeyboard driver) { try { driver.hideKeyboard(); } catch (Exception e) { @@ -145,7 +150,8 @@ public static void hideKeyboard(HidesKeyboard driver) { * @param y - y coordinate press * @param waitOption - wait action option value in millis */ - public static void pressByCoordinates(AppiumDriver driver, int x, int y, long waitOption) { + public static void pressByCoordinates( + @NonNull final AppiumDriver driver, final int x, final int y, final long waitOption) { logger.info("Pressing natively by coordinates: " + x + " " + y); PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger"); @@ -173,7 +179,9 @@ public static void pressByCoordinates(AppiumDriver driver, int x, int y, long wa * @param waitAfterPageSourceRefresh - wait after page source refresh in millis */ public static void refreshPageSource( - WebDriver driver, int waitBeforePageSourceRefresh, int waitAfterPageSourceRefresh) { + @NonNull final WebDriver driver, + final int waitBeforePageSourceRefresh, + final int waitAfterPageSourceRefresh) { logger.info("Refresh page source"); ThreadHelper.sleep(waitBeforePageSourceRefresh); getMobileDriver(driver).getPageSource(); @@ -186,7 +194,7 @@ public static void refreshPageSource( * @param driver - automation driver * @return casted mobile driver or exception if driver casting to mobile one is not supported */ - public static AppiumDriver getMobileDriver(WebDriver driver) { + public static AppiumDriver getMobileDriver(@NonNull final WebDriver driver) { if (driver instanceof AppiumDriver) { return (AppiumDriver) driver; } else { @@ -197,9 +205,10 @@ public static AppiumDriver getMobileDriver(WebDriver driver) { /** * Get App Bundle Identifier for android device * - * @return String + * @param driver The StartsActivity driver instance. + * @return The app bundle identifier. */ - public static String getBundleIdentifierAndroid(StartsActivity driver) { + public static String getBundleIdentifierAndroid(@NonNull final StartsActivity driver) { String bundleId = driver.getCurrentPackage(); logger.info(String.format("App bundle ID is [%s]", bundleId)); return bundleId; @@ -208,28 +217,34 @@ public static String getBundleIdentifierAndroid(StartsActivity driver) { /** * Get App Bundle Identifier iOS * - * @return String + * @param driver The RemoteWebDriver instance. + * @return The app bundle identifier. */ - public static String getBundleIdentifierIos(RemoteWebDriver driver) { + public static String getBundleIdentifierIos(@NonNull final RemoteWebDriver driver) { String bundleId = getMobileDriver(driver).getCapabilities().getCapability(CF_BUNDLE_IDENTIFIER).toString(); - logger.info(String.format("App bundle ID is [%s]", bundleId)); + logger.info("App bundle ID is [{}]", bundleId); return bundleId; } /** * Get App Bundle Identifier * - * @return String + * @param driver The RemoteWebDriver instance. + * @return The app bundle identifier. */ - public static String getBundleIdentifier(RemoteWebDriver driver) { + public static String getBundleIdentifier(@NonNull final RemoteWebDriver driver) { return driver instanceof IOSDriver ? getBundleIdentifierIos(driver) : getBundleIdentifierAndroid((StartsActivity) driver); } - /** Move App In Background */ - public static void moveAppToBackground(InteractsWithApps driver) { + /** + * Move App In Background + * + * @param driver The InteractsWithApps driver instance. + */ + public static void moveAppToBackground(@NonNull final InteractsWithApps driver) { logger.info("Move App To Background"); driver.runAppInBackground(Duration.ofSeconds(-1)); } @@ -237,9 +252,11 @@ public static void moveAppToBackground(InteractsWithApps driver) { /** * Activate App using bundleId and Return to AUT * - * @param bundleId Bundle ID + * @param driver The InteractsWithApps driver instance. + * @param bundleId The Bundle ID of the app to activate. */ - public static void activateApp(InteractsWithApps driver, String bundleId) { + public static void activateApp( + @NonNull final InteractsWithApps driver, @NonNull final String bundleId) { logger.info("Return to AUT."); driver.activateApp(bundleId); } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/MobileDeepLinksUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/MobileDeepLinksUtils.java index 7a2b788..1b8bd1c 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/MobileDeepLinksUtils.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/MobileDeepLinksUtils.java @@ -25,18 +25,22 @@ import java.time.Duration; import java.util.HashMap; import java.util.Map; +import lombok.NonNull; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.openqa.selenium.By; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.FluentWait; -import org.openqa.selenium.support.ui.Wait; /** Provides some common mobile deeplinks navigation methods */ -public class MobileDeepLinksUtils { +public final class MobileDeepLinksUtils { private static final Logger logger = LogManager.getLogger(MobileDeepLinksUtils.class); + private MobileDeepLinksUtils() { + // utility class + } + /** * Open deeplink on Android * @@ -44,11 +48,12 @@ public class MobileDeepLinksUtils { * @param androidDeepLink - Andrdoid deeplink DTO object */ public static void openDeepLinkOnAndroid( - AppiumDriver driver, NativeMobileAppCommonDeeplink.AndroidDeepLink androidDeepLink) { - logger.info("Opening a Android deeplink: " + androidDeepLink.getDeepLinkUrl()); + @NonNull final AppiumDriver driver, + @NonNull final NativeMobileAppCommonDeeplink.AndroidDeepLink androidDeepLink) { + logger.info("Opening a Android deeplink: " + androidDeepLink.deepLinkUrl()); Map parameters = new HashMap<>(); - parameters.put("url", androidDeepLink.getDeepLinkUrl()); - parameters.put("package", androidDeepLink.getDeepLinkPackage()); + parameters.put("url", androidDeepLink.deepLinkUrl()); + parameters.put("package", androidDeepLink.deepLinkPackage()); driver.executeScript("mobile:deepLink", parameters); // another possible approach // getMobileDriver().get(androidDeepLink.getDeepLinkUrl()); @@ -63,12 +68,13 @@ public static void openDeepLinkOnAndroid( * deeplink * @param waitForSafariURLBarPollingInSec - polling timeout for Safari URL bar to appear during * opening a deeplink + * @param the driver */ public static void openDeepLinkOniOS( - T driver, - NativeMobileAppCommonDeeplink.iOSDeepLink iosDeepLink, - int waitForSafariURLBarTimeoutInSec, - int waitForSafariURLBarPollingInSec) { + @NonNull final T driver, + @NonNull final NativeMobileAppCommonDeeplink.IOSDeepLink iosDeepLink, + final int waitForSafariURLBarTimeoutInSec, + final int waitForSafariURLBarPollingInSec) { logger.info("Launch Safari and enter the deep link in the address bar"); Map parameters = new HashMap<>(); parameters.put("bundleId", "com.apple.mobilesafari"); @@ -80,8 +86,8 @@ public static void openDeepLinkOniOS( // Wait for the url button to appear and click on it so the text field will appear // iOS 13 now has the keyboard open by default because the URL field has focus when opening // the Safari browser - Wait wait = - new FluentWait(driver) + final var wait = + new FluentWait<>(driver) .withTimeout(Duration.ofSeconds(waitForSafariURLBarTimeoutInSec)) .pollingEvery(Duration.ofSeconds(waitForSafariURLBarPollingInSec)) .ignoring(Exception.class); @@ -96,7 +102,7 @@ public static void openDeepLinkOniOS( By urlFieldSelector = AppiumBy.iOSNsPredicateString("type == 'XCUIElementTypeTextField' && name CONTAINS 'URL'"); - driver.findElement(urlFieldSelector).sendKeys(iosDeepLink.getDeepLinkUrl()); + driver.findElement(urlFieldSelector).sendKeys(iosDeepLink.deepLinkUrl()); By goSelector = AppiumBy.iOSNsPredicateString("type == 'XCUIElementTypeButton' && label CONTAINS 'go'"); try { diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/NativeMobileAppCommonDeeplink.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/NativeMobileAppCommonDeeplink.java index 41239e9..b8739b6 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/NativeMobileAppCommonDeeplink.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/deeplinks/NativeMobileAppCommonDeeplink.java @@ -17,30 +17,28 @@ */ package com.applause.auto.helpers.mobile.deeplinks; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; - -/** Deeplink DTO to keep deeplink data for mobile deeplink navigation */ -@Builder -@Data -public class NativeMobileAppCommonDeeplink { - - private AndroidDeepLink androidDeepLink; - private iOSDeepLink iOSDeepLink; +/** + * Deeplink DTO to keep deeplink data for mobile deeplink navigation. + * + * @param androidDeepLink The Android deep link information. + * @param iOSDeepLink The iOS deep link information. + */ +public record NativeMobileAppCommonDeeplink( + AndroidDeepLink androidDeepLink, IOSDeepLink iOSDeepLink) { - /** Android deep link class. */ - @Data - @AllArgsConstructor - public static class AndroidDeepLink { - private String deepLinkUrl; - private String deepLinkPackage; - } + /** + * Android deep link record. + * + * @param deepLinkUrl The deep link URL. + * @param deepLinkPackage The deep link package. + */ + public record AndroidDeepLink(String deepLinkUrl, String deepLinkPackage) {} - /** iOS deep link class. */ - @Data - @AllArgsConstructor - public static class iOSDeepLink { - private String deepLinkUrl; - } + /** + * iOS deep link record. + * + * @param deepLinkUrl The deep link URL. + */ + @SuppressWarnings("checkstyle:TypeName") + public record IOSDeepLink(String deepLinkUrl) {} } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/file_uploading/SauceLabs/FileUploadingHelper.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/fileuploading/saucelabs/FileUploadingHelper.java similarity index 61% rename from auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/file_uploading/SauceLabs/FileUploadingHelper.java rename to auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/fileuploading/saucelabs/FileUploadingHelper.java index 2808c2c..9aa9e69 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/file_uploading/SauceLabs/FileUploadingHelper.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/mobile/fileuploading/saucelabs/FileUploadingHelper.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package com.applause.auto.helpers.mobile.file_uploading.SauceLabs; +package com.applause.auto.helpers.mobile.fileuploading.saucelabs; import com.applause.auto.helpers.mobile.MobileUtils; import com.applause.auto.helpers.util.ThreadHelper; @@ -33,74 +33,100 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; +import lombok.NonNull; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; -import org.testng.TestException; /** The type File Uploading Helper. */ -public class FileUploadingHelper { +public final class FileUploadingHelper { + private static final Logger logger = LogManager.getLogger(FileUploadingHelper.class); public static final String DEVICE_IMAGES_LOCATION_ANDROID = "sdcard/Download/%s"; + private FileUploadingHelper() { + // utility class + } + /** - * Upload Images to the device + * Upload Images to the device. * - * @param imagesPath + * @param driver The AppiumDriver instance. + * @param imagesPath the image file path. + * @param timeOuts The timeout in milliseconds. + * @param The type of AppiumDriver, which must extend both AppiumDriver and InteractsWithApps. + * @throws RuntimeException If an exception occurs during file upload. */ public static void uploadImages( - T driver, String imagesPath, long timeOuts) { + @NonNull final T driver, @NonNull final String imagesPath, final long timeOuts) { String bundleIdentifier = MobileUtils.getBundleIdentifier(driver); MobileUtils.moveAppToBackground(driver); - /** + /* * we have static wait because there is no way to check if the app was moved to the Background. * Sometimes, especially in the cloud, I found that image uploading started too early, and they * were uploaded but the app didn't see them */ ThreadHelper.sleep(5000); - getFilesFromPaths(imagesPath).forEach(image -> uploadFile(driver, image)); + getFilesFromPaths(imagesPath) + .forEach( + image -> { + try { + uploadFile(driver, image); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); ThreadHelper.sleep(timeOuts); MobileUtils.activateApp(driver, bundleIdentifier); } /** - * Upload one file + * Upload one file. * - * @param fileName + * @param driver the WebDriver instance to use. + * @param fileName the file to upload. + * @throws RuntimeException if an error occurs during file upload. */ - public static void uploadFile(WebDriver driver, File fileName) { + public static void uploadFile(@NonNull final WebDriver driver, @NonNull final File fileName) { try { logger.info("Uploading [{}] file", fileName); ((AndroidDriver) MobileUtils.getMobileDriver(driver)) .pushFile(String.format(DEVICE_IMAGES_LOCATION_ANDROID, fileName.getName()), fileName); } catch (Exception e) { - e.printStackTrace(); + logger.error(e); + throw new RuntimeException(e); // Re-throw the exception after logging it. } } /** * Get Files From Path * - * @param pathToFolder - * @return Set + * @param pathToFolder the folder path + * @return A Set of Files found in the given path. Returns an empty set if an IOException occurs. */ - private static Set getFilesFromPaths(String pathToFolder) { + private static Set getFilesFromPaths(@NonNull final String pathToFolder) { try (Stream stringStream = Files.walk(Paths.get(pathToFolder)).filter(Files::isRegularFile)) { - List pathList = stringStream.collect(Collectors.toList()); - Set filesInFolder = - pathList.stream().map(path -> new File(path.toString())).collect(Collectors.toSet()); - return filesInFolder; + List pathList = stringStream.toList(); + return pathList.stream().map(path -> new File(path.toString())).collect(Collectors.toSet()); } catch (IOException e) { - logger.info(e.getStackTrace().toString()); + logger.info(e); } return Collections.emptySet(); } - /** Clear Measurement images folder. */ - public static void clearImagesFolder(WebDriver driver) { + /** + * Clears the Measurement images folder on the device. + * + *

This method clears the folder located at "sdcard/greendot/hijacking/strip". It uses an ADB + * shell command to remove all files and subdirectories within the specified folder. + * + * @param driver The WebDriver instance used to interact with the device. Must not be null. = + */ + @SuppressWarnings("PMD.DoNotHardCodeSDCard") + public static void clearImagesFolder(@NonNull final WebDriver driver) { logger.info( "Clearing Mocked Measurement images folder on device [sdcard/greendot/hijacking/strip]"); try { @@ -122,12 +148,14 @@ public static void clearImagesFolder(WebDriver driver) { *

Need to use this caps to Enable image-injection on RDC -> * desiredCapabilities.setCapability("sauceLabsImageInjectionEnabled", true); * - *

Link - https://docs.saucelabs.com/mobile-apps/features/camera-image-injection/ + *

Link - ... * - * @param driver - * @param fileLocation + * @param driver the webdriver + * @param fileLocation file path */ - public static void injectImageToSauce(WebDriver driver, String fileLocation) { + public static void injectImageToSauce( + @NonNull final WebDriver driver, @NonNull final String fileLocation) { try { logger.info("Injecting [{}] file to Sauce device.", fileLocation); FileInputStream in = new FileInputStream(fileLocation); @@ -136,8 +164,8 @@ public static void injectImageToSauce(WebDriver driver, String fileLocation) { // Provide the transformed image to the device ((JavascriptExecutor) driver).executeScript("sauce:inject-image=" + qrCodeImage); } catch (Exception e) { - e.printStackTrace(); - throw new TestException("Image injection error."); + logger.error(e); + throw new RuntimeException("Image injection error.", e); } } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/sync/all/AllMatchCondition.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/sync/all/AllMatchCondition.java index 8e35367..7606dfc 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/sync/all/AllMatchCondition.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/sync/all/AllMatchCondition.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.Optional; import java.util.function.Function; -import lombok.RequiredArgsConstructor; import lombok.Setter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -39,7 +38,6 @@ * @param a type extending UIElement, a List of which serves as both the input and output types * of this Condition */ -@RequiredArgsConstructor public class AllMatchCondition implements Condition, List> { private static final Logger logger = LogManager.getLogger(); @@ -49,6 +47,20 @@ public class AllMatchCondition implements Condition @Setter private Duration timeout; @Setter private Duration pollingInterval; + /** + * Constructor for AllMatchCondition. + * + * @param elements The list of elements to check. + * @param function The function to apply to each element. + * @param description The description of the condition. + */ + public AllMatchCondition( + final List elements, final Function function, final String description) { + this.elements = elements; + this.function = function; + this.description = description; + } + /** * Run the Condition against the whole List. * diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/sync/all/AllMatchConditionBuilder.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/sync/all/AllMatchConditionBuilder.java index eaddaa1..5608b5f 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/sync/all/AllMatchConditionBuilder.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/sync/all/AllMatchConditionBuilder.java @@ -22,7 +22,6 @@ import com.applause.auto.pageobjectmodel.base.UIElement; import java.util.List; import java.util.function.Function; -import lombok.AllArgsConstructor; import lombok.NonNull; /** @@ -32,11 +31,19 @@ * @param a type extending UIElement, a List of which serves as both the input and output types * of this Condition */ -@AllArgsConstructor public class AllMatchConditionBuilder implements ConditionBuilder, List> { private final List elements; + /** + * Constructor for AllMatchConditionBuilder. + * + * @param elements The list of UI elements to evaluate. + */ + public AllMatchConditionBuilder(final List elements) { + this.elements = elements; + } + /** * Check if the List items meet a custom condition. * diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/TestDataProvider.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/TestDataProvider.java index 843aeb5..9dfbf8a 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/TestDataProvider.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/TestDataProvider.java @@ -24,18 +24,18 @@ public interface TestDataProvider { /** * read single value from test data file * - * @param dataPathSyntax - * @param - * @return + * @param dataPathSyntax file path + * @param test data type + * @return test data */ T readSingleValueFromTestDataFile(String dataPathSyntax); /** * read collection of values from test data file * - * @param dataPathSyntax - * @param - * @return + * @param dataPathSyntax file path + * @param test data type + * @return list of test data */ > C readValuesFromTestDataFile(String dataPathSyntax); } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/yaml/YamlTestDataProvider.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/yaml/YamlTestDataProvider.java index 7fb27ed..ec38061 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/yaml/YamlTestDataProvider.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/testdata/yaml/YamlTestDataProvider.java @@ -21,30 +21,35 @@ import io.github.yamlpath.YamlExpressionParser; import io.github.yamlpath.YamlPath; import java.io.FileInputStream; -import java.util.Set; +import java.util.Collection; import lombok.Getter; +import lombok.NonNull; import lombok.SneakyThrows; -/** Yaml test data reader based on https://github.com/yaml-path/YamlPath */ +/** Yaml test data reader based on ... */ public class YamlTestDataProvider implements TestDataProvider { @Getter private final String yamlTestDataFilePath; - private YamlExpressionParser yamlExpressionParser; + private final YamlExpressionParser yamlExpressionParser; @SneakyThrows - public YamlTestDataProvider(String yamlTestDataFilePath) { + public YamlTestDataProvider(@NonNull final String yamlTestDataFilePath) { this.yamlTestDataFilePath = yamlTestDataFilePath; - this.yamlExpressionParser = YamlPath.from(new FileInputStream(yamlTestDataFilePath)); + try (var file = new FileInputStream(yamlTestDataFilePath)) { + this.yamlExpressionParser = YamlPath.from(file); + } } @Override - public T readSingleValueFromTestDataFile(String yamlPathSyntax) { + public T readSingleValueFromTestDataFile(@NonNull final String yamlPathSyntax) { return yamlExpressionParser.readSingle(yamlPathSyntax); } + @SuppressWarnings("unchecked") @Override - public Set readValuesFromTestDataFile(String yamlPathSyntax) { - return yamlExpressionParser.read(yamlPathSyntax); + public > C readValuesFromTestDataFile( + @NonNull final String dataPathSyntax) { + return (C) yamlExpressionParser.read(dataPathSyntax); } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/AwaitilityWaitUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/AwaitilityWaitUtils.java index 80dc8e1..783e314 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/AwaitilityWaitUtils.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/AwaitilityWaitUtils.java @@ -20,10 +20,15 @@ import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import lombok.NonNull; import org.awaitility.Awaitility; /** Awaitility Wait Utils */ -public class AwaitilityWaitUtils { +public final class AwaitilityWaitUtils { + + private AwaitilityWaitUtils() { + // utility class + } /** * Wait for callable state with predicate and return it's value @@ -37,11 +42,11 @@ public class AwaitilityWaitUtils { * @return T - object instance that will be returned from callable actions during wait */ public static T waitForCondition( - Callable callable, - Predicate predicate, - int waitInterval, - int pollingInterval, - String alias) { + @NonNull final Callable callable, + @NonNull final Predicate predicate, + final int waitInterval, + final int pollingInterval, + final String alias) { return Awaitility.with() .pollInterval(pollingInterval, TimeUnit.SECONDS) .pollInSameThread() @@ -60,7 +65,10 @@ public static T waitForCondition( * @param alias - text alias for this wait */ public static void waitForCondition( - Callable callable, int waitInterval, int pollingInterval, String alias) { + @NonNull final Callable callable, + int waitInterval, + int pollingInterval, + final String alias) { Awaitility.with() .pollInterval(pollingInterval, TimeUnit.SECONDS) .pollInSameThread() diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/RandomUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/RandomUtils.java index 9efecf0..8b02fb1 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/RandomUtils.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/RandomUtils.java @@ -18,19 +18,25 @@ package com.applause.auto.helpers.util; import com.github.javafaker.Faker; +import java.util.Locale; import org.apache.commons.lang3.RandomStringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** Random dat utils */ -public class RandomUtils { +public final class RandomUtils { private static final Logger logger = LogManager.getLogger(RandomUtils.class); + private RandomUtils() { + // utility class + } + /** * Get Faker object for further fake data generation * / * - * @return Faker - faker object from Faker library https://github.com/DiUS/java-faker + * @return Faker - faker object from Faker library ... */ public static Faker getFaker() { return new Faker(); @@ -46,20 +52,26 @@ public static Faker getFaker() { * @return generated password */ public static String getRandomValidUserAccountPassword( - int upperCaseSymbolsCount, - int lowCaseSymbolsCount, - int numericSymbolsCount, - boolean withSpecialCharacter) { + final int upperCaseSymbolsCount, + final int lowCaseSymbolsCount, + final int numericSymbolsCount, + final boolean withSpecialCharacter) { StringBuilder randomPasswordStringBuilder = new StringBuilder() - .append(RandomStringUtils.randomAlphabetic(upperCaseSymbolsCount).toUpperCase()) - .append(RandomStringUtils.randomAlphabetic(lowCaseSymbolsCount).toLowerCase()) - .append(RandomStringUtils.randomNumeric(numericSymbolsCount)); + .append( + RandomStringUtils.secure() + .nextAlphabetic(upperCaseSymbolsCount) + .toUpperCase(Locale.ENGLISH)) + .append( + RandomStringUtils.secure() + .nextAlphabetic(lowCaseSymbolsCount) + .toLowerCase(Locale.ENGLISH)) + .append(RandomStringUtils.secure().nextNumeric(numericSymbolsCount)); if (withSpecialCharacter) { - randomPasswordStringBuilder.append("$"); + randomPasswordStringBuilder.append('$'); } String randomPassword = randomPasswordStringBuilder.toString(); - logger.info("Newly generated random password is: " + randomPassword); + logger.info("Newly generated random password is: {}", randomPassword); return randomPassword; } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/ThreadHelper.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/ThreadHelper.java index d2cfb45..9da39eb 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/ThreadHelper.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/ThreadHelper.java @@ -17,8 +17,12 @@ */ package com.applause.auto.helpers.util; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + /** Utility helper class that provides common actions needed to control the current Thread. */ -public class ThreadHelper { +public final class ThreadHelper { + private static final Logger logger = LogManager.getLogger(ThreadHelper.class); private ThreadHelper() {} @@ -31,7 +35,7 @@ public static void sleep(long milliseconds) { try { Thread.sleep(milliseconds); } catch (InterruptedException e) { - e.printStackTrace(); + logger.error(e); } } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/requestData/XrayRequestHeaders.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/XrayRequestHeaders.java similarity index 85% rename from auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/requestData/XrayRequestHeaders.java rename to auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/XrayRequestHeaders.java index 3625a3d..eb16a6c 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/requestData/XrayRequestHeaders.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/util/XrayRequestHeaders.java @@ -15,11 +15,14 @@ * limitations under the License. * */ -package com.applause.auto.helpers.jira.requestData; +package com.applause.auto.helpers.util; import io.restassured.http.Header; -public class XrayRequestHeaders { +public final class XrayRequestHeaders { + private XrayRequestHeaders() { + // utility class + } public static Header getAcceptApplicationJsonHeader() { return new Header("Accept", "application/json"); @@ -29,7 +32,7 @@ public static Header getContentTypeMultipartFormDataHeader() { return new Header("Content-Type", "multipart/form-data"); } - public static Header getBearerAuthorizationHeader(String token) { + public static Header getBearerAuthorizationHeader(final String token) { return new Header("Authorization", "Bearer " + token); } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/HtmlUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/HtmlUtils.java index 4568e6a..3b2e8d7 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/HtmlUtils.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/HtmlUtils.java @@ -19,6 +19,7 @@ import com.applause.auto.helpers.util.AwaitilityWaitUtils; import java.util.List; +import lombok.NonNull; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jsoup.Jsoup; @@ -27,10 +28,14 @@ import us.codecraft.xsoup.Xsoup; /** Common methods for HTML-based content. */ -public class HtmlUtils { +public final class HtmlUtils { private static final Logger logger = LogManager.getLogger(HtmlUtils.class); + private HtmlUtils() { + // utility class + } + /** * Wait for page viewport to be not empty * @@ -40,13 +45,16 @@ public class HtmlUtils { * @param pollingInterval - polling interval for wait of viewport */ public static void waitForPageViewPortNotEmpty( - WebDriver driver, String xpathRootLocator, int waitInterval, int pollingInterval) { + @NonNull final WebDriver driver, + @NonNull final String xpathRootLocator, + final int waitInterval, + final int pollingInterval) { AwaitilityWaitUtils.waitForCondition( () -> { String currentPageSource = driver.getPageSource(); boolean gsdHtmlInViewportLoaded = wasHtmlInViewportLoadedByRootElementXpathLocator(currentPageSource, xpathRootLocator); - logger.info("Page viewport was loaded correctly: " + gsdHtmlInViewportLoaded); + logger.info("Page viewport was loaded correctly: {}", gsdHtmlInViewportLoaded); return gsdHtmlInViewportLoaded; }, waitInterval, @@ -55,7 +63,7 @@ public static void waitForPageViewPortNotEmpty( } private static boolean wasHtmlInViewportLoadedByRootElementXpathLocator( - String currentHtml, String xpathRootLocator) { + @NonNull final String currentHtml, @NonNull final String xpathRootLocator) { Document doc = Jsoup.parse(currentHtml); List list = Xsoup.compile(xpathRootLocator).evaluate(doc).list(); return !list.isEmpty(); diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebElementUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebElementUtils.java index 559a21e..1e151a2 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebElementUtils.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebElementUtils.java @@ -19,24 +19,30 @@ import com.applause.auto.helpers.util.ThreadHelper; import io.appium.java_client.AppiumDriver; -import io.appium.java_client.PerformsTouchActions; -import io.appium.java_client.TouchAction; import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.ios.IOSDriver; -import io.appium.java_client.touch.offset.PointOption; +import java.time.Duration; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; +import lombok.NonNull; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.openqa.selenium.*; +import org.openqa.selenium.interactions.PointerInput; +import org.openqa.selenium.interactions.Sequence; /** Common utils for web elements */ -public class WebElementUtils { +public final class WebElementUtils { private static final Logger logger = LogManager.getLogger(WebElementUtils.class); + private WebElementUtils() { + // utility class + } + /** * Get element Y offset * @@ -44,13 +50,14 @@ public class WebElementUtils { * @param element - web element * @return element Y offset value */ - public static int getElementYOffset(WebDriver driver, WebElement element) { + public static int getElementYOffset( + @NonNull final WebDriver driver, @NonNull final WebElement element) { int offsetY = Integer.parseInt( ((JavascriptExecutor) driver) .executeScript("return arguments[0].offsetTop", element) .toString()); - logger.info("Current element" + element + " offset y = " + offsetY); + logger.info("Current element{} offset y = {}", element, offsetY); return offsetY; } @@ -62,12 +69,12 @@ public static int getElementYOffset(WebDriver driver, WebElement element) { * @return element text content */ public static String getElementTextContentWithJS( - WebDriver driver, WebElement webElementWithTextContent) { + @NonNull final WebDriver driver, @NonNull final WebElement webElementWithTextContent) { JavascriptExecutor js = (JavascriptExecutor) driver; try { String textContent = (String) js.executeScript("return arguments[0].textContent", webElementWithTextContent); - logger.info("Text content is: " + textContent); + logger.info("Text content is: {}", textContent); return textContent; } catch (Exception e) { return StringUtils.EMPTY; @@ -82,7 +89,9 @@ public static String getElementTextContentWithJS( * @param waitBetweenEachInput - wait between input in millis */ public static void customSendKeysBySymbolWithDelay( - WebElement textBoxElement, String text, int waitBetweenEachInput) { + @NonNull final WebElement textBoxElement, + @NonNull final String text, + final int waitBetweenEachInput) { for (int symbolIndex = 0; symbolIndex < text.length(); symbolIndex++) { textBoxElement.sendKeys(Character.toString(text.charAt(symbolIndex))); ThreadHelper.sleep(waitBetweenEachInput); @@ -96,7 +105,7 @@ public static void customSendKeysBySymbolWithDelay( * @param waitBetweenEachDelete - wait between each backspace deletion */ public static void clearFieldValueWithBackspaceWithDelay( - WebElement inputWebElement, int waitBetweenEachDelete) { + @NonNull final WebElement inputWebElement, final int waitBetweenEachDelete) { int valueCharacters = inputWebElement.getAttribute("value").length(); for (int i = 0; i < valueCharacters + 1; i++) { inputWebElement.sendKeys(Keys.BACK_SPACE); @@ -110,7 +119,7 @@ public static void clearFieldValueWithBackspaceWithDelay( * @param driver - automation driver * @return boolean state whether this kind of driver is mobile one of not */ - public static boolean isMobileWebExecutionDriver(WebDriver driver) { + public static boolean isMobileWebExecutionDriver(@NonNull final WebDriver driver) { return driver instanceof AppiumDriver; } @@ -118,61 +127,93 @@ public static boolean isMobileWebExecutionDriver(WebDriver driver) { * Clicks an element at an accurate point on devices, with native tap. This method is to mitigate * issues where the different device sizes cause the element locations to differ. * - * @param element + * @param driver The WebDriver instance to use. + * @param element The web element to click. + * @param xOffset The x-offset from the element's center to click. + * @param yOffset The y-offset from the element's center to click. + * @param isTablet A boolean indicating whether the device is a tablet. This is used in + * calculating the accurate Y coordinate. */ public static void clickElementWithNativeTapWithOffset( - WebDriver driver, WebElement element, int xOffset, int yOffset, boolean isTablet) { + @NonNull final AppiumDriver driver, // Use AppiumDriver + @NonNull final WebElement element, + final int xOffset, + final int yOffset, + final boolean isTablet) { + int x = getAccuratePointX(driver, element) + xOffset; int y = getAccuratePointY(driver, element, isTablet) + yOffset; - logger.info("Clicking element with native tap at (" + x + ", " + y + ")."); - new TouchAction((PerformsTouchActions) driver).tap(PointOption.point(x, y)).release().perform(); + logger.info("Clicking element with native tap at ({}, {}).", x, y); + + PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger"); + Sequence tap = new Sequence(finger, 1); + + tap.addAction( + finger.createPointerMove(Duration.ofMillis(0), PointerInput.Origin.viewport(), x, y)); + tap.addAction(finger.createPointerDown(0)); + tap.addAction(finger.createPointerUp(0)); + + driver.perform(List.of(tap)); } /** - * Gets accurate X point for element + * Gets the accurate X coordinate of the center of a web element, accounting for window scaling. + * This method calculates the element's position and width, then adjusts for any difference + * between the browser's reported window size and the actual rendered viewport width. * - * @param element - * @return Accurate X point for element + * @param driver The WebDriver instance used to interact with the browser. Must not be null. + * @param element The WebElement whose X coordinate is to be retrieved. Must not be null. + * @return The accurate X coordinate of the center of the element, adjusted for window scaling. + * Returns an integer representing the pixel value. + * @throws NullPointerException If either the driver or the element is null. */ - public static int getAccuratePointX(WebDriver driver, WebElement element) { + public static int getAccuratePointX( + @NonNull final WebDriver driver, @NonNull final WebElement element) { double widthRatio = - (double) driver.manage().window().getSize().width - / (double) getJavascriptWindowWidth(driver); + (double) driver.manage().window().getSize().width / getJavascriptWindowWidth(driver); return (int) (getLocation(driver, element).getX() * widthRatio) + (element.getSize().getWidth() / 2); } /** - * @return JavaScript window width + * Retrieves the width of the browser window as reported by JavaScript. + * + * @param driver The WebDriver instance to use for executing JavaScript. Must not be null. + * @return The width of the browser window in pixels. + * @throws NullPointerException If the provided WebDriver is null. */ - public static int getJavascriptWindowWidth(WebDriver driver) { + public static int getJavascriptWindowWidth(@NonNull final WebDriver driver) { int windowWidth = ((Long) ((JavascriptExecutor) driver) .executeScript("return window.innerWidth || document.body.clientWidth")) .intValue(); - logger.info("Current window width is: " + windowWidth); + logger.info("Current window width is: {}", windowWidth); return windowWidth; } /** - * Gets the element location on the screen using JS. TODO: We are currently using this as a - * workaround as the default getLocation is not working in W3C mode + * Gets the element location on the screen using JS. This method is currently used as a workaround + * as the default getLocation is not working in W3C mode. * + * @param driver The WebDriver instance. * @param element The element to retrieve the location from. - * @return A point representing the location + * @return A point representing the location of the top-left corner of the element. */ - public static Point getLocation(WebDriver driver, WebElement element) { + public static Point getLocation( + @NonNull final WebDriver driver, @NonNull final WebElement element) { return getElementRect(driver, element).getPoint(); } /** - * Gets element rect. TODO: remove this when getLocation & getDimension work again in W3C mode. + * Gets element rect. * - * @param element the element - * @return the element rect + * @param driver The WebDriver instance. + * @param element The WebElement to get the rectangle of. + * @return The Rectangle representing the element's dimensions and position. */ - public static Rectangle getElementRect(WebDriver driver, WebElement element) { + public static Rectangle getElementRect( + @NonNull final WebDriver driver, @NonNull final WebElement element) { Map result = (Map) ((JavascriptExecutor) driver) @@ -186,43 +227,64 @@ public static Rectangle getElementRect(WebDriver driver, WebElement element) { } /** - * Gets accurate Y point for element + * Gets accurate Y point for element. This method calculates the accurate Y coordinate of a web + * element, taking into account differences between Appium and Selenium's reported window heights, + * and whether the device is a tablet or not. * - * @param element - * @param isTablet - - * @return Accurate Y point for element + * @param driver The WebDriver instance used to interact with the browser. + * @param element The WebElement for which to calculate the Y coordinate. + * @param isTablet A boolean indicating whether the device is a tablet (true) or a phone (false). + * @return The accurate Y coordinate of the element. */ - public static int getAccuratePointY(WebDriver driver, WebElement element, boolean isTablet) { + public static int getAccuratePointY( + @NonNull final WebDriver driver, @NonNull final WebElement element, final boolean isTablet) { int windowDiff = isTablet ? getWindowHeightDiffBetweenAppiumAndSelenium(driver) : (int) (getWindowHeightDiffBetweenAppiumAndSelenium(driver) / 1.5); int seleniumWindowHeight = driver.manage().window().getSize().getHeight(); - double heightRatio = ((double) seleniumWindowHeight / getJavascriptWindowHeight(driver)); + double heightRatio = (double) seleniumWindowHeight / getJavascriptWindowHeight(driver); return (int) ((heightRatio * getLocation(driver, element).getY()) + windowDiff) + element.getSize().getHeight() / 2; } /** - * @return the window height difference between Appium and Selenium + * Calculates the difference in window height between what Appium reports and what Selenium + * reports. This is often necessary due to differences in how Appium and Selenium handle window + * sizes, especially on mobile devices. + * + * @param driver The WebDriver instance to use for retrieving window dimensions. Must not be null. + * @return The difference in window height (Appium height - Selenium height). A positive value + * indicates that Appium reports a larger height than Selenium. A negative value indicates the + * opposite. + * @throws NullPointerException If the provided {@code driver} is {@code null}. */ - public static int getWindowHeightDiffBetweenAppiumAndSelenium(WebDriver driver) { + public static int getWindowHeightDiffBetweenAppiumAndSelenium(@NonNull final WebDriver driver) { int seleniumWindowHeight = driver.manage().window().getSize().getHeight(); int appiumHeight = getAppiumWindowHeight(driver); return appiumHeight - seleniumWindowHeight; } /** - * @return JavaScript window height + * Returns the JavaScript window height. + * + * @param driver The WebDriver instance to use for executing JavaScript. + * @return The height of the JavaScript window in pixels. */ - public static int getJavascriptWindowHeight(WebDriver driver) { + public static int getJavascriptWindowHeight(@NonNull final WebDriver driver) { return ((Long) ((JavascriptExecutor) driver) .executeScript("return window.innerHeight || document.body.clientHeight")) .intValue(); } - public static int getAppiumWindowHeight(WebDriver driver) { + /** + * Retrieves the height of the Appium window. + * + * @param driver The WebDriver instance. + * @return The height of the Appium window in pixels. + */ + public static int getAppiumWindowHeight(@NonNull final WebDriver driver) { String currentContext = getContext(driver); changeContext(driver, "NATIVE_APP"); int appiumHeight = @@ -233,16 +295,25 @@ public static int getAppiumWindowHeight(WebDriver driver) { return appiumHeight; } - public static boolean changeContext(WebDriver driver, String desiredContext) { + /** + * Switches the WebDriver's context to the desired context. + * + * @param driver The WebDriver instance. + * @param desiredContext The desired context to switch to. + * @return {@code true} if the context was successfully switched, {@code false} otherwise. + */ + public static boolean changeContext( + @NonNull final WebDriver driver, @NonNull final String desiredContext) { return isAndroid(driver) ? changeContextAndroid(driver, desiredContext) : changeContextIos(driver, desiredContext); } - private static boolean changeContextAndroid(WebDriver driver, String desiredContext) { + private static boolean changeContextAndroid( + @NonNull final WebDriver driver, @NonNull final String desiredContext) { try { Set contextNames = getAndroidDriver(driver).getContextHandles(); - Iterator contextNameIterator = contextNames.iterator(); + Iterator contextNameIterator = contextNames.iterator(); String contextName; do { @@ -250,22 +321,23 @@ private static boolean changeContextAndroid(WebDriver driver, String desiredCont return false; } - contextName = (String) contextNameIterator.next(); + contextName = contextNameIterator.next(); } while (!contextName.contains(desiredContext)); - logger.debug(String.format("Switching to context [%s].", contextName)); + logger.debug("Switching to context [{}].", contextName); getAndroidDriver(driver).context(contextName); return true; } catch (Exception var6) { - logger.error(String.format("Unable to switch to context [%s].", desiredContext), var6); + logger.error("Unable to switch to context [{}].", desiredContext, var6); return false; } } - private static boolean changeContextIos(WebDriver driver, String desiredContext) { + private static boolean changeContextIos( + @NonNull final WebDriver driver, @NonNull final String desiredContext) { try { Set contextNames = getIOSDriver(driver).getContextHandles(); - Iterator contextNameIterator = contextNames.iterator(); + Iterator contextNameIterator = contextNames.iterator(); String contextName; do { @@ -273,33 +345,40 @@ private static boolean changeContextIos(WebDriver driver, String desiredContext) return false; } - contextName = (String) contextNameIterator.next(); + contextName = contextNameIterator.next(); } while (!contextName.contains(desiredContext)); - logger.debug(String.format("Switching to context [%s].", contextName)); + logger.debug("Switching to context [{}].", contextName); getIOSDriver(driver).context(contextName); return true; } catch (Exception var6) { - logger.error(String.format("Unable to switch to context [%s].", desiredContext), var6); + logger.error("Unable to switch to context [{}].", desiredContext, var6); return false; } } - public static String getContext(WebDriver driver) { + /** + * Retrieves the current context of the WebDriver. + * + * @param driver The WebDriver instance. + * @return The current context as a String. + * @throws NullPointerException If the driver is null. + */ + public static String getContext(@NonNull final WebDriver driver) { return isAndroid(driver) ? getAndroidDriver(driver).getContext() : getIOSDriver(driver).getContext(); } - private static boolean isAndroid(WebDriver driver) { + private static boolean isAndroid(@NonNull final WebDriver driver) { return driver instanceof AndroidDriver; } - private static AndroidDriver getAndroidDriver(WebDriver driver) { + private static AndroidDriver getAndroidDriver(@NonNull final WebDriver driver) { return (AndroidDriver) driver; } - private static IOSDriver getIOSDriver(WebDriver driver) { + private static IOSDriver getIOSDriver(@NonNull final WebDriver driver) { return (IOSDriver) driver; } } diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebUtils.java b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebUtils.java index 176c723..26ba9a5 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebUtils.java +++ b/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/web/WebUtils.java @@ -17,11 +17,16 @@ */ package com.applause.auto.helpers.web; +import lombok.NonNull; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; /** Web common utils */ -public class WebUtils { +public final class WebUtils { + + private WebUtils() { + // utility class + } /** * JS scroll down @@ -29,7 +34,7 @@ public class WebUtils { * @param driver - automation driver * @param yValue - y value scroll to */ - public static void jsScrollDown(WebDriver driver, final int yValue) { + public static void jsScrollDown(@NonNull final WebDriver driver, final int yValue) { ((JavascriptExecutor) driver).executeScript("window.scrollBy(0, " + yValue + ");"); } @@ -39,8 +44,7 @@ public static void jsScrollDown(WebDriver driver, final int yValue) { * @param driver - automation driver * @return Y page position value */ - public static int getPagePositionY(WebDriver driver) { - String javascript = "return window.scrollY;"; + public static int getPagePositionY(@NonNull final WebDriver driver) { return (int) Float.parseFloat( String.valueOf(((JavascriptExecutor) driver).executeScript("return window.scrollY;"))); diff --git a/auto-sdk-java-integrations/pom.xml b/auto-sdk-java-integrations/pom.xml index 3f1de43..418db8b 100644 --- a/auto-sdk-java-integrations/pom.xml +++ b/auto-sdk-java-integrations/pom.xml @@ -56,8 +56,12 @@ test - com.sun.mail - jakarta.mail + jakarta.mail + jakarta.mail-api + + + org.eclipse.angus + angus-mail diff --git a/auto-sdk-java-testng/src/main/java/com/applause/auto/testng/TestNgContextUtils.java b/auto-sdk-java-testng/src/main/java/com/applause/auto/testng/TestNgContextUtils.java index 9ac0175..77601d9 100644 --- a/auto-sdk-java-testng/src/main/java/com/applause/auto/testng/TestNgContextUtils.java +++ b/auto-sdk-java-testng/src/main/java/com/applause/auto/testng/TestNgContextUtils.java @@ -80,7 +80,7 @@ public static ContextType getContextType(final @NonNull ITestResult testResult) * @param testResult The TestNG result * @return A Tuple containing the Class and Method used for the result. */ - private static Pair, Method> getUnderlyingClassAndMethod( + public static Pair, Method> getUnderlyingClassAndMethod( final @NonNull ITestResult testResult) { final ITestNGMethod testMethod = testResult.getMethod(); final String methodName = testMethod.getMethodName(); diff --git a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/listeners/XrayListener.java b/auto-sdk-java-testng/src/main/java/com/applause/auto/testng/listeners/XrayListener.java similarity index 68% rename from auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/listeners/XrayListener.java rename to auto-sdk-java-testng/src/main/java/com/applause/auto/testng/listeners/XrayListener.java index a249abd..7fcff3f 100644 --- a/auto-sdk-java-helpers/src/main/java/com/applause/auto/helpers/jira/listeners/XrayListener.java +++ b/auto-sdk-java-testng/src/main/java/com/applause/auto/testng/listeners/XrayListener.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package com.applause.auto.helpers.jira.listeners; +package com.applause.auto.testng.listeners; import com.applause.auto.helpers.jira.annotations.scanner.JiraAnnotationsScanner; import com.applause.auto.helpers.jira.clients.JiraXrayClient; @@ -23,8 +23,12 @@ import com.applause.auto.helpers.jira.dto.responsemappers.JiraCreateTicketResponse; import com.applause.auto.helpers.jira.exceptions.JiraAnnotationException; import com.applause.auto.helpers.jira.exceptions.UnidentifiedExecutionStatusException; +import com.applause.auto.testng.TestNgContextUtils; import com.fasterxml.jackson.core.JsonProcessingException; -import java.util.Arrays; +import java.lang.reflect.Method; +import java.util.Collections; +import lombok.NonNull; +import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.testng.ITestContext; @@ -34,24 +38,24 @@ public class XrayListener extends TestListenerAdapter { private static final Logger logger = LogManager.getLogger(XrayListener.class); - private JiraXrayClient jiraXrayClient = new JiraXrayClient(); + private final JiraXrayClient jiraXrayClient = new JiraXrayClient(); @Override - public void onTestFailure(ITestResult result) { + public void onTestFailure(@NonNull final ITestResult result) { logger.info("Setting status for FAILED test: [{}]", result.getTestName()); super.onTestFailure(result); updateJiraXray(result); } @Override - public void onTestSuccess(ITestResult result) { + public void onTestSuccess(@NonNull final ITestResult result) { logger.info("Setting status for PASSED test: [{}]", result.getTestName()); super.onTestSuccess(result); updateJiraXray(result); } @Override - public void onTestSkipped(ITestResult result) { + public void onTestSkipped(@NonNull final ITestResult result) { logger.info("Setting status for SKIPPED test: [{}]", result.getTestName()); super.onTestSkipped(result); updateJiraXray(result); @@ -60,37 +64,38 @@ public void onTestSkipped(ITestResult result) { /** * Updates Jira X-Ray with execution results * - * @param result + * @param result test result */ - private void updateJiraXray(ITestResult result) { + private void updateJiraXray(@NonNull final ITestResult result) { try { int testRunId = addTestToTestExecution(result); jiraXrayClient.getTestrunAPI().updateTestRun(testRunId, getExecutionStatus(result)); } catch (JsonProcessingException | JiraAnnotationException e) { - logger.error("Failed to add Test to Test Execution and update its execution status."); - e.printStackTrace(); + logger.error("Failed to add Test to Test Execution and update its execution status.", e); } } /** * Adds test to X-Ray Test Execution based on data passed as @JiraID annotation * - * @param result + * @param result test result * @return testRunId, String value that represents identifier of the test case executed under a * Test Execution - * @throws JsonProcessingException + * @throws JsonProcessingException when response JSON is invalid */ - private int addTestToTestExecution(ITestResult result) + private int addTestToTestExecution(@NonNull final ITestResult result) throws JsonProcessingException, JiraAnnotationException { - JiraCreateTicketResponse jiraCreateTicketResponseMapping = new JiraCreateTicketResponse(); ITestContext context = result.getTestContext(); - String jiraTestIdentifier = JiraAnnotationsScanner.getJiraIdentifier(result); + final Pair, Method> classAndMethod = + TestNgContextUtils.getUnderlyingClassAndMethod(result); + final Method underlyingMethod = classAndMethod.getRight(); + String jiraTestIdentifier = JiraAnnotationsScanner.getJiraIdentifier(underlyingMethod); // Assuming testExecKey refers to Test Execution key (ticket ID) the current test is using which // must be created before execution of the test String testExecutionKey = context.getAttribute("testExecKey").toString(); - jiraCreateTicketResponseMapping.setKey(testExecutionKey); - XrayAddTo xrayAddToMapping = new XrayAddTo(); - xrayAddToMapping.setAdd(Arrays.asList(jiraTestIdentifier)); + JiraCreateTicketResponse jiraCreateTicketResponseMapping = + new JiraCreateTicketResponse(null, testExecutionKey, null); + XrayAddTo xrayAddToMapping = new XrayAddTo(Collections.singletonList(jiraTestIdentifier)); jiraXrayClient .getExecutionsAPI() .addTestToTestExecution(jiraCreateTicketResponseMapping, xrayAddToMapping); @@ -102,28 +107,23 @@ private int addTestToTestExecution(ITestResult result) /** * Gets test execution status based on the ITestResult and maps it to accepted X-Ray values * - * @param result + * @param result test result * @return status */ - private String getExecutionStatus(ITestResult result) { + private String getExecutionStatus(@NonNull final ITestResult result) { String testKey = result.getMethod().getMethodName(); String status = ""; logger.info("Getting execution status for test key: {}", testKey); int statusCode = result.getStatus(); - switch (statusCode) { - case 1: - status = "PASS"; - break; - case 2: - status = "FAIL"; - break; - case 3: - status = "BLOCKED"; - break; - default: - throw new UnidentifiedExecutionStatusException( - String.format("Unable to determine status for code %s", statusCode)); - } + status = + switch (statusCode) { + case 1 -> "PASS"; + case 2 -> "FAIL"; + case 3 -> "BLOCKED"; + default -> + throw new UnidentifiedExecutionStatusException( + String.format("Unable to determine status for code %s", statusCode)); + }; return status; } } diff --git a/build-tools/checkstyle.xml b/build-tools/checkstyle.xml index 5e2379b..7376f0d 100644 --- a/build-tools/checkstyle.xml +++ b/build-tools/checkstyle.xml @@ -155,10 +155,6 @@ - - - - @@ -189,7 +185,7 @@ - + diff --git a/pom.xml b/pom.xml index 7a515f9..4d82e7a 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,8 @@ 3.3.2 1.0.12 1.18.34 - 2.1.2 + 2.1.3 + 2.0.3 2.3.31 6.0.0 4.4 @@ -80,8 +81,6 @@ 0.0.12 2.7.2 v4-rev612-1.25.0 - 2.1.3 - 5.13.0 5.2.0 @@ -240,9 +239,14 @@ ${javax.ws.rs-api.version} - com.sun.mail - jakarta.mail - 2.0.1 + jakarta.mail + jakarta.mail-api + ${jakarta.mail-api.version} + + + org.eclipse.angus + angus-mail + ${jakarta.angus.mail.version} com.github.spotbugs @@ -310,11 +314,6 @@ google-api-services-sheets ${google-api-services-sheets.version} - - jakarta.mail - jakarta.mail-api - ${jakarta.mail-api.version} -