From 7db0beb3add3e52af5ecfe2b6a88d5747d10209c Mon Sep 17 00:00:00 2001 From: Ismail Elshafeiy Date: Sat, 30 Dec 2023 04:12:15 +0200 Subject: [PATCH] Update Signed-off-by: Ismail Elshafeiy --- .gitignore | 2 +- ConsoleLogs/testConsole.txt | 12 - README.md | 2 +- pom.xml | 21 +- .../com/engine/actions/BrowserActions.java | 134 ++- .../com/engine/actions/ElementActions.java | 63 ++ .../java/com/engine/actions/FileActions.java | 39 +- .../com/engine/actions/RestApiActions.java | 15 +- .../com/engine/actions/TerminalActions.java | 21 +- .../engine/constants/FrameworkConstants.java | 15 +- .../com/engine/dataDriven/CSVFileManager.java | 47 +- .../engine/dataDriven/PropertiesManager.java | 4 +- .../java/com/engine/driver/DriverFactory.java | 107 +- .../java/com/engine/driver/DriverHelper.java | 165 ++- .../java/com/engine/driver/DriverOptions.java | 73 ++ ...imatedGifManager.java => AnimatedGif.java} | 4 +- .../com/engine/evidence/RecordManager.java | 124 -- .../java/com/engine/evidence/RecordVideo.java | 160 +-- .../java/com/engine/evidence/ScreenShot.java | 22 +- .../engine/evidence/ScreenshotManager.java | 10 +- .../com/engine/listeners/AllureListener.java | 195 ++++ .../listeners/AnnotationTransformer.java | 14 - .../com/engine/listeners/TestNGListener.java | 150 +++ .../com/engine/listeners/TestngListener.java | 172 --- .../java/com/engine/reports/AllureReport.java | 204 +++- .../java/com/engine/reports/Attachments.java | 242 ++-- .../com/engine/reports/CustomReporter.java | 102 +- .../java/com/engine/reports/ExtentReport.java | 192 +++- .../practice/gui/pages/alerts/AlertsPage.java | 5 + .../resources/properties/config.properties | 24 +- .../resources/properties/paths.properties | 10 +- src/test/java/webPractice/BaseTests.java | 2 +- .../java/webPractice/HandleHTMLCanvas.java | 55 - .../browserInteractions/NavigationTests.java | 9 +- ...ferenceTests.java => ScreenShotTests.java} | 21 +- .../webPractice/dataDriven/FilesTest.java | 57 +- .../dataDriven/Login_ReadDataUsingJson.java | 5 +- .../downloadFile/DownloadFile.java | 30 +- .../Login_Test.java | 12 +- .../interactions/HandleHTMLCanvas.java | 82 ++ .../filLoginForm/RegisterTest.java | 36 - .../JavaScriptExecutorBrowserActionsTest.java | 4 +- .../{ => TestData}/__files/24848.xml | 54 +- .../{ => TestData}/__files/90210.xml | 24 +- .../{ => TestData}/__files/albums.json | 1002 ++++++++--------- .../resources/{ => TestData}/__files/cars.xml | 32 +- .../__files/photosforalbum.json | 702 ++++++------ .../{ => TestData}/__files/users.json | 462 ++++---- .../resources/TestData/{ => csv}/CSVFile.csv | 0 .../resources/TestData/{ => csv}/CSVFile2.csv | 0 .../TestData/{ => excel}/ExcelFile.xlsx | Bin .../TestData/{ => excel}/LoginData.xlsx | Bin .../{ => TestData}/images/automation.png | Bin .../TestData/{ => json}/TestData.json | 26 +- .../{ => TestData}/mappings/24848_xml.json | 20 +- .../{ => TestData}/mappings/90210_xml.json | 20 +- .../mappings/AddressDeserialization.json | 20 +- .../mappings/AddressSerialization.json | 20 +- .../{ => TestData}/mappings/AlbumsJson.json | 20 +- .../{ => TestData}/mappings/CAY1A.json | 20 +- .../mappings/CarDeserialization.json | 20 +- .../mappings/CarSerialization.json | 26 +- .../{ => TestData}/mappings/CarsXml.json | 20 +- .../{ => TestData}/mappings/DE24848.json | 24 +- .../{ => TestData}/mappings/DE24848Xml.json | 20 +- .../mappings/PhotosForAlbum.json | 20 +- .../{ => TestData}/mappings/US12345.json | 20 +- .../{ => TestData}/mappings/US90210.json | 20 +- .../{ => TestData}/mappings/US99999.json | 20 +- .../{ => TestData}/mappings/UsersJson.json | 20 +- .../{ => TestData}/mappings/jsonFile.json | 124 +- .../{ => TestData}/mappings/location.json | 16 +- src/test/resources/downloadFiles2/CSVFile.csv | 15 - src/test/resources/downloadFiles2/java.jpg | Bin 53900 -> 0 bytes .../downloadFiles2/verifyNavigator.png | Bin 103480 -> 0 bytes 75 files changed, 2892 insertions(+), 2558 deletions(-) delete mode 100644 ConsoleLogs/testConsole.txt create mode 100644 src/main/java/com/engine/driver/DriverOptions.java rename src/main/java/com/engine/evidence/{AnimatedGifManager.java => AnimatedGif.java} (96%) delete mode 100644 src/main/java/com/engine/evidence/RecordManager.java create mode 100644 src/main/java/com/engine/listeners/AllureListener.java delete mode 100644 src/main/java/com/engine/listeners/AnnotationTransformer.java create mode 100644 src/main/java/com/engine/listeners/TestNGListener.java delete mode 100644 src/main/java/com/engine/listeners/TestngListener.java delete mode 100644 src/test/java/webPractice/HandleHTMLCanvas.java rename src/test/java/webPractice/browserInteractions/{ReferenceTests.java => ScreenShotTests.java} (74%) rename src/test/java/webPractice/{filLoginForm => elementActions}/Login_Test.java (74%) create mode 100644 src/test/java/webPractice/elementActions/interactions/HandleHTMLCanvas.java delete mode 100644 src/test/java/webPractice/filLoginForm/RegisterTest.java rename src/test/resources/{ => TestData}/__files/24848.xml (97%) rename src/test/resources/{ => TestData}/__files/90210.xml (96%) rename src/test/resources/{ => TestData}/__files/albums.json (94%) rename src/test/resources/{ => TestData}/__files/cars.xml (95%) rename src/test/resources/{ => TestData}/__files/photosforalbum.json (96%) rename src/test/resources/{ => TestData}/__files/users.json (96%) rename src/test/resources/TestData/{ => csv}/CSVFile.csv (100%) rename src/test/resources/TestData/{ => csv}/CSVFile2.csv (100%) rename src/test/resources/TestData/{ => excel}/ExcelFile.xlsx (100%) rename src/test/resources/TestData/{ => excel}/LoginData.xlsx (100%) rename src/test/resources/{ => TestData}/images/automation.png (100%) rename src/test/resources/TestData/{ => json}/TestData.json (94%) rename src/test/resources/{ => TestData}/mappings/24848_xml.json (95%) rename src/test/resources/{ => TestData}/mappings/90210_xml.json (95%) rename src/test/resources/{ => TestData}/mappings/AddressDeserialization.json (96%) rename src/test/resources/{ => TestData}/mappings/AddressSerialization.json (95%) rename src/test/resources/{ => TestData}/mappings/AlbumsJson.json (95%) rename src/test/resources/{ => TestData}/mappings/CAY1A.json (97%) rename src/test/resources/{ => TestData}/mappings/CarDeserialization.json (96%) rename src/test/resources/{ => TestData}/mappings/CarSerialization.json (96%) rename src/test/resources/{ => TestData}/mappings/CarsXml.json (95%) rename src/test/resources/{ => TestData}/mappings/DE24848.json (98%) rename src/test/resources/{ => TestData}/mappings/DE24848Xml.json (95%) rename src/test/resources/{ => TestData}/mappings/PhotosForAlbum.json (95%) rename src/test/resources/{ => TestData}/mappings/US12345.json (97%) rename src/test/resources/{ => TestData}/mappings/US90210.json (97%) rename src/test/resources/{ => TestData}/mappings/US99999.json (96%) rename src/test/resources/{ => TestData}/mappings/UsersJson.json (95%) rename src/test/resources/{ => TestData}/mappings/jsonFile.json (95%) rename src/test/resources/{ => TestData}/mappings/location.json (93%) delete mode 100644 src/test/resources/downloadFiles2/CSVFile.csv delete mode 100644 src/test/resources/downloadFiles2/java.jpg delete mode 100644 src/test/resources/downloadFiles2/verifyNavigator.png diff --git a/.gitignore b/.gitignore index e7f392e..5925516 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,6 @@ .project /.idea/ /.github/workflows/ -/ScreenShot/ +/src/test/resources/downloads/ScreenShot/ /.github/workflows/ /video/ diff --git a/ConsoleLogs/testConsole.txt b/ConsoleLogs/testConsole.txt deleted file mode 100644 index 079e124..0000000 --- a/ConsoleLogs/testConsole.txt +++ /dev/null @@ -1,12 +0,0 @@ -Console log found in Test- testConsole -__________________________________________________________ -Information Message in Console: [http://the-internet.herokuapp.com/asdf.jpg - Failed to load resource: the server responded with a status of 404 (Not Found)] -Console log found in Test- testConsole -__________________________________________________________ -Information Message in Console: [http://298279967.log.optimizely.com/event?a=298279967&d=298279967&y=false&n=http%3A%2F%2Fthe-internet.herokuapp.com%2Fbroken_images&u=oeu1670872283754r0.9912588330427856&wxhr=true&t=1670872283758&f=298349752,318188263 - Failed to load resource: net::ERR_NAME_NOT_RESOLVED] -Console log found in Test- testConsole -__________________________________________________________ -Information Message in Console: [http://the-internet.herokuapp.com/hjkl.jpg - Failed to load resource: the server responded with a status of 404 (Not Found)] -Console log found in Test- testConsole -__________________________________________________________ -Information Message in Console: [http://the-internet.herokuapp.com/favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)] diff --git a/README.md b/README.md index 202ca51..dc920a6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Me +Me # 🔧 Technologies diff --git a/pom.xml b/pom.xml index c3a1ba5..b8b0f25 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ 2.0 2.25.0 - 2.12.0 + 2.25.0 2.10.0 1.0.0 @@ -710,8 +710,22 @@ csv-comparator ${csv-compartor-version} - + + + e-iceblue + spire.xls + 13.11.0 + + + + + + com.e-iceblue + e-iceblue + https://repo.e-iceblue.com/nexus/content/groups/public/ + + @@ -763,8 +777,7 @@ listener - com.engine.listeners.TestngListener, com.engine.listeners.WebListener - + com.engine.listeners.TestNGListener, com.engine.listeners.WebListener diff --git a/src/main/java/com/engine/actions/BrowserActions.java b/src/main/java/com/engine/actions/BrowserActions.java index 8678834..6b4be5d 100644 --- a/src/main/java/com/engine/actions/BrowserActions.java +++ b/src/main/java/com/engine/actions/BrowserActions.java @@ -3,7 +3,10 @@ import com.engine.Helper; import com.engine.Waits; import com.engine.constants.FrameworkConstants; -import com.engine.reports.Attachments; +import com.engine.dataDriven.PropertiesManager; +import com.engine.evidence.RecordVideo; +import com.engine.reports.AllureReport; +import com.engine.reports.ExtentReport; import io.qameta.allure.Step; import org.openqa.selenium.*; import org.openqa.selenium.Dimension; @@ -11,12 +14,16 @@ import org.openqa.selenium.bidi.BiDiException; import org.openqa.selenium.chromium.ChromiumDriver; import org.openqa.selenium.devtools.DevToolsException; +import org.openqa.selenium.print.PrintOptions; import org.openqa.selenium.support.ui.ExpectedConditions; import org.testng.ITestResult; import com.engine.reports.CustomReporter; +import java.io.File; +import java.nio.file.Files; import java.util.*; +import static com.engine.reports.CustomReporter.passAction; import static org.testng.Assert.fail; public class BrowserActions { @@ -26,6 +33,10 @@ public BrowserActions(WebDriver driver) { BrowserActions.driver = driver; } + public static BrowserActions getInstance() { + return new BrowserActions(driver); + } + @Step("Navigate to URL: [{url}]") public static void navigateToUrl(WebDriver driver, String url) { try { @@ -148,8 +159,6 @@ public static void getWindowPositions(WebDriver driver) { } } - - //****** Switch Windows ******// public enum PageType { WINDOW("window"), TAB("tab"); private final String windowType; @@ -201,21 +210,18 @@ public static void switchToNewWindowByClickHyperLink(WebDriver driver, By elemen @Step("Check the test result and Close All Opened Browser Windows.....") public static void closeAllOpenedBrowserWindows(WebDriver driver, ITestResult result) throws Throwable { if (ITestResult.FAILURE == result.getStatus()) { - Attachments.attachScreenshotToAllureReport(driver); - Attachments.attachScreenshotToExtentReport(driver); + AllureReport.attachScreenshotToAllureReport(driver); + ExtentReport.attachScreenshotToExtentReport(driver); CustomReporter.logConsoleLogs(driver, result); -// if (System.getProperty("videoParams_scope").trim().equals("DriverSession")) { -// RecordManager.attachVideoRecording(); -// } + closeAllOpenedBrowserWindows(driver); + // eyesManager.abort(); } -// RecordManager.attachVideoRecording(); - closeAllOpenedBrowserWindows(driver); - // eyesManager.abort(); } @Step("Close All Opened Browser Windows.....") public static void closeAllOpenedBrowserWindows(WebDriver driver) { CustomReporter.logInfoStep("[Browser Action] Close all Opened Browser Windows"); + RecordVideo.attachVideoRecording(); if (driver != null) { try { driver.quit(); @@ -232,7 +238,10 @@ public static void closeAllOpenedBrowserWindows(WebDriver driver) { //*********************************************************************************************// public enum AlertAction { - ACCEPT("accept"), DISMISS("dismiss"), SET_TEXT("Text"), GET_TEXT("Get Text"); + ACCEPT("accept"), + DISMISS("dismiss"), + SET_TEXT("Text"), + GET_TEXT("Get Text"); private final String alertType; AlertAction(String alertType) { @@ -390,6 +399,21 @@ public BrowserActions goBack() { return this; } + public static void goBackUsingJavascript(WebDriver driver) { + try { + CustomReporter.logInfoStep("[Browser Action] Navigate Back from [" + getCurrentUrl(driver) + "]"); + ((JavascriptExecutor) driver).executeScript("window.history.go(-1)"); + } catch (Exception e) { + CustomReporter.logInfoStep(e.getMessage()); + fail(e.getMessage()); + } + } + + public BrowserActions goBackUsingJavascript() { + goBackUsingJavascript(driver); + return this; + } + public static void goForward(WebDriver driver) { try { driver.navigate().forward(); @@ -405,6 +429,22 @@ public BrowserActions goForward() { return this; } + public static void goForwardUsingJavascript(WebDriver driver) { + try { + ((JavascriptExecutor) driver).executeScript("window.history.forward()"); + CustomReporter.logInfoStep("[Browser Action] Navigate Forward [" + getCurrentUrl(driver) + "]"); + } catch (Exception e) { + CustomReporter.logError(e.getMessage()); + fail(e.getMessage()); + } + } + + public BrowserActions goForwardUsingJavascript() { + goForwardUsingJavascript(driver); + return this; + } + + public static void refreshPage(WebDriver driver) { try { CustomReporter.logInfoStep("[Browser Action] Refresh current page [" + getCurrentUrl(driver) + "]"); @@ -495,13 +535,16 @@ public BrowserActions getCurrentUrlUsingJavaScript() { return this; } + //**********************************************************************************************// + //************************************** Others Methods **************************************// + //*********************************************************************************************// public BrowserActions capturePageSnapshot() { var serializedPageData = capturePageSnapshot(driver); passAction(driver, serializedPageData); return this; } - public static String capturePageSnapshot(WebDriver driver) { + private static String capturePageSnapshot(WebDriver driver) { CustomReporter.logConsole("Capturing page snapshot..."); var serializedPageData = ""; try { @@ -526,53 +569,26 @@ public static String capturePageSnapshot(WebDriver driver) { } } - public static void passAction(WebDriver driver, String testData) { - String actionName = Thread.currentThread().getStackTrace()[2].getMethodName(); - passAction(driver, actionName, testData); - } - - public static void passAction(WebDriver driver, String actionName, String testData) { - reportActionResult(driver, actionName, testData, true); - } - - private static String reportActionResult(WebDriver driver, String actionName, String testData, - Boolean passFailStatus, - Exception... rootCauseException) { - actionName = Helper.convertToSentenceCase(actionName); - String message; - if (Boolean.TRUE.equals(passFailStatus)) { - message = "Browser Action: " + actionName; - } else { - message = "Browser Action: " + actionName + " failed"; - } + static String pdfPath = PropertiesManager.getPropertyValue("paths.properties", "pdfPath"); - List> attachments = new ArrayList<>(); - if (testData != null && !testData.isEmpty()) { - if (testData.length() >= 500 || testData.contains("") || testData.contains("") || testData.startsWith("From: ")) { - List actualValueAttachment = Arrays.asList("Browser Action Test Data - " + actionName, - "Actual Value", testData); - attachments.add(actualValueAttachment); - } else { - message = message + " \"" + testData.trim() + "\""; - } - } - - if (rootCauseException != null && rootCauseException.length >= 1) { - List actualValueAttachment = Arrays.asList("Browser Action Exception - " + actionName, - "Stacktrace", CustomReporter.formatStackTraceToLogEntry(rootCauseException[0])); - attachments.add(actualValueAttachment); - } - - message = message + "."; - - message = message.replace("Browser Action: ", ""); - if (!attachments.equals(new ArrayList<>())) { - CustomReporter.logAttachments(message, attachments); - } else { - CustomReporter.logInfoStep(message); + /** + * Print the page using the PrintOptions class and the PrintsPage interface of the WebDriver class + * + * @param driver - WebDriver Instance of the Browser + * @param pageRange - Page Range to be printed + * @throws + */ + public static void printPage(WebDriver driver, int pageRange) { + try { + CustomReporter.logInfoStep("Printing " + driver.getTitle() + " page....... "); + PrintsPage printer = ((PrintsPage) driver); + PrintOptions printOptions = new PrintOptions(); + printOptions.setPageRanges(String.valueOf(pageRange)); + Pdf pdf = printer.print(printOptions); + Files.write(new File(pdfPath + driver.getTitle() + Helper.getCurrentTime() + ".pdf").toPath(), OutputType.BYTES.convertFromBase64Png(pdf.getContent())); + } catch (Exception e) { + e.printStackTrace(); + CustomReporter.logError("Page not printed: " + e.getMessage()); } - return message; } - - } diff --git a/src/main/java/com/engine/actions/ElementActions.java b/src/main/java/com/engine/actions/ElementActions.java index 736c384..aed6bfd 100644 --- a/src/main/java/com/engine/actions/ElementActions.java +++ b/src/main/java/com/engine/actions/ElementActions.java @@ -30,6 +30,10 @@ public ElementActions(WebDriver driver) { this.driver = driver; } + public static ElementActions getInstance() { + return new ElementActions(driver); + } + @Step("Click on element: [{elementLocator}]") public static void click(WebDriver driver, By elementLocator) { @@ -409,6 +413,65 @@ public static boolean ClipboardActions(WebDriver driver, ClipboardAction action) return false; } } + //************************************** drawing Actions **********************************************// + //******************************************************************************************************************// + + public static void drawCircle(WebDriver driver, By canvasElement) { + ElementHelper.locatingElementStrategy(driver, canvasElement); + try { + actions = new Actions(driver); + CustomReporter.logInfoStep("[Element Action] Drawing [" + driver.findElement(canvasElement).getText() + "] "); + actions.moveToElement(driver.findElement(canvasElement)).clickAndHold(); + int numPoints = 10; + int radius = 50; + for (int i = 0; i < numPoints; i++) { + double theta = (2 * Math.PI * i) / numPoints; + int x = (int) (radius * Math.cos(theta)); + int y = (int) (radius * Math.sin(theta)); + actions.moveByOffset(x, y).perform(); + } + actions.release(driver.findElement(canvasElement)).build().perform(); + } catch (Exception e) { + CustomReporter.logError(e.getMessage()); + fail(e.getMessage()); + } + } + + public static void drawUsingJavaScript(WebDriver driver, By canvasElement) { + int canvasWidth = driver.findElement(canvasElement).getSize().getWidth(); + int canvasHeight = driver.findElement(canvasElement).getSize().getHeight(); + // Calculate the offsets for drawing the signature + int startX = canvasWidth / 4; // Adjust as needed + int startY = canvasHeight / 2; // Adjust as needed + // Use JavaScript to simulate mouse movements for drawing + JavascriptExecutor jsExecutor = (JavascriptExecutor) driver; + jsExecutor.executeScript("arguments[0].dispatchEvent(new MouseEvent('mousedown', {" + + "clientX: " + startX + ", clientY: " + startY + "}));", driver.findElement(canvasElement)); + // Simulate drawing strokes (adjust offsets and add more points as needed) + jsExecutor.executeScript("arguments[0].dispatchEvent(new MouseEvent('mousemove', {" + + "clientX: " + (startX + 100) + ", clientY: " + (startY + 30) + "}));", driver.findElement(canvasElement)); + jsExecutor.executeScript("arguments[0].dispatchEvent(new MouseEvent('mousemove', {" + + "clientX: " + (startX + 80) + ", clientY: " + (startY + 20) + "}));", driver.findElement(canvasElement)); + jsExecutor.executeScript("arguments[0].dispatchEvent(new MouseEvent('mousemove', {" + + "clientX: " + (startX + 60) + ", clientY: " + (startY + -20) + "}));", driver.findElement(canvasElement)); + jsExecutor.executeScript("arguments[0].dispatchEvent(new MouseEvent('mousemove', {" + + "clientX: " + (startX + 40) + ", clientY: " + (startY + -40) + "}));", driver.findElement(canvasElement)); + jsExecutor.executeScript("arguments[0].dispatchEvent(new MouseEvent('mousemove', {" + + "clientX: " + (startX + 20) + ", clientY: " + (startY + -60) + "}));", driver.findElement(canvasElement)); + jsExecutor.executeScript("arguments[0].dispatchEvent(new MouseEvent('mousemove', {" + + "clientX: " + (startX + 10) + ", clientY: " + (startY + -80) + "}));", driver.findElement(canvasElement)); + jsExecutor.executeScript("arguments[0].dispatchEvent(new MouseEvent('mousemove', {" + + "clientX: " + (startX + 5) + ", clientY: " + (startY + -100) + "}));", driver.findElement(canvasElement)); + // Simulate drawing strokes (adjust offsets and add more points as needed) + jsExecutor.executeScript("arguments[0].dispatchEvent(new MouseEvent('mousemove', {" + + "clientX: " + (startX + 70) + ", clientY: " + (startY + 10) + "}));", driver.findElement(canvasElement)); + jsExecutor.executeScript("arguments[0].dispatchEvent(new MouseEvent('mousemove', {" + + "clientX: " + (startX + 30) + ", clientY: " + (startY + 30) + "}));", driver.findElement(canvasElement)); + // Release the mouse button to complete the signature + jsExecutor.executeScript("arguments[0].dispatchEvent(new MouseEvent('mouseup', {" + + "clientX: " + (startX + 100) + ", clientY: " + (startY + 100) + "}));", driver.findElement(canvasElement)); + + } //******************************************* Java script actions **********************************************************// //***********************************************************************************************************************************// diff --git a/src/main/java/com/engine/actions/FileActions.java b/src/main/java/com/engine/actions/FileActions.java index 0e9a970..e75ffc5 100644 --- a/src/main/java/com/engine/actions/FileActions.java +++ b/src/main/java/com/engine/actions/FileActions.java @@ -3,6 +3,8 @@ import com.engine.Helper; import com.google.common.hash.Hashing; import com.engine.reports.CustomReporter; +import com.spire.xls.Workbook; +import com.spire.xls.Worksheet; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.filefilter.TrueFileFilter; @@ -62,21 +64,17 @@ public void copyFolderFromJar(String sourceFolderPath, String destinationFolderP URL url = new URL(sourceFolderPath.replace("file:", "jar:file:")); JarURLConnection jarConnection = (JarURLConnection) url.openConnection(); JarFile jarFile = jarConnection.getJarFile(); - /* * Iterate all entries in the jar file. */ for (Enumeration e = jarFile.entries(); e.hasMoreElements(); ) { - JarEntry jarEntry = e.nextElement(); String jarEntryName = jarEntry.getName(); String jarConnectionEntryName = jarConnection.getEntryName(); - /* * Extract files only if they match the path. */ if (jarEntryName.startsWith(jarConnectionEntryName)) { - String filename = jarEntryName.startsWith(jarConnectionEntryName) ? jarEntryName.substring(jarConnectionEntryName.length()) : jarEntryName; File currentFile = new File(destinationFolderPath, filename); if (!currentFile.toPath().normalize().startsWith(new File(destinationFolderPath).toPath())) { @@ -505,19 +503,43 @@ public Collection getFileList(String targetDirectory) { return filesList; } + public void convertFileToCSV(String excelFilePath, String csvFilePath) { + //Create an instance of Workbook class + Workbook workbook = new Workbook(); + //Load an Excel file + workbook.loadFromFile(excelFilePath); + //Get the first worksheet + Worksheet sheet = workbook.getWorksheets().get(0); + //Save the worksheet as CSV + sheet.saveToFile(csvFilePath, ","); + } + + public File getFileLastModified(String folderPath) { + File dir = new File(folderPath); + if (dir.isDirectory()) { + Optional opFile = Arrays.stream(dir.listFiles(File::isFile)).max((f1, f2) -> Long.compare(f1.lastModified(), f2.lastModified())); + if (opFile.isPresent()) { + CustomReporter.logInfoStep("getFileLastModified: " + opFile.get().getPath()); + return opFile.get(); + } else { + CustomReporter.logInfoStep("getFileLastModified: " + opFile.get().getPath()); + return null; + } + } + return null; + } public URL downloadFile(String targetFileURL, String destinationFilePath) { return downloadFile(targetFileURL, destinationFilePath, 0, 0); } - public URL downloadFile(String targetFileURL, String destinationFilePath, int connectionTimeout, - int readTimeout) { + public URL downloadFile(String targetFileURL, String destinationFilePath, int connectionTimeout, int readTimeout) { if (targetFileURL != null && destinationFilePath != null) { try { - CustomReporter.logError("Downloading a file from this url \"" + targetFileURL + "\" to this directory \"" + CustomReporter.logInfoStep("Downloading a file from this url \"" + targetFileURL + "\" to this directory \"" + destinationFilePath + "\", please wait as downloading may take some time..."); FileUtils.copyURLToFile(new URL(targetFileURL), new File(destinationFilePath), connectionTimeout, readTimeout); - CustomReporter.logError("Downloading completed successfully."); + CustomReporter.logInfoStep("Downloading completed successfully."); URL downloadedFile = new File(destinationFilePath).toURI().toURL(); passAction("Target File URL\"" + targetFileURL + "\" | Destination Folder: \"" + destinationFilePath + "\" | Connection Timeout: \"" + connectionTimeout + "\" | Read Timeout: \"" + readTimeout @@ -668,7 +690,6 @@ public String getAbsolutePath(String relativePath) { } - private File unpackArchive(File theFile, File targetDir) throws IOException { if (!theFile.exists()) { throw new IOException(theFile.getAbsolutePath() + " does not exist"); diff --git a/src/main/java/com/engine/actions/RestApiActions.java b/src/main/java/com/engine/actions/RestApiActions.java index 140404b..08f7aa5 100644 --- a/src/main/java/com/engine/actions/RestApiActions.java +++ b/src/main/java/com/engine/actions/RestApiActions.java @@ -1,6 +1,7 @@ package com.engine.actions; import com.aventstack.extentreports.markuputils.MarkupHelper; +import com.engine.reports.AllureReport; import com.engine.reports.Attachments; import io.qameta.allure.Step; import io.restassured.RestAssured; @@ -94,37 +95,37 @@ public Response performRequest(RequestType requestType, String serviceName, int if (headers != null) { request.headers(headers); String qHeaders = queryableRequestSpecs.getHeaders().toString(); - Attachments.attachApiRequestToAllureReport("Headers", qHeaders.getBytes()); + AllureReport.attachApiRequestToAllureReport("Headers", qHeaders.getBytes()); ExtentReport.info(MarkupHelper.createCodeBlock("Request Headers: " + "\n" + qHeaders)); } if (contentType != null) { request.contentType(contentType); String qContentType = queryableRequestSpecs.getContentType(); - Attachments.attachApiRequestToAllureReport("Content Type", qContentType.getBytes()); + AllureReport.attachApiRequestToAllureReport("Content Type", qContentType.getBytes()); ExtentReport.info(MarkupHelper.createCodeBlock("Request Content Type: " + qContentType)); } if (formParams != null) { request.formParams(formParams); String qFormParams = queryableRequestSpecs.getFormParams().toString(); - Attachments.attachApiRequestToAllureReport("Form params", qFormParams.getBytes()); + AllureReport.attachApiRequestToAllureReport("Form params", qFormParams.getBytes()); ExtentReport.info(MarkupHelper.createCodeBlock("Request Form params: " + "\n" + qFormParams)); } if (queryParams != null) { request.queryParams(queryParams); String qQueryParams = queryableRequestSpecs.getQueryParams().toString(); - Attachments.attachApiRequestToAllureReport("Query params", qQueryParams.getBytes()); + AllureReport.attachApiRequestToAllureReport("Query params", qQueryParams.getBytes()); ExtentReport.info(MarkupHelper.createCodeBlock("Request Query params: " + "\n" + qQueryParams)); } if (body != null) { request.body(body); String qBody = queryableRequestSpecs.getBody().toString(); - Attachments.attachApiRequestToAllureReport("Body", qBody.getBytes()); + AllureReport.attachApiRequestToAllureReport("Body", qBody.getBytes()); ExtentReport.info(MarkupHelper.createCodeBlock("Request Body: " + "\n" + qBody)); } if (cookies != null) { request.cookies(cookies); String qCookies = queryableRequestSpecs.getCookies().toString(); - Attachments.attachApiRequestToAllureReport("Cookies", qCookies.getBytes()); + AllureReport.attachApiRequestToAllureReport("Cookies", qCookies.getBytes()); ExtentReport.info(MarkupHelper.createCodeBlock("Request Cookies: " + "\n" + qCookies)); } request.filter(sessionFilter); @@ -141,7 +142,7 @@ public Response performRequest(RequestType requestType, String serviceName, int CustomReporter.logInfoStep(e.getMessage()); fail(e.getMessage()); } - Attachments.attachApiResponseToAllureReport(response.asByteArray()); + AllureReport.attachApiResponseToAllureReport(response.asByteArray()); ExtentReport.info(MarkupHelper.createCodeBlock("API Response: " + "\n" + response.asPrettyString())); return response; } diff --git a/src/main/java/com/engine/actions/TerminalActions.java b/src/main/java/com/engine/actions/TerminalActions.java index f308d84..1d31219 100644 --- a/src/main/java/com/engine/actions/TerminalActions.java +++ b/src/main/java/com/engine/actions/TerminalActions.java @@ -16,6 +16,9 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import static com.engine.reports.CustomReporter.failAction; +import static com.engine.reports.CustomReporter.passAction; + @SuppressWarnings("unused") public class TerminalActions { private String sshHostName = ""; @@ -237,24 +240,6 @@ public String getDockerUsername() { return dockerUsername; } - private void passAction(String actionName, String testData, String log) { - reportActionResult(actionName, testData, log, true); - } - - private void passAction(String testData, String log) { - String actionName = Thread.currentThread().getStackTrace()[2].getMethodName(); - passAction(actionName, testData, log); - } - - private void failAction(String actionName, String testData, Exception... rootCauseException) { - String message = reportActionResult(actionName, testData, null, false, rootCauseException); - CustomReporter.failReporter(TerminalActions.class, message, rootCauseException[0]); - } - - private void failAction(String testData, Exception... rootCauseException) { - String actionName = Thread.currentThread().getStackTrace()[2].getMethodName(); - failAction(actionName, testData, rootCauseException); - } private Session createSSHsession() { Session session = null; diff --git a/src/main/java/com/engine/constants/FrameworkConstants.java b/src/main/java/com/engine/constants/FrameworkConstants.java index 0abf06a..0ae001b 100644 --- a/src/main/java/com/engine/constants/FrameworkConstants.java +++ b/src/main/java/com/engine/constants/FrameworkConstants.java @@ -14,8 +14,19 @@ private FrameworkConstants() { public static final String PROJECT_PATH = FileActions.getDir(); private static final String CONFIG_PROP = "config.properties"; + private static final String PATH_PROP = "paths.properties"; + public static final Boolean AUTOMATIC_DOWNLOAD_DRIVER = Boolean.valueOf(PropertiesManager.getPropertyValue(CONFIG_PROP, "automaticallyInstallDriver")); + public static final String CHROME_DRIVER_PATH = PropertiesManager.getPropertyValue(CONFIG_PROP, "chromeDriverPath"); + public static final String FIREFOX_DRIVER_PATH = PropertiesManager.getPropertyValue(CONFIG_PROP, "firefoxDriverPath"); + public static final String EDGE_DRIVER_PATH = PropertiesManager.getPropertyValue(CONFIG_PROP, "edgeDriverPath"); public static final String EXECUTION_TYPE = PropertiesManager.getPropertyValue(CONFIG_PROP, "executionType"); public static final String BROWSER_TYPE = PropertiesManager.getPropertyValue(CONFIG_PROP, "browserType"); + public static final int SCRIPT_EXECUTION_TIMEOUT = Integer.parseInt(PropertiesManager.getPropertyValue(CONFIG_PROP, "scriptExecutionTimeout")); + public static final int PAGE_LOAD_TIMEOUT = Integer.parseInt(PropertiesManager.getPropertyValue(CONFIG_PROP, "pageLoadTimeout")); + public static final String BROWSER_DOWNLOAD_DIR = PropertiesManager.getPropertyValue(PATH_PROP, "downloadBrowserPath"); + public static final Boolean MAXIMIZE_OPTION = Boolean.valueOf(PropertiesManager.getPropertyValue(CONFIG_PROP, "startMaximize")); + public static final Boolean HEADLESS_OPTION = Boolean.valueOf(PropertiesManager.getPropertyValue(CONFIG_PROP, "headless")); + public static final Boolean RECORD_VIDEO = Boolean.valueOf(PropertiesManager.getPropertyValue(CONFIG_PROP, "recordVideo")); public static final String HOST = PropertiesManager.getPropertyValue(CONFIG_PROP, "host"); public static final String PORT = PropertiesManager.getPropertyValue(CONFIG_PROP, "port"); public static final String WIDTH = PropertiesManager.getPropertyValue(CONFIG_PROP, "width"); @@ -28,10 +39,8 @@ private FrameworkConstants() { public static final String REPORT_TITLE = PropertiesManager.getPropertyValue(CONFIG_PROP, "reportTitle"); public static final String EXTENT_REPORT_NAME = PropertiesManager.getPropertyValue(CONFIG_PROP, "reportName"); public static final int MAX_TRY = Integer.parseInt(PropertiesManager.getPropertyValue(CONFIG_PROP, "retryFailedTest")); - // public static final String BASE_URL = PropertiesReader.getPropertyValue("automationPractice.properties", "phptravels.baseuri"); public static final String BASE_URL = PropertiesManager.getPropertyValue(CONFIG_PROP, "rest.baseUrl"); - public static final String HOME_URL_TAU = PropertiesManager.getPropertyValue("config.properties", "TAU.homeUrl"); - + public static final String HOME_URL_TAU = PropertiesManager.getPropertyValue("paths.properties", "TAU.homeUrl"); public static final String YES = "yes"; public static final String NO = "no"; diff --git a/src/main/java/com/engine/dataDriven/CSVFileManager.java b/src/main/java/com/engine/dataDriven/CSVFileManager.java index 1efd23e..21c0fcd 100644 --- a/src/main/java/com/engine/dataDriven/CSVFileManager.java +++ b/src/main/java/com/engine/dataDriven/CSVFileManager.java @@ -12,10 +12,9 @@ import org.testng.Assert; import org.testng.asserts.SoftAssert; -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; -import java.io.Reader; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; @@ -54,7 +53,6 @@ public static String[][] getAllData(String filePath) throws IOException, CsvExce return csvReader.readAll().toArray(new String[0][]); } - // Java code to illustrate reading a // CSV file line by line public static void readDataLineByLine(String file) { try { @@ -65,7 +63,6 @@ public static void readDataLineByLine(String file) { // file reader as a parameter CSVReader csvReader = new CSVReader(filereader); String[] nextRecord; - // we are going to read data line by line while ((nextRecord = csvReader.readNext()) != null) { for (String cell : nextRecord) { @@ -78,7 +75,7 @@ public static void readDataLineByLine(String file) { } } - public static void compareTwoCSVFiles(String file1, String file2) throws IOException { + public static void compareTwoCSVFilesByLine(String file1, String file2) throws IOException { BufferedReader reader1 = new BufferedReader(new FileReader(file1)); BufferedReader reader2 = new BufferedReader(new FileReader(file2)); String line1 = reader1.readLine(); @@ -104,9 +101,9 @@ public static void compareTwoCSVFiles(String file1, String file2) throws IOExcep reader2.close(); } - public static void compareTwoCSVFiles2(String expectedCSVFilePath, String actualCSVFilePath) throws IOException { - List expectedFile = readCsvFile(expectedCSVFilePath); - List actualFile = readCsvFile(actualCSVFilePath); + public static void compareTwoCSVFilesByValue(String expectedCSVFilePath, String actualCSVFilePath) throws IOException { + List expectedFile = readCSVFileAsList(expectedCSVFilePath); + List actualFile = readCSVFileAsList(actualCSVFilePath); SoftAssert softAssert = new SoftAssert(); // Compare the size of both files Assert.assertEquals(expectedFile.size(), actualFile.size()); @@ -135,11 +132,7 @@ public static void compareTwoCSVFiles2(String expectedCSVFilePath, String actual softAssert.assertAll("Data mismatch between the two files"); } - private static List readCsvFile(String filePath) throws IOException { - Reader reader = new FileReader(filePath); - org.apache.commons.csv.CSVParser csvParser = CSVFormat.DEFAULT.parse(reader); - return csvParser.getRecords(); - } + // Java code to illustrate // Reading CSV File with different separator @@ -149,13 +142,11 @@ public static void readDataFromCustomSeparator(String file, String file2) { FileReader fileReader = new FileReader(file); FileReader fileReader2 = new FileReader(file2); - // create csvParser object with // custom separator semi-colon CSVParser parser = new CSVParserBuilder().withSeparator(',').build(); CSVParser parser2 = new CSVParserBuilder().withSeparator(',').build(); - // create csvReader object with parameter // fileReader and parser CSVReader csvReader = new CSVReaderBuilder(fileReader) @@ -164,7 +155,6 @@ public static void readDataFromCustomSeparator(String file, String file2) { CSVReader csvReader2 = new CSVReaderBuilder(fileReader2) .withCSVParser(parser2) .build(); - // Read all data at once List allData = csvReader.readAll(); List allData2 = csvReader2.readAll(); @@ -185,5 +175,26 @@ public static void readDataFromCustomSeparator(String file, String file2) { } } + public static List readCSVFileAsList(String filePath) { + try { + Reader reader = new FileReader(filePath); + org.apache.commons.csv.CSVParser csvParser = CSVFormat.DEFAULT.parse(reader); + return csvParser.getRecords(); + } catch (IOException e) { + e.printStackTrace(); + CustomReporter.logError("Error reading CSV file: " + filePath); + return null; + } + } + public static String readCSVFile(String filePath) { + try { + Path path = new File(filePath).toPath(); + return new String(Files.readAllBytes(path)); + } catch (IOException e) { + e.printStackTrace(); + CustomReporter.logError("Error reading CSV file: " + filePath); + return null; + } + } } diff --git a/src/main/java/com/engine/dataDriven/PropertiesManager.java b/src/main/java/com/engine/dataDriven/PropertiesManager.java index bb2fc5a..bda1ea9 100644 --- a/src/main/java/com/engine/dataDriven/PropertiesManager.java +++ b/src/main/java/com/engine/dataDriven/PropertiesManager.java @@ -35,7 +35,7 @@ public static Properties loadAllFiles() { properties.putAll(tempProp); } file.close(); - CustomReporter.logInfoStep("Loaded all properties files."); + CustomReporter.logConsole("Loaded all properties files."); return properties; } catch (IOException e) { CustomReporter.logError("Warning !! Can not Load All File."); @@ -58,7 +58,7 @@ public static String getPropertyValue(String propertyFileName, String propertyNa CustomReporter.logError(e.getMessage() + " Couldn't find any properties with the given property name: " + propertyName); e.printStackTrace(); } - CustomReporter.logInfoStep("Property value for [ " + propertyName + " ] is: [" + properties.getProperty(propertyName) + "] from file: [ " + propertyFileName + " ]"); + CustomReporter.logConsole("Property value for [ " + propertyName + " ] is: [" + properties.getProperty(propertyName) + "] from file: [ " + propertyFileName + " ]"); return properties.getProperty(propertyName); } diff --git a/src/main/java/com/engine/driver/DriverFactory.java b/src/main/java/com/engine/driver/DriverFactory.java index a8084b7..da48ba5 100644 --- a/src/main/java/com/engine/driver/DriverFactory.java +++ b/src/main/java/com/engine/driver/DriverFactory.java @@ -1,6 +1,6 @@ package com.engine.driver; -import io.github.bonigarcia.wdm.WebDriverManager; +import com.engine.evidence.RecordVideo; import io.qameta.allure.Step; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; @@ -8,15 +8,13 @@ import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.remote.RemoteWebDriver; import com.engine.reports.CustomReporter; -import org.testng.ITestContext; -import org.testng.ITestResult; -import org.testng.Reporter; import java.net.MalformedURLException; import java.net.URL; import static com.engine.constants.FrameworkConstants.*; import static com.engine.driver.DriverHelper.*; +import static com.engine.driver.DriverOptions.*; import static org.testng.Assert.fail; public class DriverFactory { @@ -31,30 +29,50 @@ public class DriverFactory { @Step("Initializing a new Web GUI Browser!.....") public static synchronized WebDriver getBrowser(BrowserType browserType, ExecutionType executionType) { //eyesManager = new EyesManager(driver.get(), appName); - CustomReporter.logInfoStep("Initialize [" + browserType.getValue() + "] Browser and the Execution Type is [" + executionType.getValue() + "]"); - boolean googleChrome = browserType == BrowserType.GOOGLE_CHROME || (browserType == BrowserType.FROM_EXCEL && BROWSER_TYPE.equalsIgnoreCase("google chrome")); - boolean mozillaFirefox = browserType == BrowserType.MOZILLA_FIREFOX || (browserType == BrowserType.FROM_EXCEL && BROWSER_TYPE.equalsIgnoreCase("mozilla firefox")); - boolean microsoftEdge = browserType == BrowserType.MICROSOFT_EDGE || (browserType == BrowserType.FROM_EXCEL && BROWSER_TYPE.equalsIgnoreCase("microsoft edge")); + CustomReporter.logInfoStep("Initialize [ " + browserType.getValue() + " ] Browser and the Execution Type is [ " + executionType.getValue() + " ] and Headless Mode is [ " + HEADLESS_OPTION + " ]"); + boolean googleChrome = browserType == BrowserType.CHROME || (browserType == BrowserType.FROM_CONFIG_FILE && BROWSER_TYPE.equalsIgnoreCase("chrome")); + boolean mozillaFirefox = browserType == BrowserType.FIREFOX || (browserType == BrowserType.FROM_CONFIG_FILE && BROWSER_TYPE.equalsIgnoreCase("firefox")); + boolean microsoftEdge = browserType == BrowserType.EDGE || (browserType == BrowserType.FROM_CONFIG_FILE && BROWSER_TYPE.equalsIgnoreCase("edge")); String browserTypeWarningMsg = "The driver is null! because the browser type [" + BROWSER_TYPE + "] is not valid/supported; Please choose a valid browser type from the given choices in the properties file"; - // Remote execution condition - if (executionType == ExecutionType.REMOTE || (executionType == ExecutionType.FROM_EXCEL && BROWSER_TYPE.equalsIgnoreCase("remote"))) { + // Local execution condition + if (executionType == ExecutionType.LOCAL || (executionType == ExecutionType.FROM_CONFIG_FILE && EXECUTION_TYPE.equalsIgnoreCase("local"))) { + if (googleChrome) { + checkDriverDownloadOption(BrowserType.CHROME.getValue()); + driver = new ChromeDriver(DriverOptions.getChromeOptions()); + setITestContext(); + } else if (mozillaFirefox) { + checkDriverDownloadOption(BrowserType.FIREFOX.getValue()); + driver = new FirefoxDriver(DriverOptions.getFirefoxOptions()); + setITestContext(); + } else if (microsoftEdge) { + checkDriverDownloadOption(BrowserType.EDGE.getValue()); + driver = new EdgeDriver(DriverOptions.getEdgeOptions()); + setITestContext(); + } else { + CustomReporter.logError(browserTypeWarningMsg); + fail(browserTypeWarningMsg); + } + // Start video recording + RecordVideo.startVideoRecording(); + // Remote execution condition + } else if (executionType == ExecutionType.REMOTE || (executionType == ExecutionType.FROM_CONFIG_FILE && BROWSER_TYPE.equalsIgnoreCase("remote"))) { if (googleChrome) { try { - driver.set(new RemoteWebDriver(new URL("http://" + HOST + ":" + PORT + "/wd/hub"), getChromeOptions())); + driver = new RemoteWebDriver(new URL("http://" + HOST + ":" + PORT + "/wd/hub"), getChromeOptions()); setITestContext(); } catch (MalformedURLException e) { e.printStackTrace(); } } else if (mozillaFirefox) { try { - driver.set(new RemoteWebDriver(new URL("http://" + HOST + ":" + PORT + "/wd/hub"), getFirefoxOptions())); + driver = new RemoteWebDriver(new URL("http://" + HOST + ":" + PORT + "/wd/hub"), getFirefoxOptions()); setITestContext(); } catch (MalformedURLException e) { e.printStackTrace(); } } else if (microsoftEdge) { try { - driver.set(new RemoteWebDriver(new URL("http://" + HOST + ":" + PORT + "/wd/hub"), getEdgeOptions())); + driver = new RemoteWebDriver(new URL("http://" + HOST + ":" + PORT + "/wd/hub"), getEdgeOptions()); setITestContext(); } catch (MalformedURLException e) { e.printStackTrace(); @@ -62,50 +80,6 @@ public static synchronized WebDriver getBrowser(BrowserType browserType, Executi } else { CustomReporter.logError(browserTypeWarningMsg); fail(browserTypeWarningMsg); -// throw new NullPointerException(warningMsg); - } - } - // Local execution condition - else if (executionType == ExecutionType.LOCAL || (executionType == ExecutionType.FROM_EXCEL && EXECUTION_TYPE.equalsIgnoreCase("local"))) { - if (googleChrome) { - WebDriverManager.chromedriver().setup(); - driver.set(new ChromeDriver()); - setITestContext(); - checkMaximizeOption(); - //RecordManager.startVideoRecording(driver.get()); - } else if (mozillaFirefox) { -// WebDriverManager.firefoxdriver().setup(); - driver.set(new FirefoxDriver()); - setITestContext(); - checkMaximizeOption(); - } else if (microsoftEdge) { -// WebDriverManager.edgedriver().setup(); - driver.set(new EdgeDriver()); - checkMaximizeOption(); - setITestContext(); - } else { - CustomReporter.logError(browserTypeWarningMsg); - fail(browserTypeWarningMsg); -// throw new NullPointerException(warningMsg); - } - } - // Local Headless execution condition - else if (executionType == ExecutionType.LOCAL_HEADLESS || (executionType == ExecutionType.FROM_EXCEL && EXECUTION_TYPE.equalsIgnoreCase("local headless"))) { - if (googleChrome) { - //WebDriverManager.chromedriver().setup(); - driver.set(new ChromeDriver(getChromeOptions())); - setITestContext(); - } else if (mozillaFirefox) { - // WebDriverManager.firefoxdriver().setup(); - driver.set(new FirefoxDriver(getFirefoxOptions())); - setITestContext(); - } else if (microsoftEdge) { - // WebDriverManager.edgedriver().setup(); - driver.set(new EdgeDriver(getEdgeOptions())); - setITestContext(); - } else { - CustomReporter.logError(browserTypeWarningMsg); - fail(browserTypeWarningMsg); } } else { String warningMsg = "The driver is null! because the execution type [" + EXECUTION_TYPE + "] is not valid/supported; Please choose a valid execution type from the given choices in the properties file"; @@ -113,21 +87,16 @@ else if (executionType == ExecutionType.LOCAL_HEADLESS || (executionType == Exec fail(warningMsg); } // start session - return driver.get(); + return driver; } - public static WebDriver getBrowser() { - return getBrowser(BrowserType.FROM_EXCEL, ExecutionType.FROM_EXCEL); + public static WebDriver getBrowser(BrowserType browserType) { + return getBrowser(browserType, ExecutionType.FROM_CONFIG_FILE); } - private static void setITestContext() { - ITestResult result = Reporter.getCurrentTestResult(); - ITestContext context = result.getTestContext(); - context.setAttribute("driver", driver.get()); - try { - checkTimeoutImplicitOption(); - } catch (Exception e) { - e.printStackTrace(); - } + public static WebDriver getBrowser() { + return getBrowser(BrowserType.FROM_CONFIG_FILE, ExecutionType.FROM_CONFIG_FILE); } + + } diff --git a/src/main/java/com/engine/driver/DriverHelper.java b/src/main/java/com/engine/driver/DriverHelper.java index 1b48084..a126526 100644 --- a/src/main/java/com/engine/driver/DriverHelper.java +++ b/src/main/java/com/engine/driver/DriverHelper.java @@ -1,16 +1,19 @@ package com.engine.driver; -import com.engine.Waits; -import com.engine.actions.BrowserActions; +import com.engine.actions.FileActions; import com.engine.reports.CustomReporter; import com.engine.validations.EyesManager; +import io.github.bonigarcia.wdm.WebDriverManager; import org.openqa.selenium.WebDriver; -import org.openqa.selenium.chrome.ChromeOptions; -import org.openqa.selenium.edge.EdgeOptions; -import org.openqa.selenium.firefox.FirefoxOptions; +import org.testng.ITestContext; +import org.testng.ITestResult; +import org.testng.Reporter; + +import java.util.Objects; import static com.engine.constants.FrameworkConstants.*; + public class DriverHelper { /** * This class will hold all the browser related methods and variables that we will use in our framework, @@ -18,25 +21,58 @@ public class DriverHelper { * hold the enum that will hold the browser types that we support in our framework (chrome, firefox, edge), * hold the enum that will hold the execution types that we support in our framework (local, remote, local headless) and will also be used to read the execution type from the properties file */ - protected static final ThreadLocal driver = new ThreadLocal<>(); + protected static WebDriver driver; protected static EyesManager eyesManager; -// public static final String EXECUTION_TYPE = PropertiesReader.getPropertyValue("config.properties", "executionType"); -// public static final String BROWSER_TYPE = PropertiesReader.getPropertyValue("config.properties", "browserType"); -// public static final String HOST = PropertiesReader.getPropertyValue("config.properties", "host"); -// public static final String PORT = PropertiesReader.getPropertyValue("config.properties", "port"); -// public static String width = PropertiesReader.getPropertyValue("config.properties", "width"); -// public static String height = PropertiesReader.getPropertyValue("config.properties", "height"); -// public static final int TIMEOUT_EXPLICIT = Integer.parseInt(PropertiesReader.getPropertyValue("config.properties", "timeoutImplicitDefault")); -// public static final int POLLING = Integer.parseInt(PropertiesReader.getPropertyValue("config.properties", "fluentWaitpolling")); + + + public static void setITestContext() { + ITestResult result = Reporter.getCurrentTestResult(); + ITestContext context = result.getTestContext(); + context.setAttribute("driver", driver); + } + + public static void checkDriverDownloadOption(String browserType) { + switch (browserType.toLowerCase()) { + case "chrome": + if (Objects.equals(AUTOMATIC_DOWNLOAD_DRIVER, Boolean.TRUE)) { + CustomReporter.logConsole("Automatic download driver is enabled and the chrome driver will be downloaded automatically"); + WebDriverManager.chromedriver().setup(); + } else { + FileActions.getInstance().doesFileExist(CHROME_DRIVER_PATH); + System.setProperty("webdriver.chrome.driver", CHROME_DRIVER_PATH); + } + break; + case "firefox": + if (Objects.equals(Boolean.TRUE, AUTOMATIC_DOWNLOAD_DRIVER)) { + CustomReporter.logConsole("Automatic download driver is enabled and the gecko driver will be downloaded automatically"); + WebDriverManager.firefoxdriver().setup(); + } else { + FileActions.getInstance().doesFileExist(FIREFOX_DRIVER_PATH); + System.setProperty("webdriver.gecko.driver", FIREFOX_DRIVER_PATH); + } + break; + case "edge": + if (Objects.equals(Boolean.TRUE, AUTOMATIC_DOWNLOAD_DRIVER)) { + CustomReporter.logConsole("Automatic download driver is enabled and the edge driver will be downloaded automatically"); + WebDriverManager.edgedriver().setup(); + } else { + FileActions.getInstance().doesFileExist(EDGE_DRIVER_PATH); + System.setProperty("webdriver.edge.driver", EDGE_DRIVER_PATH); + } + break; + default: + CustomReporter.logError("The driver is null! because the browser type [ " + browserType + " ] is not valid/supported; Please choose a valid browser type from the given choices in the properties file"); + } + } /** * This enum will hold the browser types that we support in our framework (chrome, firefox, edge) */ public enum BrowserType { - MOZILLA_FIREFOX("Mozilla Firefox"), - GOOGLE_CHROME("Google Chrome"), - MICROSOFT_EDGE("Edge"), - FROM_EXCEL(BROWSER_TYPE); + FIREFOX("Firefox"), + CHROME("Chrome"), + EDGE("Edge"), + FROM_CONFIG_FILE(BROWSER_TYPE); private final String value; BrowserType(String type) { @@ -54,8 +90,7 @@ public String getValue() { public enum ExecutionType { LOCAL("Local"), REMOTE("Remote"), - LOCAL_HEADLESS("Local Headless"), - FROM_EXCEL(EXECUTION_TYPE); + FROM_CONFIG_FILE(EXECUTION_TYPE); private final String value; ExecutionType(String type) { @@ -68,10 +103,10 @@ String getValue() { } public enum Environment { - DEV("Local"), - STG("Remote"), - UAT("Local Headless"), - PROD("Local Headless"); + DEV("dev"), + STG("stg"), + UAT("uat"), + PROD("prod"); private final String value; Environment(String type) { @@ -84,10 +119,10 @@ String getValue() { } public enum TestDataAccount { - DEV("Local"), - STG("Remote"), - UAT("Local Headless"), - PROD("Local Headless"); + DEV("dev"), + STG("stg"), + UAT("uat"), + PROD("prod"); private final String value; TestDataAccount(String type) { @@ -99,70 +134,20 @@ String getValue() { } } - /** - * This method will check if the user wants to maximize the browser window or not and will maximize it if the - */ - public static void checkMaximizeOption() { - try { - String IS_MAXIMIZE = "true"; - if (IS_MAXIMIZE.equalsIgnoreCase("true")) { - BrowserActions.maximizeWindow(driver.get()); - } else if (IS_MAXIMIZE.equalsIgnoreCase("false") || IS_MAXIMIZE.equalsIgnoreCase("")) { - BrowserActions.setWindowSize(driver.get()); - } - } catch (Exception e) { - CustomReporter.logError("Error while set window size :" + e.getMessage()); - e.printStackTrace(); - } - } + public enum ArgumentsBrowserOptions { + HEADLESS("--headless"), + START_MAXIMIZED("--start-maximized"), + IN_PRIVATE("inprivate"), + DISABLE_NOTIFICATIONS("--disable-notifications"); - public static void checkTimeoutImplicitOption() { - try { - String timeoutImplicit = ""; - if (timeoutImplicit.equalsIgnoreCase("") || TIMEOUT_EXPLICIT > 30) { - // String timeoutImplicitDefault = PropertiesReader.getPropertyValue("config.properties", "timeoutImplicitDefault"); - Waits.implicitWait(driver.get(), TIMEOUT_EXPLICIT); - } else { - Waits.implicitWait(driver.get(), Integer.parseInt(timeoutImplicit)); - } - } catch (Exception e) { - CustomReporter.logError("Error while set implicit wait :" + e.getMessage()); - e.printStackTrace(); - } - } - - // public static ChromeOptions getChromeOptions() { -// ChromeOptions chOptions = new ChromeOptions(); -// chOptions.setHeadless(false); -// chOptions.addArguments("--window-size=1920,1080"); -// chOptions.addArguments("--start-maximized"); -// chOptions.setCapability("platform", Platform.LINUX); -// chOptions.addArguments("--headless"); -// chOptions.addArguments("disable--infobars"); -// chOptions.setExperimentalOption("excludeSwitches", new String[]{"enable-automation"}); -// chOptions.addArguments("--ignore-certificate-errors"); -// return chOptions; -// } - public static ChromeOptions getChromeOptions() { - ChromeOptions chOptions = new ChromeOptions(); - chOptions.addArguments("--disable-extensions"); - return chOptions; - } + private final String value; - public static FirefoxOptions getFirefoxOptions() { - FirefoxOptions ffOptions = new FirefoxOptions(); - ffOptions.setHeadless(false); - ffOptions.addArguments("--window-size=1920,1080"); - return ffOptions; - } + ArgumentsBrowserOptions(String type) { + this.value = type; + } - public static EdgeOptions getEdgeOptions() { - EdgeOptions edgeOptions = new EdgeOptions(); - edgeOptions.setHeadless(true); - edgeOptions.addArguments("--window-size=1920,1080"); - edgeOptions.addArguments("inprivate"); - return edgeOptions; + String getValue() { + return value; + } } - - } diff --git a/src/main/java/com/engine/driver/DriverOptions.java b/src/main/java/com/engine/driver/DriverOptions.java new file mode 100644 index 0000000..ebe8177 --- /dev/null +++ b/src/main/java/com/engine/driver/DriverOptions.java @@ -0,0 +1,73 @@ +package com.engine.driver; + +import com.engine.actions.FileActions; +import com.engine.constants.FrameworkConstants; +import org.openqa.selenium.PageLoadStrategy; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.edge.EdgeOptions; +import org.openqa.selenium.firefox.FirefoxOptions; +import org.openqa.selenium.firefox.FirefoxProfile; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +public class DriverOptions { + protected static Boolean isHeadless = FrameworkConstants.HEADLESS_OPTION; + protected static Boolean isStartMaximize = FrameworkConstants.MAXIMIZE_OPTION; + + public static ChromeOptions getChromeOptions() { + ChromeOptions chOptions = new ChromeOptions(); + if (Boolean.TRUE.equals(isHeadless)) + chOptions.addArguments(DriverHelper.ArgumentsBrowserOptions.HEADLESS.getValue()); + if (Boolean.TRUE.equals(isStartMaximize)) + chOptions.addArguments(DriverHelper.ArgumentsBrowserOptions.START_MAXIMIZED.getValue()); + chOptions.addArguments(DriverHelper.ArgumentsBrowserOptions.DISABLE_NOTIFICATIONS.getValue()); + chOptions.setPageLoadStrategy(PageLoadStrategy.NORMAL); + chOptions.setPageLoadTimeout(Duration.ofSeconds(FrameworkConstants.PAGE_LOAD_TIMEOUT)); + chOptions.setScriptTimeout(Duration.ofSeconds(FrameworkConstants.SCRIPT_EXECUTION_TIMEOUT)); + Map prefs = getStringObjectMap(FileActions.getDir() + FrameworkConstants.BROWSER_DOWNLOAD_DIR); + chOptions.setExperimentalOption("prefs", prefs); + return chOptions; + } + + + public static EdgeOptions getEdgeOptions() { + EdgeOptions edgeOptions = new EdgeOptions(); + if (Boolean.TRUE.equals(isHeadless)) + edgeOptions.addArguments(DriverHelper.ArgumentsBrowserOptions.HEADLESS.getValue()); + if (Boolean.TRUE.equals(isStartMaximize)) + edgeOptions.addArguments(DriverHelper.ArgumentsBrowserOptions.START_MAXIMIZED.getValue()); + edgeOptions.addArguments("inprivate"); + edgeOptions.addArguments(DriverHelper.ArgumentsBrowserOptions.DISABLE_NOTIFICATIONS.getValue()); + edgeOptions.setPageLoadStrategy(PageLoadStrategy.NORMAL); + edgeOptions.setPageLoadTimeout(Duration.ofSeconds(FrameworkConstants.PAGE_LOAD_TIMEOUT)); + edgeOptions.setScriptTimeout(Duration.ofSeconds(FrameworkConstants.SCRIPT_EXECUTION_TIMEOUT)); + Map prefs = getStringObjectMap(FileActions.getDir() + FrameworkConstants.BROWSER_DOWNLOAD_DIR); + edgeOptions.setExperimentalOption("prefs", prefs); + return edgeOptions; + } + + public static FirefoxOptions getFirefoxOptions() { + FirefoxOptions ffOptions = new FirefoxOptions(); + var ffProfile = new FirefoxProfile(); + if (Boolean.TRUE.equals(isHeadless)) + ffOptions.addArguments(DriverHelper.ArgumentsBrowserOptions.HEADLESS.getValue()); + if (Boolean.TRUE.equals(isStartMaximize)) + ffOptions.addArguments(DriverHelper.ArgumentsBrowserOptions.START_MAXIMIZED.getValue()); + ffOptions.addArguments(DriverHelper.ArgumentsBrowserOptions.DISABLE_NOTIFICATIONS.getValue()); + ffOptions.setPageLoadStrategy(PageLoadStrategy.NORMAL); + ffOptions.setPageLoadTimeout(Duration.ofSeconds(FrameworkConstants.PAGE_LOAD_TIMEOUT)); + ffOptions.setScriptTimeout(Duration.ofSeconds(FrameworkConstants.SCRIPT_EXECUTION_TIMEOUT)); + ffProfile.setPreference("browser.download.dir", FileActions.getDir() + FrameworkConstants.BROWSER_DOWNLOAD_DIR); + ffOptions.setProfile(ffProfile); + return ffOptions; + } + + private static Map getStringObjectMap(String downloadFilepath) { + Map prefs = new HashMap<>(); + prefs.put("download.default_directory", downloadFilepath); + prefs.put("download.prompt_for_download", false); + return prefs; + } +} diff --git a/src/main/java/com/engine/evidence/AnimatedGifManager.java b/src/main/java/com/engine/evidence/AnimatedGif.java similarity index 96% rename from src/main/java/com/engine/evidence/AnimatedGifManager.java rename to src/main/java/com/engine/evidence/AnimatedGif.java index 84a616e..8c322cb 100644 --- a/src/main/java/com/engine/evidence/AnimatedGifManager.java +++ b/src/main/java/com/engine/evidence/AnimatedGif.java @@ -8,7 +8,7 @@ import java.io.IOException; import java.util.Iterator; -public class AnimatedGifManager { +public class AnimatedGif { private static final ThreadLocal gifWriter = new ThreadLocal<>(); private static final ThreadLocal imageWriteParam = new ThreadLocal<>(); private static final ThreadLocal imageMetaData = new ThreadLocal<>(); @@ -22,7 +22,7 @@ public class AnimatedGifManager { * * @throws IOException if no gif ImageWriters are found */ - protected AnimatedGifManager (ImageOutputStream outputStream, int imageType, int timeBetweenFramesMS) throws IOException { + protected AnimatedGif(ImageOutputStream outputStream, int imageType, int timeBetweenFramesMS) throws IOException { initialize(outputStream, imageType, timeBetweenFramesMS); } diff --git a/src/main/java/com/engine/evidence/RecordManager.java b/src/main/java/com/engine/evidence/RecordManager.java deleted file mode 100644 index 7163098..0000000 --- a/src/main/java/com/engine/evidence/RecordManager.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.engine.evidence; - -import com.automation.remarks.video.RecorderFactory; -import com.automation.remarks.video.recorder.IVideoRecorder; -import com.automation.remarks.video.recorder.VideoRecorder; - -import com.engine.Helper; -import com.engine.reports.CustomReporter; -import com.engine.reports.Attachments; -import io.appium.java_client.android.AndroidDriver; -import io.appium.java_client.ios.IOSDriver; - -import org.apache.commons.io.FileUtils; -import org.openqa.selenium.WebDriver; -import ws.schild.jave.Encoder; -import ws.schild.jave.EncoderException; -import ws.schild.jave.MultimediaObject; -import ws.schild.jave.encode.AudioAttributes; -import ws.schild.jave.encode.EncodingAttributes; -import ws.schild.jave.encode.VideoAttributes; - -import java.io.*; -import java.nio.file.Path; -import java.util.Base64; - -import static com.automation.remarks.video.RecordingUtils.doVideoProcessing; - -public class RecordManager { - private static final Boolean RECORD_VIDEO = true; - private static final ThreadLocal recorder = new ThreadLocal<>(); - private static final ThreadLocal videoDriver = new ThreadLocal<>(); - private static boolean isRecordingStarted = false; - - private RecordManager () { - throw new IllegalStateException("Utility class"); - } - - //TODO: the animated GIF should follow the same path as the video - public static void startVideoRecording (WebDriver driver) { - startVideoRecording(); - } - - public static void startVideoRecording () { - recorder.set(RecorderFactory.getRecorder(VideoRecorder.conf().recorderType())); - recorder.get().start(); - - } - - public static void attachVideoRecording (Path pathToRecording) { - if ( pathToRecording != null ) { - String testMethodName = Helper.getTestMethodName(); - try { - Attachments.attach("Video Recording", testMethodName, - new FileInputStream(pathToRecording.toString())); - } catch ( FileNotFoundException e ) { - CustomReporter.logError(e.getMessage()); - } - } - } - - public static void attachVideoRecording () { - Attachments.attach("Video Recording", Helper.getTestMethodName(), getVideoRecording()); - } - - public static String getVideoRecordingFilePath () { - try { - String tempFilePath = "target/tempVideoFile/"; - FileUtils.copyInputStreamToFile(getVideoRecording(), new File(tempFilePath)); - return tempFilePath; - } catch ( IOException e ) { - CustomReporter.logError(e.getMessage()); - return ""; - } - } - - public static InputStream getVideoRecording () { - InputStream inputStream = null; - String pathToRecording = ""; - String testMethodName = Helper.getTestMethodName(); - - if ( Boolean.TRUE.equals(RECORD_VIDEO) && recorder.get() != null ) { - pathToRecording = doVideoProcessing(Helper.isCurrentTestPassed(), recorder.get().stopAndSave(System.currentTimeMillis() + "_" + testMethodName)); - try { - inputStream = new FileInputStream(encodeRecording(pathToRecording)); - } catch ( FileNotFoundException e ) { - CustomReporter.logError(e.getMessage()); -// inputStream = new ByteArrayInputStream(new byte[0]); - } - recorder.set(null); - - } else if ( Boolean.TRUE.equals(RECORD_VIDEO) && videoDriver.get() != null ) { - String base64EncodedRecording = ""; - if ( videoDriver.get() instanceof AndroidDriver androidDriver ) { - base64EncodedRecording = androidDriver.stopRecordingScreen(); - } else if ( videoDriver.get() instanceof IOSDriver iosDriver ) { - base64EncodedRecording = iosDriver.stopRecordingScreen(); - } - inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(base64EncodedRecording)); - videoDriver.set(null); - isRecordingStarted = false; - } - return inputStream; - } - - private static File encodeRecording (String pathToRecording) { - File source = new File(pathToRecording); - File target = new File(pathToRecording.replace("avi", "mp4")); - try { - - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("libvorbis"); - VideoAttributes video = new VideoAttributes(); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setOutputFormat("mp4"); - attrs.setAudioAttributes(audio); - attrs.setVideoAttributes(video); - Encoder encoder = new Encoder(); - encoder.encode(new MultimediaObject(source), target, attrs); - } catch (EncoderException e) { - CustomReporter.logError(e.getMessage()); - } - return target; - } -} diff --git a/src/main/java/com/engine/evidence/RecordVideo.java b/src/main/java/com/engine/evidence/RecordVideo.java index 6857bbd..e7285cb 100644 --- a/src/main/java/com/engine/evidence/RecordVideo.java +++ b/src/main/java/com/engine/evidence/RecordVideo.java @@ -1,105 +1,111 @@ package com.engine.evidence; -import org.monte.media.Format; -import org.monte.media.Registry; -import org.monte.media.math.Rational; -import org.monte.screenrecorder.ScreenRecorder; - -import java.awt.*; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; +import com.automation.remarks.video.RecorderFactory; +import com.automation.remarks.video.recorder.IVideoRecorder; +import com.automation.remarks.video.recorder.VideoRecorder; + +import com.engine.Helper; +import com.engine.constants.FrameworkConstants; +import com.engine.dataDriven.PropertiesManager; +import com.engine.reports.CustomReporter; +import com.engine.reports.Attachments; + +import io.qameta.allure.Step; +import org.apache.commons.io.FileUtils; +import ws.schild.jave.Encoder; +import ws.schild.jave.EncoderException; +import ws.schild.jave.MultimediaObject; +import ws.schild.jave.encode.AudioAttributes; +import ws.schild.jave.encode.EncodingAttributes; +import ws.schild.jave.encode.VideoAttributes; + +import java.io.*; import java.nio.file.Path; -import java.nio.file.Paths; -import java.text.SimpleDateFormat; -import java.util.Date; - -import static org.monte.media.FormatKeys.*; -import static org.monte.media.VideoFormatKeys.*; -public class RecordVideo extends ScreenRecorder { +import static com.automation.remarks.video.RecordingUtils.doVideoProcessing; +import static com.engine.constants.FrameworkConstants.RECORD_VIDEO; - private String fileName; - private File currentFile; - private SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH-mm-ss"); - private String videoFilePath = "video"; +public class RecordVideo { - public RecordVideo() throws IOException, AWTException { - super(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(), - new Rectangle(0, 0, Toolkit.getDefaultToolkit().getScreenSize().width, Toolkit.getDefaultToolkit().getScreenSize().height), - new Format(MediaTypeKey, MediaType.FILE, MimeTypeKey, MIME_AVI), - new Format(MediaTypeKey, MediaType.VIDEO, EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, CompressorNameKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 24, FrameRateKey, - Rational.valueOf(15), QualityKey, 1.0f, KeyFrameIntervalKey, 15 * 60), - new Format(MediaTypeKey, MediaType.VIDEO, EncodingKey, "black", FrameRateKey, Rational.valueOf(30)), - null, - new File("./video/")); - } + private static final ThreadLocal recorder = new ThreadLocal<>(); - @Override - protected File createMovieFile(Format fileFormat) throws IOException { - if (!movieFolder.exists()) { - movieFolder.mkdirs(); - } else if (!movieFolder.isDirectory()) { - throw new IOException("\"" + movieFolder + "\" is not a directory."); + public static void startVideoRecording() { + if (Boolean.TRUE.equals(RECORD_VIDEO) && recorder.get() == null && !Boolean.TRUE.equals(FrameworkConstants.HEADLESS_OPTION)) { + CustomReporter.logInfoStep("Started recording device screen"); + recorder.set(RecorderFactory.getRecorder(VideoRecorder.conf().recorderType())); + recorder.get().start(); + } else { + CustomReporter.logConsole("Video recording is disabled"); } - - currentFile = getFileWithUniqueName(movieFolder.getAbsolutePath() + File.separator + fileName + "_" + dateFormat.format(new Date()) + "." + Registry.getInstance().getExtension(fileFormat)); - return currentFile; } - private File getFileWithUniqueName(String fileName) { - String extension = ""; - String name = ""; - - int idxOfDot = fileName.lastIndexOf('.'); // Get the last index of . to separate extension - extension = fileName.substring(idxOfDot + 1); - name = fileName.substring(0, idxOfDot); - - Path path = Paths.get(fileName); - int counter = 1; - while (Files.exists(path)) { - fileName = name + "-" + counter + "." + extension; - path = Paths.get(fileName); - counter++; + public static InputStream getVideoRecording() { + InputStream inputStream = null; + String pathToRecording = ""; + String testMethodName = Helper.getTestMethodName(); + if (Boolean.TRUE.equals(RECORD_VIDEO) && recorder.get() != null) { + pathToRecording = doVideoProcessing(Helper.isCurrentTestPassed(), recorder.get().stopAndSave(System.currentTimeMillis() + "_" + testMethodName)); + try { + inputStream = new FileInputStream(encodeRecording(pathToRecording)); + } catch (FileNotFoundException e) { + CustomReporter.logError(e.getMessage()); + } + recorder.remove(); } - return new File(fileName); + return inputStream; } - public void startRecording(String fileName) { - this.fileName = fileName; + public static String getVideoRecordingFilePath() { try { - start(); + String tempFilePath = "target/tempVideoFile/"; + FileUtils.copyInputStreamToFile(getVideoRecording(), new File(tempFilePath)); + return tempFilePath; } catch (IOException e) { - throw new RuntimeException(e); + CustomReporter.logError(e.getMessage()); + return ""; } } - public void stopRecording(boolean keepFile) { - try { - stop(); - } catch (IOException e) { - throw new RuntimeException(e); - } - if (!keepFile) { - deleteRecording(); + public static void attachVideoRecording() { + if (RECORD_VIDEO) { + Attachments.attach("Video Recording", Helper.getTestMethodName(), getVideoRecording()); + } else { + CustomReporter.logConsole("There is no video recording to attach"); } } - private void deleteRecording() { - boolean deleted = false; - try { - if (currentFile.exists()) { - deleted = currentFile.delete(); + public static void attachVideoRecording(Path pathToRecording) { + if (pathToRecording != null) { + String testMethodName = Helper.getTestMethodName(); + try { + Attachments.attach("Video Recording", testMethodName, new FileInputStream(pathToRecording.toString())); + } catch (FileNotFoundException e) { + CustomReporter.logError(e.getMessage()); } - } catch (Exception e) { - e.printStackTrace(); } + } - if (deleted) - currentFile = null; - else - System.out.println("Could not delete the screen record!"); + private static File encodeRecording(String pathToRecording) { + File source = new File(pathToRecording); + File target = new File(pathToRecording.replace("avi", "mp4")); + try { + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("libvorbis"); + VideoAttributes video = new VideoAttributes(); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("mp4"); + attrs.setAudioAttributes(audio); + attrs.setVideoAttributes(video); + Encoder encoder = new Encoder(); + encoder.encode(new MultimediaObject(source), target, attrs); + } catch (EncoderException e) { + CustomReporter.logError(e.getMessage()); + } + return target; } + private RecordVideo() { + throw new IllegalStateException("Utility class"); + } } diff --git a/src/main/java/com/engine/evidence/ScreenShot.java b/src/main/java/com/engine/evidence/ScreenShot.java index 0f71ce7..8420d6d 100644 --- a/src/main/java/com/engine/evidence/ScreenShot.java +++ b/src/main/java/com/engine/evidence/ScreenShot.java @@ -1,6 +1,7 @@ package com.engine.evidence; import com.engine.Helper; +import com.engine.dataDriven.PropertiesManager; import com.engine.reports.CustomReporter; import org.apache.commons.io.FileUtils; import org.openqa.selenium.*; @@ -19,8 +20,9 @@ public class ScreenShot { private static String gifRelativePathWithFileName = ""; //private static final Boolean CREATE_GIF = Boolean.valueOf(System.getProperty("createAnimatedGif").trim()); //private static final int GIF_FRAME_DELAY = Integer.parseInt(System.getProperty("animatedGif_frameDelay").trim()); + public static final String screenShotPath = PropertiesManager.getPropertyValue("paths.properties", "screenshotPath"); private static ThreadLocal gifOutputStream = new ThreadLocal<>(); - private static ThreadLocal gifWriter = new ThreadLocal<>(); + private static ThreadLocal gifWriter = new ThreadLocal<>(); /** * Take the screen shot and save it in the ScreenShot folder @@ -31,7 +33,7 @@ public static void takeScreenShotToFile (WebDriver driver) { try { CustomReporter.logInfoStep("Screenshot taken for Test Case ..... [" + Helper.getTestMethodName() + "]"); File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); - FileUtils.copyFile(srcFile, new File("./ScreenShot/" + Helper.getTestMethodName() + ".png")); + FileUtils.copyFile(srcFile, new File("./" + screenShotPath + Helper.getTestMethodName() + ".png")); } catch ( Exception e ) { e.printStackTrace(); CustomReporter.logError("Screenshot not taken: " + e.getMessage()); @@ -70,7 +72,7 @@ public static void takeElementScreenShot (WebDriver driver, By locator) { try { CustomReporter.logInfoStep("Screenshot taken for Element ..... [" + locatorName + "]"); File srcFile = driver.findElement(locator).getScreenshotAs(OutputType.FILE); - FileUtils.copyFile(srcFile, new File("./ScreenShot/" + locatorName + ".png")); + FileUtils.copyFile(srcFile, new File("./" + screenShotPath + locatorName + ".png")); } catch ( Exception e ) { e.printStackTrace(); CustomReporter.logError("Element Screenshot not taken: " + e.getMessage()); @@ -87,33 +89,29 @@ public static void takeElementScreenShot (WebDriver driver, By locator) { public static void takeFullPageScreenshot (WebDriver driver, String imageName) { try { File source = ((FirefoxDriver) driver).getFullPageScreenshotAs(OutputType.FILE); - FileUtils.copyFile(source, new File("./ScreenShot/" + imageName + "_FullPage.png")); + FileUtils.copyFile(source, new File("./" + screenShotPath + imageName + "_FullPage.png")); } catch ( Exception e ) { e.printStackTrace(); CustomReporter.logError("Full Page Screenshot not taken: " + e.getMessage()); } } - public static void takeFullScreenShoot(WebDriver driver, String filePath) { - + public static void takeFullScreenShoot(WebDriver driver) { //take screenshot of the entire page Screenshot screenshot = new AShot().shootingStrategy(ShootingStrategies.viewportPasting(1000)).takeScreenshot(driver); try { - ImageIO.write(screenshot.getImage(), "PNG", new File(filePath + Helper.getTestMethodName() + ".png")); + ImageIO.write(screenshot.getImage(), "PNG", new File("./" + screenShotPath + Helper.getTestMethodName() + ".png")); } catch (IOException e) { - // TODO Auto-generated catch block e.printStackTrace(); } } - public static BufferedImage takeFullScreenShoot2(WebDriver driver, String filePath) { - + public static BufferedImage takeFullScreenShoot2(WebDriver driver) { //take screenshot of the entire page Screenshot screenshot = new AShot().shootingStrategy(ShootingStrategies.viewportPasting(1000)).takeScreenshot(driver); try { - ImageIO.write(screenshot.getImage(), "PNG", new File(filePath + Helper.getTestMethodName() + ".png")); + ImageIO.write(screenshot.getImage(), "PNG", new File("./" + screenShotPath + Helper.getTestMethodName() + ".png")); } catch (IOException e) { - // TODO Auto-generated catch block e.printStackTrace(); } return screenshot.getImage(); diff --git a/src/main/java/com/engine/evidence/ScreenshotManager.java b/src/main/java/com/engine/evidence/ScreenshotManager.java index 552aaa2..5820b38 100644 --- a/src/main/java/com/engine/evidence/ScreenshotManager.java +++ b/src/main/java/com/engine/evidence/ScreenshotManager.java @@ -24,14 +24,12 @@ public class ScreenshotManager { private static final int GIF_SIZE = 1280; // TODO: parameterize the detailed gif value - private static final Boolean DETAILED_GIF = true; - private static final String DETAILED_GIF_REGEX = "(verify.*)|(assert.*)|(click.*)|(tap.*)|(key.*)|(navigate.*)"; private static String gifRelativePathWithFileName = ""; private static String testCaseName = ""; @Getter(AccessLevel.PUBLIC) private static final org.openqa.selenium.Dimension TARGET_WINDOW_SIZE = new Dimension(1920, 1080); private static ThreadLocal gifOutputStream = new ThreadLocal<>(); - private static ThreadLocal gifWriter = new ThreadLocal<>(); + private static ThreadLocal gifWriter = new ThreadLocal<>(); private static String gifOptions = System.getProperty("config.properties", "createAnimatedGif"); private static String gifDelay = System.getProperty("config.properties", "animatedGif_frameDelay"); @@ -67,8 +65,7 @@ private static void startAnimatedGif(byte[] screenshot) { if (Boolean.TRUE.equals(gifOptions) && screenshot != null) { try { testCaseName = Helper.getTestMethodName(); - String gifFileName = FileSystems.getDefault().getSeparator() + System.currentTimeMillis() + "_" - + testCaseName + ".gif"; + String gifFileName = FileSystems.getDefault().getSeparator() + System.currentTimeMillis() + "_" + testCaseName + ".gif"; gifRelativePathWithFileName = "allure-result/screenshots/" + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()) + gifFileName; // get the width and height of the current window of the browser @@ -87,7 +84,7 @@ private static void startAnimatedGif(byte[] screenshot) { // create a gif sequence with the type of the first image, 500 milliseconds // between frames, which loops infinitely - gifWriter.set(new AnimatedGifManager(gifOutputStream.get(), firstImage.getType(), 500)); + gifWriter.set(new AnimatedGif(gifOutputStream.get(), firstImage.getType(), 500)); // draw initial blank image to set the size of the GIF... BufferedImage initialImage = new BufferedImage(width, height, firstImage.getType()); @@ -123,6 +120,7 @@ private static void startOrAppendToAnimatedGif(byte[] screenshot) { } } + private static void appendToAnimatedGif(byte[] screenshot) { try { BufferedImage image; diff --git a/src/main/java/com/engine/listeners/AllureListener.java b/src/main/java/com/engine/listeners/AllureListener.java new file mode 100644 index 0000000..00c9563 --- /dev/null +++ b/src/main/java/com/engine/listeners/AllureListener.java @@ -0,0 +1,195 @@ +package com.engine.listeners; + +import io.qameta.allure.listener.ContainerLifecycleListener; +import io.qameta.allure.listener.FixtureLifecycleListener; +import io.qameta.allure.listener.StepLifecycleListener; +import io.qameta.allure.listener.TestLifecycleListener; +import io.qameta.allure.model.FixtureResult; +import io.qameta.allure.model.StepResult; +import io.qameta.allure.model.TestResult; +import io.qameta.allure.model.TestResultContainer; + +public class AllureListener implements StepLifecycleListener, FixtureLifecycleListener, TestLifecycleListener, + ContainerLifecycleListener { + + //Before each step starts inside the methods + @Override + public void beforeStepStart(StepResult result) { + StepLifecycleListener.super.beforeStepStart(result); + } + + //After each step starts inside the methods + @Override + public void afterStepStart(StepResult result) { + StepLifecycleListener.super.afterStepStart(result); + } + + //Before each step update inside the methods + @Override + public void beforeStepUpdate(StepResult result) { + StepLifecycleListener.super.beforeStepUpdate(result); + } + + //After each step update inside the methods + @Override + public void afterStepUpdate(StepResult result) { + StepLifecycleListener.super.afterStepUpdate(result); + } + + //Before each step Stop inside the methods + @Override + public void beforeStepStop(StepResult result) { + StepLifecycleListener.super.beforeStepStop(result); + } + + //After each step Stop inside the methods +// @Override +// public void afterStepStop(StepResult result) { +// var iTestResult = TestNGListener.getITestResult(); +// TestNGListenerHelper.updateConfigurationMethods(iTestResult); +// } + + //Before The Class starts + @Override + public void beforeContainerStart(TestResultContainer container) { + ContainerLifecycleListener.super.beforeContainerStart(container); + } + + //After The Class starts + @Override + public void afterContainerStart(TestResultContainer container) { + ContainerLifecycleListener.super.afterContainerStart(container); + } + + //Before The Class updates + @Override + public void beforeContainerUpdate(TestResultContainer container) { + ContainerLifecycleListener.super.beforeContainerUpdate(container); + } + + //After The Class updates + @Override + public void afterContainerUpdate(TestResultContainer container) { + ContainerLifecycleListener.super.afterContainerUpdate(container); + } + + //Before The Class stops + @Override + public void beforeContainerStop(TestResultContainer container) { + ContainerLifecycleListener.super.beforeContainerStop(container); + } + + //After The Class stops + @Override + public void afterContainerStop(TestResultContainer container) { + ContainerLifecycleListener.super.afterContainerStop(container); + } + + //Before The Class writes + @Override + public void beforeContainerWrite(TestResultContainer container) { + ContainerLifecycleListener.super.beforeContainerWrite(container); + } + + //After The Class writes + @Override + public void afterContainerWrite(TestResultContainer container) { + ContainerLifecycleListener.super.afterContainerWrite(container); + } + + //Before The Configuration 'SetUp' "and probably 'TearDown' too" starts + @Override + public void beforeFixtureStart(FixtureResult result) { + FixtureLifecycleListener.super.beforeFixtureStart(result); + } + + //After The Configuration 'SetUp' "and probably 'TearDown' too" starts + @Override + public void afterFixtureStart(FixtureResult result) { + FixtureLifecycleListener.super.afterFixtureStart(result); + } + + //Before The Configuration 'SetUp' "and probably 'TearDown' too" updates + @Override + public void beforeFixtureUpdate(FixtureResult result) { + FixtureLifecycleListener.super.beforeFixtureUpdate(result); + } + + //After The Configuration 'SetUp' "and probably 'TearDown' too" updates + @Override + public void afterFixtureUpdate(FixtureResult result) { + FixtureLifecycleListener.super.afterFixtureUpdate(result); + } + + //Before The Configuration 'SetUp' "and probably 'TearDown' too" stops +// @Override +// public void beforeFixtureStop(FixtureResult result) { +// TestNGListenerHelper.attachConfigurationMethods(); +// } + + //After The Configuration 'SetUp' "and probably 'TearDown' too" stops + @Override + public void afterFixtureStop(FixtureResult result) { + FixtureLifecycleListener.super.afterFixtureStop(result); + } + + //Before The Configuration 'SetUp' starts + @Override + public void beforeTestSchedule(TestResult result) { + TestLifecycleListener.super.beforeTestSchedule(result); + } + + //After The Configuration 'SetUp' starts + @Override + public void afterTestSchedule(TestResult result) { + TestLifecycleListener.super.afterTestSchedule(result); + } + + //Before The @test updates + @Override + public void beforeTestUpdate(TestResult result) { + TestLifecycleListener.super.beforeTestUpdate(result); + } + + //After The @test updates + @Override + public void afterTestUpdate(TestResult result) { + TestLifecycleListener.super.afterTestUpdate(result); + } + + //Before The @test starts + @Override + public void beforeTestStart(TestResult result) { + TestLifecycleListener.super.beforeTestStart(result); + } + + //After The @test starts + @Override + public void afterTestStart(TestResult result) { + TestLifecycleListener.super.afterTestStart(result); + } + + //Before The @test stops + @Override + public void beforeTestStop(TestResult result) { + TestLifecycleListener.super.beforeTestStop(result); + } + + //After The @test stops + @Override + public void afterTestStop(TestResult result) { + TestLifecycleListener.super.afterTestStop(result); + } + + //Before The @test writes + @Override + public void beforeTestWrite(TestResult result) { + TestLifecycleListener.super.beforeTestWrite(result); + } + + //After The @test writes + @Override + public void afterTestWrite(TestResult result) { + TestLifecycleListener.super.afterTestWrite(result); + } +} diff --git a/src/main/java/com/engine/listeners/AnnotationTransformer.java b/src/main/java/com/engine/listeners/AnnotationTransformer.java deleted file mode 100644 index 970aedd..0000000 --- a/src/main/java/com/engine/listeners/AnnotationTransformer.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.engine.listeners; - -import org.testng.IAnnotationTransformer; -import org.testng.annotations.ITestAnnotation; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - -public class AnnotationTransformer implements IAnnotationTransformer { - @Override - public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) { - annotation.setRetryAnalyzer(Retry.class); - } -} \ No newline at end of file diff --git a/src/main/java/com/engine/listeners/TestNGListener.java b/src/main/java/com/engine/listeners/TestNGListener.java new file mode 100644 index 0000000..f06f3f9 --- /dev/null +++ b/src/main/java/com/engine/listeners/TestNGListener.java @@ -0,0 +1,150 @@ +package com.engine.listeners; + +import com.aventstack.extentreports.markuputils.ExtentColor; +import com.aventstack.extentreports.markuputils.MarkupHelper; +import com.engine.reports.AllureReport; +import com.engine.reports.CustomReporter; +import com.engine.reports.ExtentReport; +import io.qameta.allure.Allure; +import io.qameta.allure.listener.TestLifecycleListener; +import org.openqa.selenium.WebDriver; +import org.testng.*; +import org.testng.annotations.ITestAnnotation; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class TestNGListener implements IAlterSuiteListener, IAnnotationTransformer, + IExecutionListener, ISuiteListener, IInvokedMethodListener, ITestListener { + static int count_totalTCs; + static int count_passedTCs; + static int count_skippedTCs; + static int count_failedTCs; + + + String runBy = " Ismail ElShaFeiy 😉"; + + //////////////////////////////////////////////////// + ///////////////// ISuiteListener ////////////////// + ////////////////////////////////////////////////// + @Override + public void onExecutionStart() { + CustomReporter.createImportantReportEntry("Start execution by " + runBy); + Allure.getLifecycle(); + ExtentReport.initializeExtentReport(); + AllureReport.cleanAllureResultsDirectory(); + } + + @Override + public void onExecutionFinish() { + CustomReporter.createImportantReportEntry("Finished execution by " + runBy); + ExtentReport.flushReports(); + } + + @Override + public void onStart(ISuite suite) { + //FileActions.getInstance().createFolder(Properties.paths.services()); + //FileActions.getInstance().writeToFile(Properties.paths.services(), "org.testng.ITestNGListener", "com.engine.listeners.TestngListener"); + + } + + @Override + public void onFinish(ISuite suite) { + ExtentReport.flushReports(); + //AllureReport.writeAllureReport(); + //openAllureReportAfterExecution(); + //EmailSendUtils.sendEmail(count_totalTCs, count_passedTCs, count_failedTCs, count_skippedTCs); + } + + @Override + public void onStart(ITestContext context) { + CustomReporter.createImportantReportEntry(" Test: [ " + context.getName() + " ] Started "); + } + + @Override + public void onFinish(ITestContext context) { + CustomReporter.createImportantReportEntry(" Test: [ " + context.getName() + " ] Finished "); + } + + + //////////////////////////////////////////////////////////// + ///////////////// IInvokedMethodListener ////////////////// + ////////////////////////////////////////////////////////// + @Override + public void beforeInvocation(IInvokedMethod method, ITestResult testResult) { + ITestNGMethod testMethod = method.getTestMethod(); + if (testMethod.getDescription() != null && !testMethod.getDescription().equals("")) { + ExtentReport.createTest(testMethod.getDescription()); + } else { + ExtentReport.createTest(testResult.getName()); + } + if (method.isConfigurationMethod()) { + CustomReporter.createImportantReportEntry("Starting Configuration Method (Setup or TearDown): [" + testResult.getName() + "]"); + if (testMethod.getDescription() != null && !testMethod.getDescription().equals("")) { + ExtentReport.removeTest(testMethod.getDescription()); + } else { + ExtentReport.removeTest(testResult.getName()); + } + } else { + CustomReporter.createImportantReportEntry("Starting Test Case: [ " + testResult.getName() + " ]"); + } + } + + @Override + public void afterInvocation(IInvokedMethod method, ITestResult testResult) { + if (method.isConfigurationMethod()) { + CustomReporter.createImportantReportEntry("Finished Configuration Method (Setup or TearDown): [" + testResult.getName() + "]"); + } else { + CustomReporter.createImportantReportEntry("Finished Test Case: [ " + testResult.getName() + " ]"); + } + } + + /////////////////////////////////////////////////// + ///////////////// ITestListener ////////////////// + ///////////////////////////////////////////////// + + @Override + public void onTestStart(ITestResult result) { + count_totalTCs = count_totalTCs + 1; + } + + @Override + public void onTestSuccess(ITestResult result) { + count_passedTCs = count_passedTCs + 1; + ExtentReport.pass(MarkupHelper.createLabel(result.getMethod().getMethodName() + " Passed!", ExtentColor.GREEN)); + } + + @Override + public void onTestFailure(ITestResult result) { + count_failedTCs = count_failedTCs + 1; + ITestContext context = result.getTestContext(); + WebDriver driver = (WebDriver) context.getAttribute("driver"); + if (driver != null) { + try { + AllureReport.attachScreenshotToAllureReport(driver); + ExtentReport.fail(ExtentReport.attachScreenshotToExtentReport(driver)); + } catch (Throwable e) { + CustomReporter.logError("Error: " + e.getMessage()); + } + } + ExtentReport.fail(MarkupHelper.createLabel(result.getMethod().getMethodName() + " Failed!", ExtentColor.RED)); + if (result.getThrowable() != null) { + ExtentReport.fail(result.getThrowable()); + } + } + + @Override + public void onTestSkipped(ITestResult result) { + count_skippedTCs = count_skippedTCs + 1; + ExtentReport.skip(MarkupHelper.createLabel(result.getMethod().getMethodName() + " Skipped!", ExtentColor.YELLOW)); + if (result.getThrowable() != null) { + ExtentReport.skip(result.getThrowable()); + } + } + + @Override + public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) { + annotation.setRetryAnalyzer(Retry.class); + } + +} diff --git a/src/main/java/com/engine/listeners/TestngListener.java b/src/main/java/com/engine/listeners/TestngListener.java deleted file mode 100644 index f21bb43..0000000 --- a/src/main/java/com/engine/listeners/TestngListener.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.engine.listeners; - -import com.aventstack.extentreports.markuputils.ExtentColor; -import com.aventstack.extentreports.markuputils.MarkupHelper; -import com.engine.reports.Attachments; -import com.engine.evidence.RecordVideo; -import com.engine.mail.EmailSendUtils; -import com.engine.reports.CustomReporter; -import com.engine.reports.ExtentReport; -import com.engine.evidence.RecordManager; -import org.openqa.selenium.WebDriver; -import org.testng.*; - -import java.awt.*; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static com.engine.reports.AllureReport.openAllureReportAfterExecution; - -public class TestngListener implements ISuiteListener, ITestListener, IInvokedMethodListener { - private RecordVideo screenRecorder; - static int count_totalTCs; - static int count_passedTCs; - static int count_skippedTCs; - static int count_failedTCs; - - public TestngListener() { - try { - screenRecorder = new RecordVideo(); - } catch (IOException | AWTException e) { - CustomReporter.logError(e.getMessage()); - e.printStackTrace(); - } - } - - - String runBy = " Ismail ElShaFeiy ;)"; - String boundaryStartEnd = "======================================================"; - String boundaryBeforeAfter = "################################################################################################################################################"; - //////////////////////////////////////////////////// - ///////////////// ISuiteListener ////////////////// - ////////////////////////////////////////////////// - - @Override - public void onStart (ISuite suite) { - //FileActions.getInstance().createFolder(Properties.paths.services()); - //FileActions.getInstance().writeToFile(Properties.paths.services(), "org.testng.ITestNGListener", "com.engine.listeners.TestngListener"); - CustomReporter.createImportantReportEntry("Starting Test Suite;" + runBy); - ExtentReport.initializeExtentReport(); - } - - @Override - public void onFinish (ISuite suite) { - CustomReporter.createImportantReportEntry("Finished Test Suite;" + runBy); - ExtentReport.flushReports(); - EmailSendUtils.sendEmail(count_totalTCs, count_passedTCs, count_failedTCs, count_skippedTCs); - } - - /////////////////////////////////////////////////// - ///////////////// ITestListener ////////////////// - ///////////////////////////////////////////////// - @Override - public void onStart (ITestContext context) { - CustomReporter.createImportantReportEntry(" Test: [ " + context.getName() + " ] Started "); - // CustomReporter.logConsole();("\n" + boundaryStartEnd + " Test: [ " + context.getName() + " ] Started " + boundaryStartEnd + "\n"); - } - - @Override - public void onTestStart (ITestResult result) { - count_totalTCs = count_totalTCs + 1; - screenRecorder.startRecording(result.getMethod().getMethodName()); - // ExtentReport.createTest(result.getName()); - } - - @Override - public void onFinish (ITestContext context) { - CustomReporter.createImportantReportEntry(" Test: [ " + context.getName() + " ] Finished "); - openAllureReportAfterExecution(); - } - - @Override - public void onTestSuccess (ITestResult result) { - count_passedTCs = count_passedTCs + 1; - ExtentReport.pass(MarkupHelper.createLabel(result.getMethod().getMethodName() + " Passed!", ExtentColor.GREEN)); - screenRecorder.stopRecording(true); - } - - @Override - public void onTestFailure (ITestResult result) { - count_failedTCs = count_failedTCs + 1; - ITestContext context = result.getTestContext(); - WebDriver driver = (WebDriver) context.getAttribute("driver"); - if ( driver != null ) { - try { - Attachments.attachScreenshotToAllureReport(driver); - ExtentReport.fail(Attachments.attachScreenshotToExtentReport(driver)); -// ExtentReport.fail(Attachments.attachFullPageScreenShotToExtentReport((FirefoxDriver) driver)); -// Logger.logConsoleLogs(driver, result); - } catch ( Throwable e ) { - CustomReporter.logError("Error: " + e.getMessage()); - } - } - ExtentReport.fail(MarkupHelper.createLabel(result.getMethod().getMethodName() + " Failed!", ExtentColor.RED)); - if ( result.getThrowable() != null ) { - ExtentReport.fail(result.getThrowable()); - } - screenRecorder.stopRecording(true); - } - - @Override - public void onTestSkipped (ITestResult result) { - count_skippedTCs = count_skippedTCs + 1; - ExtentReport.skip(MarkupHelper.createLabel(result.getMethod().getMethodName() + " Skipped!", ExtentColor.YELLOW)); - if ( result.getThrowable() != null ) { - ExtentReport.skip(result.getThrowable()); - } - screenRecorder.stopRecording(true); - } - - //////////////////////////////////////////////////////////// - ///////////////// IInvokedMethodListener ////////////////// - ////////////////////////////////////////////////////////// - @Override - public void beforeInvocation (IInvokedMethod method, ITestResult testResult) { - ITestNGMethod testMethod = method.getTestMethod(); - if ( testMethod.getDescription() != null && ! testMethod.getDescription().equals("") ) { - ExtentReport.createTest(testMethod.getDescription()); - } else { - ExtentReport.createTest(testResult.getName()); - } - if ( method.isConfigurationMethod() ) { - CustomReporter.createImportantReportEntry("Starting Configuration Method (Setup or TearDown): [" + testResult.getName() + "]"); - if ( testMethod.getDescription() != null && ! testMethod.getDescription().equals("") ) { - ExtentReport.removeTest(testMethod.getDescription()); - } else { - ExtentReport.removeTest(testResult.getName()); - } - } else { - CustomReporter.logConsole("Starting Test Case: [" + testResult.getName() + "]"); - } - } - - @Override - public void afterInvocation (IInvokedMethod method, ITestResult testResult) { - if ( method.isConfigurationMethod() ) { - CustomReporter.createImportantReportEntry("Finished Configuration Method (Setup or TearDown): [" + testResult.getName() + "]"); - } else { - //attachTestArtifacts(testResult); - CustomReporter.createImportantReportEntry("Finished Test Case: [" + testResult.getName() + "]"); - } - } - - public static void attachTestArtifacts (ITestResult iTestResult) { - ITestNGMethod iTestNGMethod = iTestResult.getMethod(); - - if ( ! Arrays.asList("suiteSetup", "suiteTeardown", "classTeardown").contains(iTestNGMethod.getMethodName()) ) { - List attachments = new ArrayList<>(); - String attachment; - // if ( System.getProperty("videoParams_scope").trim().equals("TestMethod") ) { - RecordManager.attachVideoRecording(); - attachment = RecordManager.getVideoRecordingFilePath(); - if ( ! attachment.equals("") ) - attachments.add(attachment); - // } - // attachment = ScreenshotManager.attachAnimatedGif(); - - - } - } -} diff --git a/src/main/java/com/engine/reports/AllureReport.java b/src/main/java/com/engine/reports/AllureReport.java index 6d768a2..9c77cbf 100644 --- a/src/main/java/com/engine/reports/AllureReport.java +++ b/src/main/java/com/engine/reports/AllureReport.java @@ -1,34 +1,37 @@ package com.engine.reports; +import com.engine.actions.FileActions; import com.engine.actions.TerminalActions; import com.engine.constants.FrameworkConstants; +import com.engine.dataDriven.PropertiesManager; import com.google.common.collect.ImmutableMap; import com.github.automatedowl.tools.AllureEnvironmentWriter; import com.google.common.io.Files; import io.qameta.allure.Allure; +import io.qameta.allure.Attachment; +import io.qameta.allure.Step; +import io.qameta.allure.model.Status; import org.apache.commons.lang3.SystemUtils; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.TakesScreenshot; +import org.openqa.selenium.WebDriver; +import org.testng.ITestResult; +import org.testng.Reporter; +import java.io.ByteArrayInputStream; +import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import static com.engine.reports.CustomReporter.createLog; + public class AllureReport { -// public static void addAttachmentVideoAVI() { -// // if (FrameworkConstants.VIDEO_RECORD.toLowerCase().trim().equals(FrameworkConstants.YES)) { -// try { -// //Get file Last Modified in folder -// File video = FileHelpers.getFileLastModified("ExportData/Videos"); -// if (video != null) { -// Allure.addAttachment("Failed test Video record AVI", "video/avi", Files.asByteSource(video).openStream(), ".avi"); -// } else { -// CustomReporter.logWarn("Video record not found."); -// CustomReporter.logWarn("Can not attachment Video in Allure report"); -// } -// -// } catch (IOException e) { -// CustomReporter.logMessage("Can not attachment Video in Allure report"); -// e.printStackTrace(); -// } -// } -// // } + private static final String allureExtractionLocation = System.getProperty("user.home") + File.separator + ".m2" + File.separator + "repository" + File.separator + "allure" + File.separator; + static String allureBinaryPath = ""; + static String allureResultsFolderPath = "allure-results/"; + public static String videoRecordedPath = PropertiesManager.getPropertyValue("paths.properties", "videoRecordedPath"); private AllureReport() { } @@ -43,25 +46,126 @@ public static void setAllureEnvironmentInformation() { build()); CustomReporter.logConsole("Allure Reports is installed."); } -// public static void addAttachmentVideoAVI() { -// if (FrameworkConstants.VIDEO_RECORD.toLowerCase().trim().equals(FrameworkConstants.YES)) { -// try { -// //Get file Last Modified in folder -// File video = FileHelpers.getFileLastModified("ExportData/Videos"); -// if (video != null) { -// Allure.addAttachment("Failed test Video record AVI", "video/avi", Files.asByteSource(video).openStream(), ".avi"); -// } else { -// LogUtils.warn("Video record not found."); -// LogUtils.warn("Can not attachment Video in Allure report"); -// } -// -// } catch (IOException e) { -// LogUtils.error("Can not attachment Video in Allure report"); -// e.printStackTrace(); -// } -// } -// } + public static void addAttachmentVideoAVI() { + try { + //Get file Last Modified in folder + File video = FileActions.getInstance().getFileLastModified("./" + videoRecordedPath); + if (video != null) { + Allure.addAttachment("Video record AVI", "video/avi", Files.asByteSource(video).openStream(), ".avi"); + } else { + CustomReporter.logWarning("Video record not found."); + CustomReporter.logWarning("Can not attachment Video in Allure report"); + } + + } catch (IOException e) { + CustomReporter.logError("Can not attachment Video in Allure report"); + e.printStackTrace(); + } + } + + public static void addAttachmentVideoMP4() { + try { + //Get file Last Modified in folder + File video = FileActions.getInstance().getFileLastModified(videoRecordedPath); + if (video != null) { + Allure.addAttachment("Failed test Video record MP4", "video/mp4", Files.asByteSource(video).openStream(), ".mp4"); + } else { + CustomReporter.logWarning("Video record not found."); + CustomReporter.logWarning("Can not attachment Video in Allure report"); + } + + } catch (IOException e) { + CustomReporter.logError("Can not attachment Video in Allure report"); + e.printStackTrace(); + } + } + + /** + * Attach the Screenshot to the allure report + * + * @param driver - WebDriver Instance of the Browser + * @return byte[] - Byte Array of the Screenshot + */ + @Attachment(value = "Screenshot", type = "image/png") + public static byte[] attachScreenshotToAllureReport(WebDriver driver) { + return ((TakesScreenshot) driver) + .getScreenshotAs(OutputType.BYTES); + } + + /** + * Attach the api request to the allure report + * + * @param type - Type + * @param b - Byte Array of the API Request + * @return byte[] - Byte Array of the API Request + */ + @Attachment(value = "API Request - {type}", type = "text/json") + public static byte[] attachApiRequestToAllureReport(String type, byte[] b) { + return attachTextJson(b); + } + + @Attachment(value = "API Response", type = "text/json") + public static byte[] attachApiResponseToAllureReport(byte[] b) { + return attachTextJson(b); + } + + @Attachment(value = "Log Console", type = "text/json") + public static byte[] attachLogConsoleToAllureReport(byte[] b) { + return attachTextJson(b); + } + + public static byte[] attachTextJson(byte[] b) { + try { + return b; + } catch (Exception e) { + return null; + } + } + + @Step("{logText}") + static void writeStepToReport(String logText, List> attachments) { + createLog(logText, false); + if (attachments != null && !attachments.isEmpty()) { + attachments.forEach(attachment -> { + if (attachment != null + && !attachment.isEmpty() + && attachment.get(2).getClass().toString().toLowerCase().contains("string") + && !attachment.get(2).getClass().toString().contains("StringInputStream")) { + if (!attachment.get(2).toString().isEmpty()) { + Attachments.attach(attachment.get(0).toString(), attachment.get(1).toString(), attachment.get(2).toString()); + } + } else if (attachment != null && !attachment.isEmpty()) { + if (attachment.get(2) instanceof byte[]) { + Attachments.attach(attachment.get(0).toString(), attachment.get(1).toString(), new ByteArrayInputStream((byte[]) attachment.get(2))); + } else { + Attachments.attach(attachment.get(0).toString(), attachment.get(1).toString(), (InputStream) attachment.get(2)); + } + } + }); + } + } + + public static void writeStepToReport(String logText) { + createLog(logText, true); + Allure.step(logText, getAllureStepStatus(logText)); + } + + static Status getAllureStepStatus(String logText) { + if (logText != null && logText.toLowerCase().contains("failed")) { + return Status.FAILED; + } + if (Reporter.getCurrentTestResult() != null) { + var testNgStatus = Reporter.getCurrentTestResult().getStatus(); + return switch (testNgStatus) { + case ITestResult.FAILURE -> Status.FAILED; + case ITestResult.SKIP -> Status.SKIPPED; + default -> Status.PASSED; + }; + } else { + return Status.PASSED; + } + } public static void openAllureReportAfterExecution() { String commandToOpenAllureReport; @@ -70,6 +174,32 @@ public static void openAllureReportAfterExecution() { } else { commandToOpenAllureReport = ("sh generate_allure_report.sh"); } - TerminalActions.getInstance(true, true).performTerminalCommand(commandToOpenAllureReport); + TerminalActions.getInstance().performTerminalCommand(commandToOpenAllureReport); + } + + public static void cleanAllureResultsDirectory() { + // clean allure-results directory before execution + try { + FileActions.getInstance().deleteFile("allure-report/"); + FileActions.getInstance().deleteFolder(allureResultsFolderPath.substring(0, allureResultsFolderPath.length() - 1)); + } catch (Exception t) { + CustomReporter.logError("Failed to delete allure-results as it is currently open. Kindly restart your device to unlock the directory."); + } + } + + public static void writeAllureReport() { + // add correct file extension based on target OS + // allure generate --single-file allure-results -o allure-report + String commandToCreateAllureReport = "allure generate --single-file allure-results -o allure-report"; + allureBinaryPath = allureExtractionLocation + "allure-" + "/bin/allure"; + +// if (SystemUtils.IS_OS_WINDOWS) { +// commandToCreateAllureReport = allureBinaryPath + ".bat" + " generate --single-file --clean '" +// + allureResultsFolderPath.substring(0, allureResultsFolderPath.length() - 1) + "' -o 'allure-report'"; +// } else { +// commandToCreateAllureReport = allureBinaryPath + " generate --single-file --clean " +// + allureResultsFolderPath.substring(0, allureResultsFolderPath.length() - 1) + " -o allure-report"; +// } + TerminalActions.getInstance().performTerminalCommand(commandToCreateAllureReport); } } diff --git a/src/main/java/com/engine/reports/Attachments.java b/src/main/java/com/engine/reports/Attachments.java index 544137a..c568f03 100644 --- a/src/main/java/com/engine/reports/Attachments.java +++ b/src/main/java/com/engine/reports/Attachments.java @@ -1,98 +1,21 @@ package com.engine.reports; -import com.aventstack.extentreports.ExtentTest; -import com.aventstack.extentreports.MediaEntityBuilder; -import com.aventstack.extentreports.markuputils.CodeLanguage; -import com.aventstack.extentreports.markuputils.MarkupHelper; -import com.aventstack.extentreports.model.Media; import com.engine.Helper; -import com.engine.evidence.ScreenShot; import io.qameta.allure.Allure; -import io.qameta.allure.Attachment; -import org.apache.commons.io.IOUtils; import org.openqa.selenium.*; -import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.print.PrintOptions; import org.testng.Reporter; -import java.awt.image.BufferedImage; import java.io.*; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.util.Base64; -import java.util.stream.Collectors; + +import static com.engine.reports.CustomReporter.logAttachmentAction; +import static com.engine.reports.ExtentReport.attachCodeBlockToExtentReport; +import static com.engine.reports.ExtentReport.attachImageToExtentReport; public class Attachments { private static org.apache.logging.log4j.Logger logger4j; - private static final ThreadLocal extentTest = new ThreadLocal<>(); private static final String currentTime = Helper.getCurrentTime("dd-MM-yyyy HH:mm:ss"); - // ***************************************** Take Screen Shot Methods *****************************************// - //**************************************************************************************************************// - - - // ***************************************** Attach Methods *****************************************// - //******************************************************************************************************// - - /** - * Attach the Screenshot to the Extent Report - * - * @param driver - WebDriver Instance of the Browser - * @return Media - Media Entity Builder Instance of the Screenshot - */ - public static Media attachScreenshotToExtentReport(WebDriver driver) { - return MediaEntityBuilder - .createScreenCaptureFromBase64String(((TakesScreenshot) driver) - .getScreenshotAs(OutputType.BASE64), Helper.getTestMethodName() + currentTime + "_Screenshot").build(); - } - - public static BufferedImage attachScreenshotToExtentReport2(WebDriver driver) { - return ScreenShot.takeFullScreenShoot2(driver, Helper.getTestMethodName() + currentTime + "_Screenshot"); - } - - public static Media attachFullPageScreenShotToExtentReport(FirefoxDriver driver) { - return MediaEntityBuilder - .createScreenCaptureFromBase64String(String.valueOf((driver) - .getFullPageScreenshotAs(OutputType.FILE)), Helper.getTestMethodName() + currentTime + "_fullPage").build(); - } - - /** - * Attach the Screenshot to the allure report - * - * @param driver - WebDriver Instance of the Browser - * @return byte[] - Byte Array of the Screenshot - */ - @Attachment(value = "Screenshot", type = "image/png") - public static byte[] attachScreenshotToAllureReport(WebDriver driver) { - return ((TakesScreenshot) driver) - .getScreenshotAs(OutputType.BYTES); - } -// @Attachment (value = "Full Page Screenshot", type = "image/png") -// public static byte[] attachScreenshotToAllureReport (WebDriver driver) { -// return ((TakesScreenshot) driver) -// .getScreenshotAs(OutputType.BYTES); -// } -//TODO: attach diff types of files to allure report - /** - * Attach the api request to the allure report - * - * @param type - Type - * @param b - Byte Array of the API Request - * @return byte[] - Byte Array of the API Request - */ - @Attachment(value = "API Request - {type}", type = "text/json") - public static byte[] attachApiRequestToAllureReport(String type, byte[] b) { - return attachTextJson(b); - } - - @Attachment(value = "API Response", type = "text/json") - public static byte[] attachApiResponseToAllureReport(byte[] b) { - return attachTextJson(b); - } - - @Attachment(value = "Log Console", type = "text/json") - public static byte[] attachLogConsoleToAllureReport(byte[] b) { - return attachTextJson(b); - } // ***************************************** Print Method *****************************************// //******************************************************************************************************// @@ -104,7 +27,7 @@ public static byte[] attachLogConsoleToAllureReport(byte[] b) { * @param pageRange - Page Range to be printed * @throws */ - public static void printPage(WebDriver driver, int pageRange) throws IOException { + public static void printPage(WebDriver driver, int pageRange) { try { CustomReporter.logInfoStep("Printing" + driver.getTitle() + "page....... "); PrintsPage printer = ((PrintsPage) driver); @@ -118,83 +41,6 @@ public static void printPage(WebDriver driver, int pageRange) throws IOException } } - @SuppressWarnings("SpellCheckingInspection") - private static void attachBasedOnFileType(String attachmentType, String attachmentName, - ByteArrayOutputStream attachmentContent, String attachmentDescription) { - var content = new ByteArrayInputStream(attachmentContent.toByteArray()); - if (attachmentType.toLowerCase().contains("screenshot")) { - Allure.addAttachment(attachmentDescription, "image/png", content, ".png"); - attachImageToExtentReport("image/png", new ByteArrayInputStream(attachmentContent.toByteArray())); - } else if (attachmentType.toLowerCase().contains("recording")) { - Allure.addAttachment(attachmentDescription, "video/mp4", content, ".mp4"); - } else if (attachmentType.toLowerCase().contains("gif")) { - Allure.addAttachment(attachmentDescription, "image/gif", content, ".gif"); - attachImageToExtentReport("image/gif", new ByteArrayInputStream(attachmentContent.toByteArray())); - } else if (attachmentType.toLowerCase().contains("csv") || attachmentName.toLowerCase().contains("csv")) { - Allure.addAttachment(attachmentDescription, "text/csv", content, ".csv"); - attachCodeBlockToExtentReport("text/csv", new ByteArrayInputStream(attachmentContent.toByteArray())); - } else if (attachmentType.toLowerCase().contains("xml") || attachmentName.toLowerCase().contains("xml")) { - Allure.addAttachment(attachmentDescription, "text/xml", content, ".xml"); - attachCodeBlockToExtentReport("text/xml", new ByteArrayInputStream(attachmentContent.toByteArray())); - } else if (attachmentType.toLowerCase().contains("excel") || attachmentName.toLowerCase().contains("excel")) { - Allure.addAttachment(attachmentDescription, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", content, ".xlsx"); - } else if (attachmentType.toLowerCase().contains("json") || attachmentName.toLowerCase().contains("json")) { - Allure.addAttachment(attachmentDescription, "text/json", content, ".json"); - attachCodeBlockToExtentReport("text/json", new ByteArrayInputStream(attachmentContent.toByteArray())); - } else if (attachmentType.toLowerCase().contains("properties")) { - Allure.addAttachment(attachmentDescription, "text/plain", content, ".properties"); - } else if (attachmentType.toLowerCase().contains("link")) { - Allure.addAttachment(attachmentDescription, "text/uri-list", content, ".uri"); - } else if (attachmentType.toLowerCase().contains("engine logs")) { - Allure.addAttachment(attachmentDescription, "text/plain", content, ".txt"); - } else if (attachmentType.toLowerCase().contains("page snapshot")) { - Allure.addAttachment(attachmentDescription, "multipart/related", content, ".mhtml"); - } else if (attachmentType.toLowerCase().contains("html")) { - Allure.addAttachment(attachmentDescription, "text/html", content, ".html"); - } else { - Allure.addAttachment(attachmentDescription, content); - } - } - - private static void attachImageToExtentReport(String attachmentType, InputStream attachmentContent) { - if (extentTest.get() != null) { - try { - var image = Base64.getEncoder().encodeToString(IOUtils.toByteArray(attachmentContent)); - if (attachmentType.toLowerCase().contains("gif")) { - extentTest.get().addScreenCaptureFromBase64String(image); - } else { - extentTest.get().info(MediaEntityBuilder.createScreenCaptureFromBase64String(image).build()); - } - } catch (IOException e) { - CustomReporter.logError("Failed to attach screenshot to extentReport."); - } - } - } - - private static void attachCodeBlockToExtentReport(String attachmentType, InputStream attachmentContent) { - if (extentTest.get() != null) { - try { - var codeBlock = IOUtils.toString(attachmentContent, StandardCharsets.UTF_8); - switch (attachmentType) { - case "text/json" -> - extentTest.get().info(MarkupHelper.createCodeBlock(codeBlock, CodeLanguage.JSON)); - case "text/xml" -> extentTest.get().info(MarkupHelper.createCodeBlock(codeBlock, CodeLanguage.XML)); - default -> extentTest.get().info(MarkupHelper.createCodeBlock(codeBlock)); - } - } catch (IOException e) { - CustomReporter.logError("Failed to attach code block to extentReport."); - } - } - } - - // @Attachment(type = "text/json") - public static byte[] attachTextJson(byte[] b) { - try { - return b; - } catch (Exception e) { - return null; - } - } /** * Adds a new attachment using the input parameters provided. The attachment is @@ -229,9 +75,6 @@ public static void createAttachment(String attachmentType, String attachmentName attachmentContent.transferTo(byteArrayOutputStream); } catch (IOException e) { var error = "Error while creating Attachment"; -// if (logger == null) { -// initializeLogger(); -// } logger4j.info(error, e); Reporter.log(error, false); } @@ -240,19 +83,70 @@ public static void createAttachment(String attachmentType, String attachmentName logAttachmentAction(attachmentType, attachmentName, byteArrayOutputStream); } } -// private static void initializeLogger() { -// Configurator.initialize(null, PropertyFileManager.getCUSTOM_PROPERTIES_FOLDER_PATH() + "/log4j2.properties"); -// logger = LogManager.getLogger(ReportManager.class.getName()); -// } - private static synchronized void logAttachmentAction(String attachmentType, String - attachmentName, ByteArrayOutputStream attachmentContent) { - CustomReporter.logInfoStep("Successfully created attachment \"" + attachmentType + " - " + attachmentName + "\""); - String timestamp = Helper.getCurrentTime(); - String theString; - var br = new BufferedReader( - new InputStreamReader(new ByteArrayInputStream(attachmentContent.toByteArray()), StandardCharsets.UTF_8)); - theString = br.lines().collect(Collectors.joining(System.lineSeparator())); + @SuppressWarnings("SpellCheckingInspection") + private static void attachBasedOnFileType(String attachmentType, String attachmentName, ByteArrayOutputStream attachmentContent, String attachmentDescription) { + var content = new ByteArrayInputStream(attachmentContent.toByteArray()); + CustomReporter.logConsole("Start attaching " + attachmentType + " to the report"); + if (attachmentType.toLowerCase().contains("screenshot")) { + Allure.addAttachment(attachmentDescription, "image/png", content, ".png"); + attachImageToExtentReport("image/png", new ByteArrayInputStream(attachmentContent.toByteArray())); + } else if (attachmentType.toLowerCase().contains("recording")) { + Allure.addAttachment(attachmentDescription, "video/mp4", content, ".mp4"); + } else if (attachmentType.toLowerCase().contains("gif")) { + Allure.addAttachment(attachmentDescription, "image/gif", content, ".gif"); + attachImageToExtentReport("image/gif", new ByteArrayInputStream(attachmentContent.toByteArray())); + } else if (attachmentType.toLowerCase().contains("csv") || attachmentName.toLowerCase().contains("csv")) { + Allure.addAttachment(attachmentDescription, "text/csv", content, ".csv"); + attachCodeBlockToExtentReport("text/csv", new ByteArrayInputStream(attachmentContent.toByteArray())); + } else if (attachmentType.toLowerCase().contains("xml") || attachmentName.toLowerCase().contains("xml")) { + Allure.addAttachment(attachmentDescription, "text/xml", content, ".xml"); + attachCodeBlockToExtentReport("text/xml", new ByteArrayInputStream(attachmentContent.toByteArray())); + } else if (attachmentType.toLowerCase().contains("excel") || attachmentName.toLowerCase().contains("excel")) { + Allure.addAttachment(attachmentDescription, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", content, ".xlsx"); + } else if (attachmentType.toLowerCase().contains("json") || attachmentName.toLowerCase().contains("json")) { + Allure.addAttachment(attachmentDescription, "text/json", content, ".json"); + attachCodeBlockToExtentReport("text/json", new ByteArrayInputStream(attachmentContent.toByteArray())); + } else if (attachmentType.toLowerCase().contains("properties")) { + Allure.addAttachment(attachmentDescription, "text/plain", content, ".properties"); + } else if (attachmentType.toLowerCase().contains("link")) { + Allure.addAttachment(attachmentDescription, "text/uri-list", content, ".uri"); + } else if (attachmentType.toLowerCase().contains("engine logs")) { + Allure.addAttachment(attachmentDescription, "text/plain", content, ".txt"); + } else if (attachmentType.toLowerCase().contains("page snapshot")) { + Allure.addAttachment(attachmentDescription, "multipart/related", content, ".mhtml"); + } else if (attachmentType.toLowerCase().contains("html")) { + Allure.addAttachment(attachmentDescription, "text/html", content, ".html"); + } else { + Allure.addAttachment(attachmentDescription, content); + } + } + + public enum AttachmentType { + SCREENSHOT("Screenshot"), + RECORDING("Recording"), + GIF("GIF"), + CSV("CSV"), + XML("XML"), + EXCEL("Excel"), + JSON("JSON"), + PROPERTIES("Properties"), + LINK("Link"), + ENGINE_LOGS("Engine Logs"), + PAGE_SNAPSHOT("Page Snapshot"), + HTML("HTML"), + TEXT("Text"), + OTHER("Other"); + + private final String value; + + AttachmentType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } } diff --git a/src/main/java/com/engine/reports/CustomReporter.java b/src/main/java/com/engine/reports/CustomReporter.java index 464299d..022984b 100644 --- a/src/main/java/com/engine/reports/CustomReporter.java +++ b/src/main/java/com/engine/reports/CustomReporter.java @@ -33,6 +33,10 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; + +import static com.engine.reports.AllureReport.getAllureStepStatus; +import static com.engine.reports.AllureReport.writeStepToReport; public class CustomReporter { @@ -66,7 +70,7 @@ public static void lineSeparator() { * @param text logged by action that will be added as a step in the execution report (e.g. click on button) */ -// @Step("{text}") + @Step("{text}") public static void logInfoStep(String text) { createLog(text, Level.INFO); ExtentReport.info(text); @@ -134,6 +138,15 @@ public static void logAttachments(String logText, List> attachments } } + static synchronized void logAttachmentAction(String attachmentType, String attachmentName, ByteArrayOutputStream attachmentContent) { + CustomReporter.logInfoStep("Successfully created attachment \"" + attachmentType + " - " + attachmentName + "\""); + String timestamp = Helper.getCurrentTime(); + String theString; + var br = new BufferedReader( + new InputStreamReader(new ByteArrayInputStream(attachmentContent.toByteArray()), StandardCharsets.UTF_8)); + theString = br.lines().collect(Collectors.joining(System.lineSeparator())); + } + public static void createImportantReportEntry(String logText) { String log = System.lineSeparator() + "\033[0;7m" + @@ -214,51 +227,10 @@ public static void failReporter(Class failedFileManager, String message, Thro Assert.fail(message + rootCause, throwable); } - - @Step("{logText}") - static void writeStepToReport(String logText, List> attachments) { - createLog(logText, false); - if (attachments != null && !attachments.isEmpty()) { - attachments.forEach(attachment -> { - if (attachment != null - && !attachment.isEmpty() - && attachment.get(2).getClass().toString().toLowerCase().contains("string") - && !attachment.get(2).getClass().toString().contains("StringInputStream")) { - if (!attachment.get(2).toString().isEmpty()) { - Attachments.attach(attachment.get(0).toString(), attachment.get(1).toString(), attachment.get(2).toString()); - } - } else if (attachment != null && !attachment.isEmpty()) { - if (attachment.get(2) instanceof byte[]) { - Attachments.attach(attachment.get(0).toString(), attachment.get(1).toString(), new ByteArrayInputStream((byte[]) attachment.get(2))); - } else { - Attachments.attach(attachment.get(0).toString(), attachment.get(1).toString(), (InputStream) attachment.get(2)); - } - } - }); - } - } - - public static void writeStepToReport(String logText) { - createLog(logText, true); - Allure.step(logText, getAllureStepStatus(logText)); - } - - private static Status getAllureStepStatus(String logText) { - if (logText != null && logText.toLowerCase().contains("failed")) { - return Status.FAILED; - } - if (Reporter.getCurrentTestResult() != null) { - var testNgStatus = Reporter.getCurrentTestResult().getStatus(); - return switch (testNgStatus) { - case ITestResult.FAILURE -> Status.FAILED; - case ITestResult.SKIP -> Status.SKIPPED; - default -> Status.PASSED; - }; - } else { - return Status.PASSED; - } + public static void passAction(WebDriver driver, String testData) { + String actionName = Thread.currentThread().getStackTrace()[2].getMethodName(); + passAction(driver, actionName, testData); } - public static void passAction(String testData) { String actionName = Thread.currentThread().getStackTrace()[2].getMethodName(); reportActionResult(actionName, testData, null, true); @@ -269,6 +241,10 @@ public static void passAction(String testData, String log) { reportActionResult(actionName, testData, log, true); } + public static void passAction(WebDriver driver, String actionName, String testData) { + reportActionResult(driver, actionName, testData, true); + } + public static void failAction(String testData, Exception... rootCauseException) { String actionName = Thread.currentThread().getStackTrace()[2].getMethodName(); failAction(actionName, testData, rootCauseException); @@ -282,7 +258,7 @@ public static void failAction(Exception... rootCauseException) { public static void failAction(String actionName, String testData, Exception... rootCauseException) { String message = reportActionResult(actionName, testData, null, false, rootCauseException); - CustomReporter.failReporter(FileActions.class, message, rootCauseException[0]); + failReporter(FileActions.class, message, rootCauseException[0]); } @@ -309,7 +285,6 @@ public static String reportActionResult(String actionName, String testData, Stri attachments.add(actualValueAttachment); } // Minimize File Action log steps and move them to discrete logs if called - // within SHAFT_Engine itself StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); StackTraceElement parentMethod = stackTrace[4]; if (parentMethod.getClassName().contains("engine")) { @@ -324,6 +299,37 @@ public static String reportActionResult(String actionName, String testData, Stri return message; } + public static String reportActionResult(WebDriver driver, String actionName, String testData, Boolean passFailStatus, Exception... rootCauseException) { + actionName = Helper.convertToSentenceCase(actionName); + String message; + if (Boolean.TRUE.equals(passFailStatus)) { + message = "Browser Action: " + actionName; + } else { + message = "Browser Action: " + actionName + " failed"; + } + List> attachments = new ArrayList<>(); + if (testData != null && !testData.isEmpty()) { + if (testData.length() >= 500 || testData.contains("") || testData.contains("") || testData.startsWith("From: ")) { + List actualValueAttachment = Arrays.asList("Browser Action Test Data - " + actionName, "Actual Value", testData); + attachments.add(actualValueAttachment); + } else { + message = message + " \"" + testData.trim() + "\""; + } + } + if (rootCauseException != null && rootCauseException.length >= 1) { + List actualValueAttachment = Arrays.asList("Browser Action Exception - " + actionName, "Stacktrace", CustomReporter.formatStackTraceToLogEntry(rootCauseException[0])); + attachments.add(actualValueAttachment); + } + message = message + "."; + message = message.replace("Browser Action: ", ""); + if (!attachments.equals(new ArrayList<>())) { + CustomReporter.logAttachments(message, attachments); + } else { + CustomReporter.logInfoStep(message); + } + return message; + } + public static void logThrowable(Throwable t) { createLog(formatStackTraceToLogEntry(t), Level.ERROR); } @@ -341,7 +347,7 @@ public static void createLog(String logText, Level loglevel) { logger.log(loglevel, logText.trim()); } - private static void createLog(String logText, boolean addToConsoleLog) { + public static void createLog(String logText, boolean addToConsoleLog) { String timestamp = (new SimpleDateFormat(TIMESTAMP_FORMAT)).format(new Date(System.currentTimeMillis())); if (logText == null) { logText = "null"; diff --git a/src/main/java/com/engine/reports/ExtentReport.java b/src/main/java/com/engine/reports/ExtentReport.java index 69e2a50..a3472fe 100644 --- a/src/main/java/com/engine/reports/ExtentReport.java +++ b/src/main/java/com/engine/reports/ExtentReport.java @@ -2,7 +2,10 @@ import com.aventstack.extentreports.ExtentReports; import com.aventstack.extentreports.ExtentTest; + +import com.aventstack.extentreports.MediaEntityBuilder; import com.aventstack.extentreports.Status; +import com.aventstack.extentreports.markuputils.CodeLanguage; import com.aventstack.extentreports.markuputils.Markup; import com.aventstack.extentreports.markuputils.MarkupHelper; import com.aventstack.extentreports.model.Media; @@ -11,14 +14,26 @@ import com.aventstack.extentreports.reporter.configuration.ViewName; import com.engine.Helper; import com.engine.constants.FrameworkConstants; +import com.engine.evidence.ScreenShot; import com.engine.utils.IconUtils; - +import org.apache.commons.io.IOUtils; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.TakesScreenshot; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.firefox.FirefoxDriver; + +import java.awt.image.BufferedImage; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.List; public class ExtentReport { - - private static ExtentReports extentReports; private static ExtentTest extentTest; + private static ExtentReports extentReports; + static String currentTime = Helper.getCurrentTime("yyyy-MM-dd_HH-mm"); public static void initializeExtentReport() { @@ -70,27 +85,33 @@ public static void createTest(String testCaseName) { } public static void createTestAndDescription(String testCaseName, String description) { - extentTest = extentReports.createTest(testCaseName, description); + extentTest + = extentReports.createTest(testCaseName, description); } public static void createTest(String testCaseName, String author) { - extentTest = extentReports.createTest(testCaseName).assignAuthor(author); + extentTest + = extentReports.createTest(testCaseName).assignAuthor(author); } public static void createTest(String testCaseName, String author, String category) { - extentTest = extentReports.createTest(testCaseName).assignAuthor(author).assignCategory(category); + extentTest + = extentReports.createTest(testCaseName).assignAuthor(author).assignCategory(category); } public static void createTest(String testCaseName, String author, String category, String device) { - extentTest = extentReports.createTest(testCaseName).assignAuthor(author).assignCategory(category).assignDevice(device); + extentTest + = extentReports.createTest(testCaseName).assignAuthor(author).assignCategory(category).assignDevice(device); } public static void createTestNode(String testCaseName, String nodeName) { - extentTest = extentReports.createTest(testCaseName).createNode(nodeName); + extentTest + = extentReports.createTest(testCaseName).createNode(nodeName); } public static void createTestNode(String testCaseName, String nodeName, String nodeDescription) { - extentTest = extentReports.createTest(testCaseName).createNode(nodeName, nodeDescription); + extentTest + = extentReports.createTest(testCaseName).createNode(nodeName, nodeDescription); } public static void removeTest(String testCaseName) { @@ -104,138 +125,171 @@ public static void flushReports() { //************* info methods *************// public static void info(String message) { - if (extentTest != null) { - extentTest.info(message); + if (extentTest + != null) { + extentTest + .info(message); } } public static void info(Throwable t) { - extentTest.info(t); + extentTest + .info(t); } public static void info(Media m) { - extentTest.info(m); + extentTest + .info(m); } public static void info(Markup m) { - extentTest.info(m); + extentTest + .info(m); } public static void info(String message, Media media) { - extentTest.info(message, media); + extentTest + .info(message, media); } public static void info(Throwable t, Media media) { - extentTest.info(t, media); + extentTest + .info(t, media); } //************* pass methods *************// public static void pass(String message) { - extentTest.pass(message); + extentTest + .pass(message); } public static void pass(Throwable t) { - extentTest.pass(t); + extentTest + .pass(t); } public static void pass(Media media) { - extentTest.pass(media); + extentTest + .pass(media); } public static void pass(Markup m) { - extentTest.pass(m); + extentTest + .pass(m); } public static void pass(String message, Media media) { - extentTest.pass(message, media); + extentTest + .pass(message, media); } public static void pass(Throwable t, Media media) { - extentTest.pass(t, media); + extentTest + .pass(t, media); } //************* fail methods *************// public static void fail(String message) { - extentTest.fail(message); + extentTest + .fail(message); } public static void fail(Throwable t) { - extentTest.fail(t); + extentTest + .fail(t); } public static void fail(Media media) { - extentTest.fail(media); + extentTest + .fail(media); } public static void fail(Markup m) { - extentTest.fail(m); + extentTest + .fail(m); } public static void fail(String message, Media media) { - extentTest.fail(message, media); + extentTest + .fail(message, media); } public static void fail(Throwable t, Media media) { - extentTest.fail(t, media); + extentTest + .fail(t, media); } //************* waring methods *************// public static void warning(String message) { - extentTest.warning(message); + extentTest + .warning(message); } public static void warning(Throwable t) { - extentTest.warning(t); + extentTest + .warning(t); } public static void warning(Media media) { - extentTest.warning(media); + extentTest + .warning(media); } public static void warning(Markup m) { - extentTest.warning(m); + extentTest + .warning(m); } public static void warning(String message, Media media) { - extentTest.warning(message, media); + extentTest + .warning(message, media); } public static void warning(Throwable t, Media media) { - extentTest.warning(t, media); + extentTest + .warning(t, media); } //************* skip methods *************// public static void skip(String message) { - extentTest.skip(message); + extentTest + .skip(message); } public static void skip(Throwable t) { - extentTest.skip(t); + extentTest + .skip(t); } public static void skip(Media media) { - extentTest.skip(media); + extentTest + .skip(media); } public static void skip(Markup m) { - extentTest.skip(m); + extentTest + .skip(m); } public static void skip(String message, Media media) { - extentTest.skip(message, media); + extentTest + .skip(message, media); } public static void skip(Throwable t, Media media) { - extentTest.skip(t, media); + extentTest + .skip(t, media); } //************* helper ****************/ public static void logCodeBlock(String codeBlock) { - extentTest.log(Status.INFO, MarkupHelper.createCodeBlock(codeBlock)); + extentTest + .log(Status.INFO, MarkupHelper.createCodeBlock(codeBlock)); } public static void logTable(String[][] data) { - extentTest.log(Status.INFO, MarkupHelper.createTable(data)); + extentTest + .log(Status.INFO, MarkupHelper.createTable(data)); } public static void logOrderList(Object data) { @@ -243,8 +297,60 @@ public static void logOrderList(Object data) { } public static void logJsonCodeBlock(Object data) { - extentTest.log(Status.INFO, MarkupHelper.createJsonCodeBlock(data)); + extentTest + .log(Status.INFO, MarkupHelper.createJsonCodeBlock(data)); } + /** + * Attach the Screenshot to the Extent Report + * + * @param driver - WebDriver Instance of the Browser + * @return Media - Media Entity Builder Instance of the Screenshot + */ + public static Media attachScreenshotToExtentReport(WebDriver driver) { + return MediaEntityBuilder + .createScreenCaptureFromBase64String(((TakesScreenshot) driver) + .getScreenshotAs(OutputType.BASE64), Helper.getTestMethodName() + currentTime + "_Screenshot").build(); + } + +// public static BufferedImage attachScreenshotToExtentReport2(WebDriver driver) { +// return ScreenShot.takeFullScreenShoot2(driver, Helper.getTestMethodName() + currentTime + "_Screenshot"); +// } + + public static Media attachFullPageScreenShotToExtentReport(FirefoxDriver driver) { + return MediaEntityBuilder + .createScreenCaptureFromBase64String(String.valueOf((driver) + .getFullPageScreenshotAs(OutputType.FILE)), Helper.getTestMethodName() + currentTime + "_fullPage").build(); + } + + public static void attachImageToExtentReport(String attachmentType, InputStream attachmentContent) { + if (extentTest != null) { + try { + var image = Base64.getEncoder().encodeToString(IOUtils.toByteArray(attachmentContent)); + if (attachmentType.toLowerCase().contains("gif")) { + extentTest.addScreenCaptureFromBase64String(image); + } else { + extentTest.info(MediaEntityBuilder.createScreenCaptureFromBase64String(image).build()); + } + } catch (IOException e) { + CustomReporter.logError("Failed to attach screenshot to extentReport."); + } + } + } + + public static void attachCodeBlockToExtentReport(String attachmentType, InputStream attachmentContent) { + if (extentTest != null) { + try { + var codeBlock = IOUtils.toString(attachmentContent, StandardCharsets.UTF_8); + switch (attachmentType) { + case "text/json" -> extentTest.info(MarkupHelper.createCodeBlock(codeBlock, CodeLanguage.JSON)); + case "text/xml" -> extentTest.info(MarkupHelper.createCodeBlock(codeBlock, CodeLanguage.XML)); + default -> extentTest.info(MarkupHelper.createCodeBlock(codeBlock)); + } + } catch (IOException e) { + CustomReporter.logError("Failed to attach code block to extentReport."); + } + } + } } diff --git a/src/main/java/practice/gui/pages/alerts/AlertsPage.java b/src/main/java/practice/gui/pages/alerts/AlertsPage.java index 7425fec..1bb6abf 100644 --- a/src/main/java/practice/gui/pages/alerts/AlertsPage.java +++ b/src/main/java/practice/gui/pages/alerts/AlertsPage.java @@ -1,5 +1,6 @@ package practice.gui.pages.alerts; +import com.engine.actions.FileActions; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import com.engine.actions.ElementActions; @@ -14,6 +15,10 @@ public AlertsPage(WebDriver driver) { this.driver = driver; } + public static AlertsPage getInstance() { + return new AlertsPage(driver); + } + //////////////////////////// Elements Locators //////////////////////////// private By triggerAlertButton = By.xpath(".//button[text()='Click for JS Alert']"); private By triggerConfirmButton = By.xpath(".//button[text()='Click for JS Confirm']"); diff --git a/src/main/resources/properties/config.properties b/src/main/resources/properties/config.properties index 6bcd100..88d19e0 100644 --- a/src/main/resources/properties/config.properties +++ b/src/main/resources/properties/config.properties @@ -1,14 +1,23 @@ #Project Name projectName=TestAutomation Using Selenium -#Google Chrome || Mozilla Firefox || Edge -browserType=Google Chrome -#Local || Local Headless || Remote +#install Driver +automaticallyInstallDriver=true +chromeDriverPath= +geckoDriverPath= +edgeDriverPath= +#Chrome || Firefox || Edge +browserType=Chrome +#Local || Remote executionType=Local +# Browser Options +headless=true +startMaximize=true +scriptExecutionTimeout=30 +pageLoadTimeout=30 #in case of remote execution host=localhost port=4444 #Window resolution in case of Local execution (if maximize value is true then it will ignore the resolution values) -maximize=true width=1024 height=768 # seconds @@ -20,15 +29,10 @@ retryFailedTest=0 createAnimatedGif=true animatedGif_frameDelay=500 #video -videoParams_recordVideo=true -videoParams_scope=DriverSession +recordVideo=true # applitools app.name=Applitools applitools.api.key=DuXZDFOMosoTqnYuT9KnVt1hyakSkg8RqfvvOW105Rw38110 -# URLs -TAU.homeUrl=https://the-internet.herokuapp.com/ -# API -rest.baseUrl=https://restful-booker.herokuapp.com/ # Appium appiumServerLink=http://127.0.0.1:4723/wd/hub # APK diff --git a/src/main/resources/properties/paths.properties b/src/main/resources/properties/paths.properties index 55f3cb8..25cb537 100644 --- a/src/main/resources/properties/paths.properties +++ b/src/main/resources/properties/paths.properties @@ -1,3 +1,11 @@ propertiesFolderPath=src/main/resources/properties/ # extent reports -extentReportPath=src/test/resources/reports/ \ No newline at end of file +extentReportPath=src/test/resources/reports/ +# URLs +TAU.homeUrl=https://the-internet.herokuapp.com/ +# API +rest.baseUrl=https://restful-booker.herokuapp.com/ +videoRecordedPath=src/test/resources/downloads/videos/ +screenshotPath=src/test/resources/downloads/screenshot/ +downloadBrowserPath=src/test/resources/downloads/browser/ +pdfPath=src/test/resources/downloads/pdf/ \ No newline at end of file diff --git a/src/test/java/webPractice/BaseTests.java b/src/test/java/webPractice/BaseTests.java index 2914384..cc4f468 100644 --- a/src/test/java/webPractice/BaseTests.java +++ b/src/test/java/webPractice/BaseTests.java @@ -13,7 +13,7 @@ public class BaseTests { @BeforeMethod public void setUp () { - driver = DriverFactory.getBrowser(DriverHelper.BrowserType.GOOGLE_CHROME, DriverHelper.ExecutionType.LOCAL); + driver = DriverFactory.getBrowser(DriverHelper.BrowserType.EDGE, DriverHelper.ExecutionType.LOCAL); } @AfterMethod diff --git a/src/test/java/webPractice/HandleHTMLCanvas.java b/src/test/java/webPractice/HandleHTMLCanvas.java deleted file mode 100644 index 99a65b1..0000000 --- a/src/test/java/webPractice/HandleHTMLCanvas.java +++ /dev/null @@ -1,55 +0,0 @@ -package webPractice; - - -import io.qameta.allure.*; -import org.apache.commons.io.FileUtils; -import org.openqa.selenium.*; -import org.openqa.selenium.interactions.Actions; -import org.openqa.selenium.support.ui.Select; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; -import com.engine.actions.BrowserActions; -import com.engine.driver.DriverFactory; - -import java.io.File; -import java.io.IOException; - - -public class HandleHTMLCanvas { - private WebDriver driver; - - @BeforeMethod - public void setUp_BeforeMethod() { - driver = DriverFactory.getBrowser(); - } - - @AfterMethod - public void closeBrowser() { - BrowserActions.closeAllOpenedBrowserWindows(driver); - } - - - /* - * Using Actions Class to perform click and hold to draw something in canvas Area - * - */ - @Test - @Severity( SeverityLevel.CRITICAL ) - @Description( "draw in canvas Test Case" ) - @Epic( "Selenium Actions on Elements" ) - @Story( "Actions Tutorial" ) - public void TestHTMLCanvas() throws IOException { - driver.get("https://cookbook.seleniumacademy.com/html5canvasdraw.html"); - WebElement drawList = driver.findElement(By.id("dtool")); - WebElement canvas = driver.findElement(By.id("imageTemp")); - Select drawTool = new Select(drawList); - drawTool.selectByValue("pencil"); - Actions builder = new Actions(driver); - builder.clickAndHold(canvas).moveByOffset(5, 60).moveByOffset(60, 5) - .moveByOffset(- 5, - 60).moveByOffset(- 60, - 5).release().perform(); - // Takes ScreenShot - File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); - FileUtils.copyFile(srcFile, new File("\\src\\ScreenShots\\canvas.png")); - } -} diff --git a/src/test/java/webPractice/browserInteractions/NavigationTests.java b/src/test/java/webPractice/browserInteractions/NavigationTests.java index 8535d12..ed0e4e3 100644 --- a/src/test/java/webPractice/browserInteractions/NavigationTests.java +++ b/src/test/java/webPractice/browserInteractions/NavigationTests.java @@ -1,7 +1,7 @@ package webPractice.browserInteractions; -import com.engine.reports.Attachments; import com.engine.evidence.ScreenShot; +import io.qameta.allure.Allure; import io.qameta.allure.Epic; import io.qameta.allure.Feature; import org.testng.annotations.Test; @@ -14,15 +14,14 @@ public class NavigationTests extends BaseTests { @Test public void verifyNavigator () { + Allure.parameter("Test Type", "Functional"); new BrowserActions(driver) .navigateToUrl("https://the-internet.herokuapp.com/") .refreshPage() .goForward() .navigateToUrl("https://github.com/ismail-elshafeiy") .goBack(); - ScreenShot.takeFullScreenShoot(driver, "ScreenShot/"); - Attachments.attachScreenshotToExtentReport2(driver); - - + BrowserActions.getInstance().capturePageSnapshot(); + ScreenShot.takeFullScreenShoot(driver); } } \ No newline at end of file diff --git a/src/test/java/webPractice/browserInteractions/ReferenceTests.java b/src/test/java/webPractice/browserInteractions/ScreenShotTests.java similarity index 74% rename from src/test/java/webPractice/browserInteractions/ReferenceTests.java rename to src/test/java/webPractice/browserInteractions/ScreenShotTests.java index 4bbd492..f309889 100644 --- a/src/test/java/webPractice/browserInteractions/ReferenceTests.java +++ b/src/test/java/webPractice/browserInteractions/ScreenShotTests.java @@ -1,7 +1,6 @@ package webPractice.browserInteractions; import com.engine.driver.DriverHelper; -import com.engine.reports.Attachments; import com.engine.actions.BrowserActions; import com.engine.driver.DriverFactory; import com.engine.evidence.ScreenShot; @@ -16,7 +15,7 @@ @Epic("Browser Interactions") @Feature("Windows") -public class ReferenceTests { +public class ScreenShotTests { private WebDriver driver; @Story("Screen Shots") @@ -49,7 +48,7 @@ public void takeElementScreenShotTest () { """) public void takeFullPage_Screenshot () throws IOException { - driver = DriverFactory.getBrowser(DriverHelper.BrowserType.MOZILLA_FIREFOX, DriverHelper.ExecutionType.LOCAL); + driver = DriverFactory.getBrowser(DriverHelper.BrowserType.FIREFOX, DriverHelper.ExecutionType.LOCAL); BrowserActions.navigateToUrl(driver, "https://www.selenium.dev/"); ScreenShot.takeFullPageScreenshot(driver, "Selenium Full Page Screenshot"); } @@ -61,8 +60,20 @@ public void takeFullPage_Screenshot () throws IOException { Note: This requires Chromium Browsers to be in headless mode """) public void printWindow () throws IOException { - driver = DriverFactory.getBrowser(DriverHelper.BrowserType.GOOGLE_CHROME, DriverHelper.ExecutionType.LOCAL_HEADLESS); + driver = DriverFactory.getBrowser(DriverHelper.BrowserType.CHROME); BrowserActions.navigateToUrl(driver, "https://www.selenium.dev"); - Attachments.printPage(driver, 6); + BrowserActions.printPage(driver, 6); + } + + @Story("Capture Page Snapshot") + @Test(description = "Capture Page Snapshot") + @Description(""" + Captures the current page within the browser. + Note: This requires Chromium Browsers to be in headless mode + """) + public void capturePageSnapshot() { + driver = DriverFactory.getBrowser(DriverHelper.BrowserType.CHROME); + BrowserActions.navigateToUrl(driver, "https://github.com/ismail-elshafeiy"); + BrowserActions.getInstance().capturePageSnapshot(); } } diff --git a/src/test/java/webPractice/dataDriven/FilesTest.java b/src/test/java/webPractice/dataDriven/FilesTest.java index 4d6ba7f..fae5fee 100644 --- a/src/test/java/webPractice/dataDriven/FilesTest.java +++ b/src/test/java/webPractice/dataDriven/FilesTest.java @@ -1,27 +1,52 @@ package webPractice.dataDriven; import com.engine.actions.FileActions; +import com.engine.reports.Attachments; import com.engine.reports.CustomReporter; +import io.qameta.allure.Allure; import org.testng.annotations.Test; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; -import java.util.Collection; -import java.util.List; -import static com.engine.dataDriven.CSVFileManager.compareTwoCSVFiles2; + +import static com.engine.dataDriven.CSVFileManager.compareTwoCSVFilesByValue; +import static com.engine.dataDriven.CSVFileManager.readCSVFile; public class FilesTest { @Test - public void testSuccessfulLogin2() throws IOException { + public void createFolder() { + String folderPath = "src/test/resources/newFolder/"; + FileActions.getInstance().createFolder(folderPath); + } + + @Test + public void copyFolder() { + String sourceFolderPath = "src/test/resources/TestData/"; + String destinationFolderPath = "src/test/resources/newFolder/"; + FileActions.getInstance().deleteFolder(destinationFolderPath); + FileActions.getInstance().copyFolder(sourceFolderPath, destinationFolderPath); + } + + @Test + public void createFile() { + String filePath = "src/test/resources/newFolder/"; + FileActions.getInstance().createFile(filePath, "newFile.csv"); + FileActions.getInstance().doesFileExist(filePath, "newFile.csv", 2); + FileActions.getInstance().writeToFile(filePath, "newFile.csv", "Hello World"); + } + + @Test + public void downloadFile() { + FileActions.getInstance().downloadFile("https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf", "dummy.pdf"); + } + + @Test + public void compareTwoCSVFiles() throws IOException { String filePath = "src/test/resources/TestData/CSVFile.csv"; String filePath2 = "src/test/resources/TestData/CSVFile2.csv"; - //new CSVFileManager("src/test/resources/TestData/CSVFile.csv"); -// String text = String.valueOf(excelFileTestDataReader.getCellData()); -// CustomReporter.logConsole("text: " + text); - //readDataLineByLine("src/test/resources/TestData/CSVFile.csv"); - //compareTwoCSVFiles(filePath, filePath2); - compareTwoCSVFiles2(filePath, filePath2); + compareTwoCSVFilesByValue(filePath, filePath2); } @Test @@ -31,4 +56,16 @@ public void renameFile() throws IOException { CustomReporter.logInfoStep("listOfFiles: " + fileName); FileActions.getInstance().renameFile("src/test/resources/csv/" + fileName, String.valueOf(destFile)); } + + @Test + public void convertFileToCSVFile() { + String excelFilePath = "src/test/resources/TestData/ExcelFile.xlsx"; + String csvFilePath = "src/test/resources/csv/ExcelToCSV1.csv"; + FileActions.getInstance().convertFileToCSV(excelFilePath, csvFilePath); + String csvContent = readCSVFile(csvFilePath); + // Attach the CSV file to the Allure report + assert csvContent != null; + Attachments.attach(Attachments.AttachmentType.CSV.getValue(), "Test case - CSV File", csvContent); + //Allure.addAttachment("Test case Attachment", "text/csv", csvContent, ".csv"); + } } \ No newline at end of file diff --git a/src/test/java/webPractice/dataDriven/Login_ReadDataUsingJson.java b/src/test/java/webPractice/dataDriven/Login_ReadDataUsingJson.java index b033fc8..e34b8f0 100644 --- a/src/test/java/webPractice/dataDriven/Login_ReadDataUsingJson.java +++ b/src/test/java/webPractice/dataDriven/Login_ReadDataUsingJson.java @@ -1,6 +1,5 @@ package webPractice.dataDriven; -import com.engine.dataDriven.CSVFileManager; import com.engine.reports.CustomReporter; import practice.gui.pages.homePage.HomePage; import practice.gui.pages.inputs.SecureAreaPage; @@ -15,8 +14,6 @@ import org.testng.annotations.Test; import java.io.IOException; -import java.util.Collections; -import java.util.List; import static com.engine.dataDriven.CSVFileManager.*; import static com.engine.dataDriven.TextFileManager.*; @@ -70,7 +67,7 @@ public void testSuccessfulLogin2() throws IOException { // CustomReporter.logConsole("text: " + text); //readDataLineByLine("src/test/resources/TestData/CSVFile.csv"); //compareTwoCSVFiles(filePath, filePath2); - compareTwoCSVFiles2(filePath, filePath2); + compareTwoCSVFilesByValue(filePath, filePath2); } private WebDriver driver; diff --git a/src/test/java/webPractice/downloadFile/DownloadFile.java b/src/test/java/webPractice/downloadFile/DownloadFile.java index bda2874..b572f17 100644 --- a/src/test/java/webPractice/downloadFile/DownloadFile.java +++ b/src/test/java/webPractice/downloadFile/DownloadFile.java @@ -5,6 +5,8 @@ import com.engine.actions.ElementActions; import com.engine.actions.FileActions; +import com.engine.driver.DriverFactory; +import com.engine.driver.DriverHelper; import io.github.bonigarcia.wdm.WebDriverManager; import org.jetbrains.annotations.NotNull; @@ -12,22 +14,12 @@ import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; -import org.openqa.selenium.support.ui.Wait; -import org.testng.Assert; import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import practice.gui.pages.homePage.HomePage; -import practice.gui.pages.uploadFilePage.FileUploadPage; -import java.io.IOException; -import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; -import static com.engine.constants.FrameworkConstants.TIMEOUT_EXPLICIT; -import static com.engine.dataDriven.CSVFileManager.compareTwoCSVFiles2; - public class DownloadFile { private WebDriver driver; @@ -48,18 +40,11 @@ public class DownloadFile { @Test public void test2() throws InterruptedException { - WebDriverManager.chromedriver().setup(); - ChromeOptions options = new ChromeOptions(); - Map prefs = getStringObjectMap(downloadFilepath); - options.setExperimentalOption("prefs", prefs); - options.addArguments("--headless"); - options.addArguments("--start-maximized"); - driver = new ChromeDriver(options); - Waits.implicitWait(driver, 30); + driver = DriverFactory.getBrowser(DriverHelper.BrowserType.CHROME); BrowserActions.navigateToUrl(driver, "https://the-internet.herokuapp.com/download"); - ElementActions.click(driver, By.linkText("testfile2.txt")); + ElementActions.click(driver, By.linkText("sample.pdf")); Thread.sleep(10000); - FileActions.getInstance().doesFileExist(downloadFilepath + "testfile2.txt"); + FileActions.getInstance().doesFileExist(downloadFilepath + "sample.pdf"); } @Test public void test3() throws InterruptedException { @@ -75,6 +60,11 @@ public void test3() throws InterruptedException { Thread.sleep(10000); } + @Test + public void test4() throws InterruptedException { + + } + // @Test // public void tes4() throws IOException { // String imageName = "CSVFile.csv"; diff --git a/src/test/java/webPractice/filLoginForm/Login_Test.java b/src/test/java/webPractice/elementActions/Login_Test.java similarity index 74% rename from src/test/java/webPractice/filLoginForm/Login_Test.java rename to src/test/java/webPractice/elementActions/Login_Test.java index 820d61e..253490e 100644 --- a/src/test/java/webPractice/filLoginForm/Login_Test.java +++ b/src/test/java/webPractice/elementActions/Login_Test.java @@ -1,4 +1,4 @@ -package webPractice.filLoginForm; +package webPractice.elementActions; import practice.gui.pages.homePage.HomePage; import practice.gui.pages.inputs.SecureAreaPage; @@ -21,8 +21,7 @@ public class Login_Test { @BeforeMethod public void setup_BeforeMethod() { driver = DriverFactory.getBrowser(); - testDataFile = new ExcelFileManager("src/test/resources/TestData/TestData.xlsx"); - testDataFile.switchToSheet("login"); + testDataFile = new ExcelFileManager("src/test/resources/TestData/TestData.xlsx", "login"); } @AfterMethod(enabled = false) @@ -32,10 +31,9 @@ public void closeBrowser() { @Test public void testSuccessfulLogin() { - String email = testDataFile.getCellData(2, "email"); - String password = testDataFile.getCellData(2, "password"); - String expectedResult_successMessage = testDataFile.getCellData(2, "expectedResult_successMessage"); - + String email = ExcelFileManager.getCellData(2, "email"); + String password = ExcelFileManager.getCellData(2, "password"); + String expectedResult_successMessage = ExcelFileManager.getCellData(2, "expectedResult_successMessage"); new HomePage(driver).navigateToHomePage() .clickFormAuthentication() .setUsername(email) diff --git a/src/test/java/webPractice/elementActions/interactions/HandleHTMLCanvas.java b/src/test/java/webPractice/elementActions/interactions/HandleHTMLCanvas.java new file mode 100644 index 0000000..323b60f --- /dev/null +++ b/src/test/java/webPractice/elementActions/interactions/HandleHTMLCanvas.java @@ -0,0 +1,82 @@ +package webPractice.elementActions.interactions; + + +import com.engine.actions.ElementActions; +import io.qameta.allure.*; +import org.openqa.selenium.*; +import org.openqa.selenium.interactions.Actions; +import org.openqa.selenium.support.ui.Select; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import com.engine.actions.BrowserActions; +import com.engine.driver.DriverFactory; + +import java.io.IOException; + + +public class HandleHTMLCanvas { + private WebDriver driver; + + @BeforeMethod + public void setUp_BeforeMethod() { + driver = DriverFactory.getBrowser(); + } + + @AfterMethod(enabled = false) + public void closeBrowser() { + BrowserActions.closeAllOpenedBrowserWindows(driver); + } + + + /* + * Using Actions Class to perform click and hold to draw something in canvas Area + * + */ + @Test + @Severity( SeverityLevel.CRITICAL ) + @Description( "draw in canvas Test Case" ) + @Epic( "Selenium Actions on Elements" ) + @Story( "Actions Tutorial" ) + public void TestHTMLCanvas() throws IOException { + driver.get("https://cookbook.seleniumacademy.com/html5canvasdraw.html"); + WebElement drawList = driver.findElement(By.id("dtool")); + WebElement canvas = driver.findElement(By.id("imageTemp")); + Select drawTool = new Select(drawList); + drawTool.selectByValue("pencil"); + Actions builder = new Actions(driver); + builder.clickAndHold(canvas) + .moveByOffset(5, 60) + .moveByOffset(60, 5) + .moveByOffset(-5, -60) + .moveByOffset(-60, -5).release().perform(); + } + + @Test + @Severity(SeverityLevel.CRITICAL) + @Description("draw in canvas Test Case") + @Epic("Selenium Actions on Elements") + @Story("Actions Tutorial") + public void TestHTMLCanvas2() throws IOException { + driver.get("https://cookbook.seleniumacademy.com/html5canvasdraw.html"); + WebElement drawList = driver.findElement(By.id("dtool")); + By canvas = By.id("imageTemp"); + Select drawTool = new Select(drawList); + drawTool.selectByValue("pencil"); + ElementActions.drawUsingJavaScript(driver, canvas); + } + + @Test + @Severity(SeverityLevel.CRITICAL) + @Description("draw in canvas Test Case") + @Epic("Selenium Actions on Elements") + @Story("Actions Tutorial") + public void TestHTMLCanvas3() throws IOException { + driver.get("https://cookbook.seleniumacademy.com/html5canvasdraw.html"); + WebElement drawList = driver.findElement(By.id("dtool")); + By canvas = By.id("imageTemp"); + Select drawTool = new Select(drawList); + drawTool.selectByValue("pencil"); + ElementActions.drawCircle(driver, canvas); + } +} diff --git a/src/test/java/webPractice/filLoginForm/RegisterTest.java b/src/test/java/webPractice/filLoginForm/RegisterTest.java deleted file mode 100644 index ae361b2..0000000 --- a/src/test/java/webPractice/filLoginForm/RegisterTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package webPractice.filLoginForm; - -import io.qameta.allure.*; -import org.testng.annotations.Test; - -public class RegisterTest { -// -// Faker faker = new Faker(); -// -// private final String emailValue = faker.internet().emailAddress(); -// private final String FirstName = faker.name().firstName(); -// private final String LastName = faker.name().lastName(); -// private final String passwordValue = faker.internet().password -// (8, 10, true); -// private final String AddressFirstName = faker.name().firstName(); -// private final String AddressLastName = faker.name().lastName(); -// private final String Company = faker.company().name(); -// private final String Address1 = faker.address().fullAddress(); -// private final String Address2 = faker.address().secondaryAddress(); -// private final String city = faker.address().cityName(); -// final String state = "Alabama"; -// final String postalCode = "12351"; -// final String AdditionalValue = "Whatever additional"; -// private final String mobilePhoneNum = faker.phoneNumber().cellPhone(); -// private final String alias = faker.funnyName().name(); - - - @Test - @Description( "Registration Testcase" ) - @Severity( SeverityLevel.CRITICAL ) - @Epic( "Registration" ) - @Story( "User Can Register Successfully" ) - public void UserCanRegisterSuccessfully() { - - } -} diff --git a/src/test/java/webPractice/javaScript/JavaScriptExecutorBrowserActionsTest.java b/src/test/java/webPractice/javaScript/JavaScriptExecutorBrowserActionsTest.java index 47d7624..a7113d9 100644 --- a/src/test/java/webPractice/javaScript/JavaScriptExecutorBrowserActionsTest.java +++ b/src/test/java/webPractice/javaScript/JavaScriptExecutorBrowserActionsTest.java @@ -22,9 +22,11 @@ public class JavaScriptExecutorBrowserActionsTest extends BaseTests { - Refresh the page """) public void getTitlePageAndRefresh () { - BrowserActions.navigateToUrl(driver, " https://the-internet.herokuapp.com/"); + BrowserActions.navigateToUrl(driver, "https://www.google.com/"); + BrowserActions.navigateToUrl(driver, "https://the-internet.herokuapp.com/"); String titlePage = ((JavascriptExecutor) driver).executeScript("return document.title;").toString(); System.out.println(currentTime + " The Url title Page is --> " + titlePage); + BrowserActions.goBackUsingJavascript(driver); ((JavascriptExecutor) driver).executeScript("history.go(0)"); } diff --git a/src/test/resources/__files/24848.xml b/src/test/resources/TestData/__files/24848.xml similarity index 97% rename from src/test/resources/__files/24848.xml rename to src/test/resources/TestData/__files/24848.xml index c2c76d4..d0d1649 100644 --- a/src/test/resources/__files/24848.xml +++ b/src/test/resources/TestData/__files/24848.xml @@ -1,28 +1,28 @@ - - - 24848 - Germany - DE - - - Alt Bennebek - Schleswig-Holstein - SH - - - Klein Rheide - Schleswig-Holstein - SH - - - Kropp - Schleswig-Holstein - SH - - - Klein Bennebek - Schleswig-Holstein - SH - - + + + 24848 + Germany + DE + + + Alt Bennebek + Schleswig-Holstein + SH + + + Klein Rheide + Schleswig-Holstein + SH + + + Kropp + Schleswig-Holstein + SH + + + Klein Bennebek + Schleswig-Holstein + SH + + \ No newline at end of file diff --git a/src/test/resources/__files/90210.xml b/src/test/resources/TestData/__files/90210.xml similarity index 96% rename from src/test/resources/__files/90210.xml rename to src/test/resources/TestData/__files/90210.xml index 8ddaf86..fa2203d 100644 --- a/src/test/resources/__files/90210.xml +++ b/src/test/resources/TestData/__files/90210.xml @@ -1,13 +1,13 @@ - - - 90210 - United States - US - - - Beverly Hills - California - CA - - + + + 90210 + United States + US + + + Beverly Hills + California + CA + + \ No newline at end of file diff --git a/src/test/resources/__files/albums.json b/src/test/resources/TestData/__files/albums.json similarity index 94% rename from src/test/resources/__files/albums.json rename to src/test/resources/TestData/__files/albums.json index 4e9f9fe..826d40b 100644 --- a/src/test/resources/__files/albums.json +++ b/src/test/resources/TestData/__files/albums.json @@ -1,502 +1,502 @@ -[ - { - "userId": 1, - "id": 1, - "title": "quidem molestiae enim" - }, - { - "userId": 1, - "id": 2, - "title": "sunt qui excepturi placeat culpa" - }, - { - "userId": 1, - "id": 3, - "title": "omnis laborum odio" - }, - { - "userId": 1, - "id": 4, - "title": "non esse culpa molestiae omnis sed optio" - }, - { - "userId": 1, - "id": 5, - "title": "eaque aut omnis a" - }, - { - "userId": 1, - "id": 6, - "title": "natus impedit quibusdam illo est" - }, - { - "userId": 1, - "id": 7, - "title": "quibusdam autem aliquid et et quia" - }, - { - "userId": 1, - "id": 8, - "title": "qui fuga est a eum" - }, - { - "userId": 1, - "id": 9, - "title": "saepe unde necessitatibus rem" - }, - { - "userId": 1, - "id": 10, - "title": "distinctio laborum qui" - }, - { - "userId": 2, - "id": 11, - "title": "quam nostrum impedit mollitia quod et dolor" - }, - { - "userId": 2, - "id": 12, - "title": "consequatur autem doloribus natus consectetur" - }, - { - "userId": 2, - "id": 13, - "title": "ab rerum non rerum consequatur ut ea unde" - }, - { - "userId": 2, - "id": 14, - "title": "ducimus molestias eos animi atque nihil" - }, - { - "userId": 2, - "id": 15, - "title": "ut pariatur rerum ipsum natus repellendus praesentium" - }, - { - "userId": 2, - "id": 16, - "title": "voluptatem aut maxime inventore autem magnam atque repellat" - }, - { - "userId": 2, - "id": 17, - "title": "aut minima voluptatem ut velit" - }, - { - "userId": 2, - "id": 18, - "title": "nesciunt quia et doloremque" - }, - { - "userId": 2, - "id": 19, - "title": "velit pariatur quaerat similique libero omnis quia" - }, - { - "userId": 2, - "id": 20, - "title": "voluptas rerum iure ut enim" - }, - { - "userId": 3, - "id": 21, - "title": "repudiandae voluptatem optio est consequatur rem in temporibus et" - }, - { - "userId": 3, - "id": 22, - "title": "et rem non provident vel ut" - }, - { - "userId": 3, - "id": 23, - "title": "incidunt quisquam hic adipisci sequi" - }, - { - "userId": 3, - "id": 24, - "title": "dolores ut et facere placeat" - }, - { - "userId": 3, - "id": 25, - "title": "vero maxime id possimus sunt neque et consequatur" - }, - { - "userId": 3, - "id": 26, - "title": "quibusdam saepe ipsa vel harum" - }, - { - "userId": 3, - "id": 27, - "title": "id non nostrum expedita" - }, - { - "userId": 3, - "id": 28, - "title": "omnis neque exercitationem sed dolor atque maxime aut cum" - }, - { - "userId": 3, - "id": 29, - "title": "inventore ut quasi magnam itaque est fugit" - }, - { - "userId": 3, - "id": 30, - "title": "tempora assumenda et similique odit distinctio error" - }, - { - "userId": 4, - "id": 31, - "title": "adipisci laborum fuga laboriosam" - }, - { - "userId": 4, - "id": 32, - "title": "reiciendis dolores a ut qui debitis non quo labore" - }, - { - "userId": 4, - "id": 33, - "title": "iste eos nostrum" - }, - { - "userId": 4, - "id": 34, - "title": "cumque voluptatibus rerum architecto blanditiis" - }, - { - "userId": 4, - "id": 35, - "title": "et impedit nisi quae magni necessitatibus sed aut pariatur" - }, - { - "userId": 4, - "id": 36, - "title": "nihil cupiditate voluptate neque" - }, - { - "userId": 4, - "id": 37, - "title": "est placeat dicta ut nisi rerum iste" - }, - { - "userId": 4, - "id": 38, - "title": "unde a sequi id" - }, - { - "userId": 4, - "id": 39, - "title": "ratione porro illum labore eum aperiam sed" - }, - { - "userId": 4, - "id": 40, - "title": "voluptas neque et sint aut quo odit" - }, - { - "userId": 5, - "id": 41, - "title": "ea voluptates maiores eos accusantium officiis tempore mollitia consequatur" - }, - { - "userId": 5, - "id": 42, - "title": "tenetur explicabo ea" - }, - { - "userId": 5, - "id": 43, - "title": "aperiam doloremque nihil" - }, - { - "userId": 5, - "id": 44, - "title": "sapiente cum numquam officia consequatur vel natus quos suscipit" - }, - { - "userId": 5, - "id": 45, - "title": "tenetur quos ea unde est enim corrupti qui" - }, - { - "userId": 5, - "id": 46, - "title": "molestiae voluptate non" - }, - { - "userId": 5, - "id": 47, - "title": "temporibus molestiae aut" - }, - { - "userId": 5, - "id": 48, - "title": "modi consequatur culpa aut quam soluta alias perspiciatis laudantium" - }, - { - "userId": 5, - "id": 49, - "title": "ut aut vero repudiandae voluptas ullam voluptas at consequatur" - }, - { - "userId": 5, - "id": 50, - "title": "sed qui sed quas sit ducimus dolor" - }, - { - "userId": 6, - "id": 51, - "title": "odit laboriosam sint quia cupiditate animi quis" - }, - { - "userId": 6, - "id": 52, - "title": "necessitatibus quas et sunt at voluptatem" - }, - { - "userId": 6, - "id": 53, - "title": "est vel sequi voluptatem nemo quam molestiae modi enim" - }, - { - "userId": 6, - "id": 54, - "title": "aut non illo amet perferendis" - }, - { - "userId": 6, - "id": 55, - "title": "qui culpa itaque omnis in nesciunt architecto error" - }, - { - "userId": 6, - "id": 56, - "title": "omnis qui maiores tempora officiis omnis rerum sed repellat" - }, - { - "userId": 6, - "id": 57, - "title": "libero excepturi voluptatem est architecto quae voluptatum officia tempora" - }, - { - "userId": 6, - "id": 58, - "title": "nulla illo consequatur aspernatur veritatis aut error delectus et" - }, - { - "userId": 6, - "id": 59, - "title": "eligendi similique provident nihil" - }, - { - "userId": 6, - "id": 60, - "title": "omnis mollitia sunt aliquid eum consequatur fugit minus laudantium" - }, - { - "userId": 7, - "id": 61, - "title": "delectus iusto et" - }, - { - "userId": 7, - "id": 62, - "title": "eos ea non recusandae iste ut quasi" - }, - { - "userId": 7, - "id": 63, - "title": "velit est quam" - }, - { - "userId": 7, - "id": 64, - "title": "autem voluptatem amet iure quae" - }, - { - "userId": 7, - "id": 65, - "title": "voluptates delectus iure iste qui" - }, - { - "userId": 7, - "id": 66, - "title": "velit sed quia dolor dolores delectus" - }, - { - "userId": 7, - "id": 67, - "title": "ad voluptas nostrum et nihil" - }, - { - "userId": 7, - "id": 68, - "title": "qui quasi nihil aut voluptatum sit dolore minima" - }, - { - "userId": 7, - "id": 69, - "title": "qui aut est" - }, - { - "userId": 7, - "id": 70, - "title": "et deleniti unde" - }, - { - "userId": 8, - "id": 71, - "title": "et vel corporis" - }, - { - "userId": 8, - "id": 72, - "title": "unde exercitationem ut" - }, - { - "userId": 8, - "id": 73, - "title": "quos omnis officia" - }, - { - "userId": 8, - "id": 74, - "title": "quia est eius vitae dolor" - }, - { - "userId": 8, - "id": 75, - "title": "aut quia expedita non" - }, - { - "userId": 8, - "id": 76, - "title": "dolorem magnam facere itaque ut reprehenderit tenetur corrupti" - }, - { - "userId": 8, - "id": 77, - "title": "cupiditate sapiente maiores iusto ducimus cum excepturi veritatis quia" - }, - { - "userId": 8, - "id": 78, - "title": "est minima eius possimus ea ratione velit et" - }, - { - "userId": 8, - "id": 79, - "title": "ipsa quae voluptas natus ut suscipit soluta quia quidem" - }, - { - "userId": 8, - "id": 80, - "title": "id nihil reprehenderit" - }, - { - "userId": 9, - "id": 81, - "title": "quibusdam sapiente et" - }, - { - "userId": 9, - "id": 82, - "title": "recusandae consequatur vel amet unde" - }, - { - "userId": 9, - "id": 83, - "title": "aperiam odio fugiat" - }, - { - "userId": 9, - "id": 84, - "title": "est et at eos expedita" - }, - { - "userId": 9, - "id": 85, - "title": "qui voluptatem consequatur aut ab quis temporibus praesentium" - }, - { - "userId": 9, - "id": 86, - "title": "eligendi mollitia alias aspernatur vel ut iusto" - }, - { - "userId": 9, - "id": 87, - "title": "aut aut architecto" - }, - { - "userId": 9, - "id": 88, - "title": "quas perspiciatis optio" - }, - { - "userId": 9, - "id": 89, - "title": "sit optio id voluptatem est eum et" - }, - { - "userId": 9, - "id": 90, - "title": "est vel dignissimos" - }, - { - "userId": 10, - "id": 91, - "title": "repellendus praesentium debitis officiis" - }, - { - "userId": 10, - "id": 92, - "title": "incidunt et et eligendi assumenda soluta quia recusandae" - }, - { - "userId": 10, - "id": 93, - "title": "nisi qui dolores perspiciatis" - }, - { - "userId": 10, - "id": 94, - "title": "quisquam a dolores et earum vitae" - }, - { - "userId": 10, - "id": 95, - "title": "consectetur vel rerum qui aperiam modi eos aspernatur ipsa" - }, - { - "userId": 10, - "id": 96, - "title": "unde et ut molestiae est molestias voluptatem sint" - }, - { - "userId": 10, - "id": 97, - "title": "est quod aut" - }, - { - "userId": 10, - "id": 98, - "title": "omnis quia possimus nesciunt deleniti assumenda sed autem" - }, - { - "userId": 10, - "id": 99, - "title": "consectetur ut id impedit dolores sit ad ex aut" - }, - { - "userId": 10, - "id": 100, - "title": "enim repellat iste" - } +[ + { + "userId": 1, + "id": 1, + "title": "quidem molestiae enim" + }, + { + "userId": 1, + "id": 2, + "title": "sunt qui excepturi placeat culpa" + }, + { + "userId": 1, + "id": 3, + "title": "omnis laborum odio" + }, + { + "userId": 1, + "id": 4, + "title": "non esse culpa molestiae omnis sed optio" + }, + { + "userId": 1, + "id": 5, + "title": "eaque aut omnis a" + }, + { + "userId": 1, + "id": 6, + "title": "natus impedit quibusdam illo est" + }, + { + "userId": 1, + "id": 7, + "title": "quibusdam autem aliquid et et quia" + }, + { + "userId": 1, + "id": 8, + "title": "qui fuga est a eum" + }, + { + "userId": 1, + "id": 9, + "title": "saepe unde necessitatibus rem" + }, + { + "userId": 1, + "id": 10, + "title": "distinctio laborum qui" + }, + { + "userId": 2, + "id": 11, + "title": "quam nostrum impedit mollitia quod et dolor" + }, + { + "userId": 2, + "id": 12, + "title": "consequatur autem doloribus natus consectetur" + }, + { + "userId": 2, + "id": 13, + "title": "ab rerum non rerum consequatur ut ea unde" + }, + { + "userId": 2, + "id": 14, + "title": "ducimus molestias eos animi atque nihil" + }, + { + "userId": 2, + "id": 15, + "title": "ut pariatur rerum ipsum natus repellendus praesentium" + }, + { + "userId": 2, + "id": 16, + "title": "voluptatem aut maxime inventore autem magnam atque repellat" + }, + { + "userId": 2, + "id": 17, + "title": "aut minima voluptatem ut velit" + }, + { + "userId": 2, + "id": 18, + "title": "nesciunt quia et doloremque" + }, + { + "userId": 2, + "id": 19, + "title": "velit pariatur quaerat similique libero omnis quia" + }, + { + "userId": 2, + "id": 20, + "title": "voluptas rerum iure ut enim" + }, + { + "userId": 3, + "id": 21, + "title": "repudiandae voluptatem optio est consequatur rem in temporibus et" + }, + { + "userId": 3, + "id": 22, + "title": "et rem non provident vel ut" + }, + { + "userId": 3, + "id": 23, + "title": "incidunt quisquam hic adipisci sequi" + }, + { + "userId": 3, + "id": 24, + "title": "dolores ut et facere placeat" + }, + { + "userId": 3, + "id": 25, + "title": "vero maxime id possimus sunt neque et consequatur" + }, + { + "userId": 3, + "id": 26, + "title": "quibusdam saepe ipsa vel harum" + }, + { + "userId": 3, + "id": 27, + "title": "id non nostrum expedita" + }, + { + "userId": 3, + "id": 28, + "title": "omnis neque exercitationem sed dolor atque maxime aut cum" + }, + { + "userId": 3, + "id": 29, + "title": "inventore ut quasi magnam itaque est fugit" + }, + { + "userId": 3, + "id": 30, + "title": "tempora assumenda et similique odit distinctio error" + }, + { + "userId": 4, + "id": 31, + "title": "adipisci laborum fuga laboriosam" + }, + { + "userId": 4, + "id": 32, + "title": "reiciendis dolores a ut qui debitis non quo labore" + }, + { + "userId": 4, + "id": 33, + "title": "iste eos nostrum" + }, + { + "userId": 4, + "id": 34, + "title": "cumque voluptatibus rerum architecto blanditiis" + }, + { + "userId": 4, + "id": 35, + "title": "et impedit nisi quae magni necessitatibus sed aut pariatur" + }, + { + "userId": 4, + "id": 36, + "title": "nihil cupiditate voluptate neque" + }, + { + "userId": 4, + "id": 37, + "title": "est placeat dicta ut nisi rerum iste" + }, + { + "userId": 4, + "id": 38, + "title": "unde a sequi id" + }, + { + "userId": 4, + "id": 39, + "title": "ratione porro illum labore eum aperiam sed" + }, + { + "userId": 4, + "id": 40, + "title": "voluptas neque et sint aut quo odit" + }, + { + "userId": 5, + "id": 41, + "title": "ea voluptates maiores eos accusantium officiis tempore mollitia consequatur" + }, + { + "userId": 5, + "id": 42, + "title": "tenetur explicabo ea" + }, + { + "userId": 5, + "id": 43, + "title": "aperiam doloremque nihil" + }, + { + "userId": 5, + "id": 44, + "title": "sapiente cum numquam officia consequatur vel natus quos suscipit" + }, + { + "userId": 5, + "id": 45, + "title": "tenetur quos ea unde est enim corrupti qui" + }, + { + "userId": 5, + "id": 46, + "title": "molestiae voluptate non" + }, + { + "userId": 5, + "id": 47, + "title": "temporibus molestiae aut" + }, + { + "userId": 5, + "id": 48, + "title": "modi consequatur culpa aut quam soluta alias perspiciatis laudantium" + }, + { + "userId": 5, + "id": 49, + "title": "ut aut vero repudiandae voluptas ullam voluptas at consequatur" + }, + { + "userId": 5, + "id": 50, + "title": "sed qui sed quas sit ducimus dolor" + }, + { + "userId": 6, + "id": 51, + "title": "odit laboriosam sint quia cupiditate animi quis" + }, + { + "userId": 6, + "id": 52, + "title": "necessitatibus quas et sunt at voluptatem" + }, + { + "userId": 6, + "id": 53, + "title": "est vel sequi voluptatem nemo quam molestiae modi enim" + }, + { + "userId": 6, + "id": 54, + "title": "aut non illo amet perferendis" + }, + { + "userId": 6, + "id": 55, + "title": "qui culpa itaque omnis in nesciunt architecto error" + }, + { + "userId": 6, + "id": 56, + "title": "omnis qui maiores tempora officiis omnis rerum sed repellat" + }, + { + "userId": 6, + "id": 57, + "title": "libero excepturi voluptatem est architecto quae voluptatum officia tempora" + }, + { + "userId": 6, + "id": 58, + "title": "nulla illo consequatur aspernatur veritatis aut error delectus et" + }, + { + "userId": 6, + "id": 59, + "title": "eligendi similique provident nihil" + }, + { + "userId": 6, + "id": 60, + "title": "omnis mollitia sunt aliquid eum consequatur fugit minus laudantium" + }, + { + "userId": 7, + "id": 61, + "title": "delectus iusto et" + }, + { + "userId": 7, + "id": 62, + "title": "eos ea non recusandae iste ut quasi" + }, + { + "userId": 7, + "id": 63, + "title": "velit est quam" + }, + { + "userId": 7, + "id": 64, + "title": "autem voluptatem amet iure quae" + }, + { + "userId": 7, + "id": 65, + "title": "voluptates delectus iure iste qui" + }, + { + "userId": 7, + "id": 66, + "title": "velit sed quia dolor dolores delectus" + }, + { + "userId": 7, + "id": 67, + "title": "ad voluptas nostrum et nihil" + }, + { + "userId": 7, + "id": 68, + "title": "qui quasi nihil aut voluptatum sit dolore minima" + }, + { + "userId": 7, + "id": 69, + "title": "qui aut est" + }, + { + "userId": 7, + "id": 70, + "title": "et deleniti unde" + }, + { + "userId": 8, + "id": 71, + "title": "et vel corporis" + }, + { + "userId": 8, + "id": 72, + "title": "unde exercitationem ut" + }, + { + "userId": 8, + "id": 73, + "title": "quos omnis officia" + }, + { + "userId": 8, + "id": 74, + "title": "quia est eius vitae dolor" + }, + { + "userId": 8, + "id": 75, + "title": "aut quia expedita non" + }, + { + "userId": 8, + "id": 76, + "title": "dolorem magnam facere itaque ut reprehenderit tenetur corrupti" + }, + { + "userId": 8, + "id": 77, + "title": "cupiditate sapiente maiores iusto ducimus cum excepturi veritatis quia" + }, + { + "userId": 8, + "id": 78, + "title": "est minima eius possimus ea ratione velit et" + }, + { + "userId": 8, + "id": 79, + "title": "ipsa quae voluptas natus ut suscipit soluta quia quidem" + }, + { + "userId": 8, + "id": 80, + "title": "id nihil reprehenderit" + }, + { + "userId": 9, + "id": 81, + "title": "quibusdam sapiente et" + }, + { + "userId": 9, + "id": 82, + "title": "recusandae consequatur vel amet unde" + }, + { + "userId": 9, + "id": 83, + "title": "aperiam odio fugiat" + }, + { + "userId": 9, + "id": 84, + "title": "est et at eos expedita" + }, + { + "userId": 9, + "id": 85, + "title": "qui voluptatem consequatur aut ab quis temporibus praesentium" + }, + { + "userId": 9, + "id": 86, + "title": "eligendi mollitia alias aspernatur vel ut iusto" + }, + { + "userId": 9, + "id": 87, + "title": "aut aut architecto" + }, + { + "userId": 9, + "id": 88, + "title": "quas perspiciatis optio" + }, + { + "userId": 9, + "id": 89, + "title": "sit optio id voluptatem est eum et" + }, + { + "userId": 9, + "id": 90, + "title": "est vel dignissimos" + }, + { + "userId": 10, + "id": 91, + "title": "repellendus praesentium debitis officiis" + }, + { + "userId": 10, + "id": 92, + "title": "incidunt et et eligendi assumenda soluta quia recusandae" + }, + { + "userId": 10, + "id": 93, + "title": "nisi qui dolores perspiciatis" + }, + { + "userId": 10, + "id": 94, + "title": "quisquam a dolores et earum vitae" + }, + { + "userId": 10, + "id": 95, + "title": "consectetur vel rerum qui aperiam modi eos aspernatur ipsa" + }, + { + "userId": 10, + "id": 96, + "title": "unde et ut molestiae est molestias voluptatem sint" + }, + { + "userId": 10, + "id": 97, + "title": "est quod aut" + }, + { + "userId": 10, + "id": 98, + "title": "omnis quia possimus nesciunt deleniti assumenda sed autem" + }, + { + "userId": 10, + "id": 99, + "title": "consectetur ut id impedit dolores sit ad ex aut" + }, + { + "userId": 10, + "id": 100, + "title": "enim repellat iste" + } ] \ No newline at end of file diff --git a/src/test/resources/__files/cars.xml b/src/test/resources/TestData/__files/cars.xml similarity index 95% rename from src/test/resources/__files/cars.xml rename to src/test/resources/TestData/__files/cars.xml index 442d440..f5712c3 100644 --- a/src/test/resources/__files/cars.xml +++ b/src/test/resources/TestData/__files/cars.xml @@ -1,16 +1,16 @@ - - - - Italy - 2016 - - - UK - 1949 - - - Japan - 2012 - - - + + + + Italy + 2016 + + + UK + 1949 + + + Japan + 2012 + + + diff --git a/src/test/resources/__files/photosforalbum.json b/src/test/resources/TestData/__files/photosforalbum.json similarity index 96% rename from src/test/resources/__files/photosforalbum.json rename to src/test/resources/TestData/__files/photosforalbum.json index ca5492c..7fb5add 100644 --- a/src/test/resources/__files/photosforalbum.json +++ b/src/test/resources/TestData/__files/photosforalbum.json @@ -1,352 +1,352 @@ -[ - { - "albumId": 35, - "id": 1701, - "title": "est voluptatibus corporis modi est", - "url": "https://via.placeholder.com/600/443482", - "thumbnailUrl": "https://via.placeholder.com/150/443482" - }, - { - "albumId": 35, - "id": 1702, - "title": "voluptatem ut nulla", - "url": "https://via.placeholder.com/600/96324f", - "thumbnailUrl": "https://via.placeholder.com/150/96324f" - }, - { - "albumId": 35, - "id": 1703, - "title": "iste molestiae et non sint", - "url": "https://via.placeholder.com/600/e8322", - "thumbnailUrl": "https://via.placeholder.com/150/e8322" - }, - { - "albumId": 35, - "id": 1704, - "title": "voluptate cum fugit", - "url": "https://via.placeholder.com/600/c701dd", - "thumbnailUrl": "https://via.placeholder.com/150/c701dd" - }, - { - "albumId": 35, - "id": 1705, - "title": "tenetur itaque omnis est excepturi", - "url": "https://via.placeholder.com/600/7cce1c", - "thumbnailUrl": "https://via.placeholder.com/150/7cce1c" - }, - { - "albumId": 35, - "id": 1706, - "title": "est qui beatae debitis rerum dolore", - "url": "https://via.placeholder.com/600/4771f1", - "thumbnailUrl": "https://via.placeholder.com/150/4771f1" - }, - { - "albumId": 35, - "id": 1707, - "title": "sed quidem qui culpa enim", - "url": "https://via.placeholder.com/600/f9e3b5", - "thumbnailUrl": "https://via.placeholder.com/150/f9e3b5" - }, - { - "albumId": 35, - "id": 1708, - "title": "consequatur laudantium porro facilis earum quia vero quo", - "url": "https://via.placeholder.com/600/e8dd61", - "thumbnailUrl": "https://via.placeholder.com/150/e8dd61" - }, - { - "albumId": 35, - "id": 1709, - "title": "qui quo corrupti consequatur accusamus occaecati", - "url": "https://via.placeholder.com/600/be458b", - "thumbnailUrl": "https://via.placeholder.com/150/be458b" - }, - { - "albumId": 35, - "id": 1710, - "title": "molestiae harum aut", - "url": "https://via.placeholder.com/600/4e0df", - "thumbnailUrl": "https://via.placeholder.com/150/4e0df" - }, - { - "albumId": 35, - "id": 1711, - "title": "suscipit veniam id", - "url": "https://via.placeholder.com/600/3d4ccc", - "thumbnailUrl": "https://via.placeholder.com/150/3d4ccc" - }, - { - "albumId": 35, - "id": 1712, - "title": "nostrum maxime sed sunt accusamus qui vel", - "url": "https://via.placeholder.com/600/b881d4", - "thumbnailUrl": "https://via.placeholder.com/150/b881d4" - }, - { - "albumId": 35, - "id": 1713, - "title": "nemo doloremque itaque quis ad id", - "url": "https://via.placeholder.com/600/8f2cdc", - "thumbnailUrl": "https://via.placeholder.com/150/8f2cdc" - }, - { - "albumId": 35, - "id": 1714, - "title": "veniam autem deserunt et id explicabo vel ut", - "url": "https://via.placeholder.com/600/7e0946", - "thumbnailUrl": "https://via.placeholder.com/150/7e0946" - }, - { - "albumId": 35, - "id": 1715, - "title": "veritatis eligendi voluptatem optio enim libero unde rerum", - "url": "https://via.placeholder.com/600/71d928", - "thumbnailUrl": "https://via.placeholder.com/150/71d928" - }, - { - "albumId": 35, - "id": 1716, - "title": "libero perspiciatis excepturi ullam et", - "url": "https://via.placeholder.com/600/6b3985", - "thumbnailUrl": "https://via.placeholder.com/150/6b3985" - }, - { - "albumId": 35, - "id": 1717, - "title": "exercitationem sunt eum qui quibusdam non dolores et reiciendis", - "url": "https://via.placeholder.com/600/ff2e53", - "thumbnailUrl": "https://via.placeholder.com/150/ff2e53" - }, - { - "albumId": 35, - "id": 1718, - "title": "voluptate dolorem est", - "url": "https://via.placeholder.com/600/43166d", - "thumbnailUrl": "https://via.placeholder.com/150/43166d" - }, - { - "albumId": 35, - "id": 1719, - "title": "enim quis nostrum consectetur laborum numquam", - "url": "https://via.placeholder.com/600/cd6e87", - "thumbnailUrl": "https://via.placeholder.com/150/cd6e87" - }, - { - "albumId": 35, - "id": 1720, - "title": "cum odit suscipit eaque est facilis qui nam beatae", - "url": "https://via.placeholder.com/600/22335c", - "thumbnailUrl": "https://via.placeholder.com/150/22335c" - }, - { - "albumId": 35, - "id": 1721, - "title": "numquam facere quia totam atque assumenda", - "url": "https://via.placeholder.com/600/5b5f93", - "thumbnailUrl": "https://via.placeholder.com/150/5b5f93" - }, - { - "albumId": 35, - "id": 1722, - "title": "ut pariatur qui asperiores similique", - "url": "https://via.placeholder.com/600/117d9e", - "thumbnailUrl": "https://via.placeholder.com/150/117d9e" - }, - { - "albumId": 35, - "id": 1723, - "title": "est qui voluptatum ad", - "url": "https://via.placeholder.com/600/9807ac", - "thumbnailUrl": "https://via.placeholder.com/150/9807ac" - }, - { - "albumId": 35, - "id": 1724, - "title": "reiciendis tempore minima voluptas sint dolores", - "url": "https://via.placeholder.com/600/dab44b", - "thumbnailUrl": "https://via.placeholder.com/150/dab44b" - }, - { - "albumId": 35, - "id": 1725, - "title": "et sit dolor laudantium illo voluptatibus similique saepe nesciunt", - "url": "https://via.placeholder.com/600/3d2e3d", - "thumbnailUrl": "https://via.placeholder.com/150/3d2e3d" - }, - { - "albumId": 35, - "id": 1726, - "title": "qui placeat et nemo molestiae", - "url": "https://via.placeholder.com/600/af8e83", - "thumbnailUrl": "https://via.placeholder.com/150/af8e83" - }, - { - "albumId": 35, - "id": 1727, - "title": "non in quia rerum fugiat commodi", - "url": "https://via.placeholder.com/600/43efff", - "thumbnailUrl": "https://via.placeholder.com/150/43efff" - }, - { - "albumId": 35, - "id": 1728, - "title": "non sint est", - "url": "https://via.placeholder.com/600/6e1979", - "thumbnailUrl": "https://via.placeholder.com/150/6e1979" - }, - { - "albumId": 35, - "id": 1729, - "title": "deserunt perferendis sed rerum", - "url": "https://via.placeholder.com/600/2a7fbf", - "thumbnailUrl": "https://via.placeholder.com/150/2a7fbf" - }, - { - "albumId": 35, - "id": 1730, - "title": "modi incidunt sed ut", - "url": "https://via.placeholder.com/600/2fb19c", - "thumbnailUrl": "https://via.placeholder.com/150/2fb19c" - }, - { - "albumId": 35, - "id": 1731, - "title": "ratione harum expedita nihil nesciunt laudantium et ut", - "url": "https://via.placeholder.com/600/f6bb1b", - "thumbnailUrl": "https://via.placeholder.com/150/f6bb1b" - }, - { - "albumId": 35, - "id": 1732, - "title": "pariatur sunt eveniet", - "url": "https://via.placeholder.com/600/400978", - "thumbnailUrl": "https://via.placeholder.com/150/400978" - }, - { - "albumId": 35, - "id": 1733, - "title": "rerum qui repellendus neque delectus", - "url": "https://via.placeholder.com/600/924b68", - "thumbnailUrl": "https://via.placeholder.com/150/924b68" - }, - { - "albumId": 35, - "id": 1734, - "title": "sapiente hic omnis libero", - "url": "https://via.placeholder.com/600/59c019", - "thumbnailUrl": "https://via.placeholder.com/150/59c019" - }, - { - "albumId": 35, - "id": 1735, - "title": "aut illum porro vel harum est exercitationem nam", - "url": "https://via.placeholder.com/600/ef2d7c", - "thumbnailUrl": "https://via.placeholder.com/150/ef2d7c" - }, - { - "albumId": 35, - "id": 1736, - "title": "dolorum eaque eos", - "url": "https://via.placeholder.com/600/73462e", - "thumbnailUrl": "https://via.placeholder.com/150/73462e" - }, - { - "albumId": 35, - "id": 1737, - "title": "facere sed eum aspernatur nulla", - "url": "https://via.placeholder.com/600/34fe75", - "thumbnailUrl": "https://via.placeholder.com/150/34fe75" - }, - { - "albumId": 35, - "id": 1738, - "title": "ratione quia cumque", - "url": "https://via.placeholder.com/600/3c9376", - "thumbnailUrl": "https://via.placeholder.com/150/3c9376" - }, - { - "albumId": 35, - "id": 1739, - "title": "aut consequuntur occaecati non doloribus laborum animi enim", - "url": "https://via.placeholder.com/600/b730f7", - "thumbnailUrl": "https://via.placeholder.com/150/b730f7" - }, - { - "albumId": 35, - "id": 1740, - "title": "quidem aperiam recusandae assumenda nostrum", - "url": "https://via.placeholder.com/600/a226ba", - "thumbnailUrl": "https://via.placeholder.com/150/a226ba" - }, - { - "albumId": 35, - "id": 1741, - "title": "atque est voluptatum accusamus natus deleniti", - "url": "https://via.placeholder.com/600/e42e20", - "thumbnailUrl": "https://via.placeholder.com/150/e42e20" - }, - { - "albumId": 35, - "id": 1742, - "title": "iste non iure harum a", - "url": "https://via.placeholder.com/600/3fae7", - "thumbnailUrl": "https://via.placeholder.com/150/3fae7" - }, - { - "albumId": 35, - "id": 1743, - "title": "non placeat sequi sed numquam rerum delectus minima", - "url": "https://via.placeholder.com/600/e92278", - "thumbnailUrl": "https://via.placeholder.com/150/e92278" - }, - { - "albumId": 35, - "id": 1744, - "title": "exercitationem hic maiores expedita quae quia", - "url": "https://via.placeholder.com/600/133ca0", - "thumbnailUrl": "https://via.placeholder.com/150/133ca0" - }, - { - "albumId": 35, - "id": 1745, - "title": "dolorum mollitia atque velit corporis", - "url": "https://via.placeholder.com/600/bf8826", - "thumbnailUrl": "https://via.placeholder.com/150/bf8826" - }, - { - "albumId": 35, - "id": 1746, - "title": "consequatur ea cupiditate qui officiis amet est officia magnam", - "url": "https://via.placeholder.com/600/570847", - "thumbnailUrl": "https://via.placeholder.com/150/570847" - }, - { - "albumId": 35, - "id": 1747, - "title": "sequi in aut nam voluptatem perferendis", - "url": "https://via.placeholder.com/600/fd2e85", - "thumbnailUrl": "https://via.placeholder.com/150/fd2e85" - }, - { - "albumId": 35, - "id": 1748, - "title": "sunt quo exercitationem molestias corporis et soluta odio", - "url": "https://via.placeholder.com/600/3b5fa3", - "thumbnailUrl": "https://via.placeholder.com/150/3b5fa3" - }, - { - "albumId": 35, - "id": 1749, - "title": "fuga asperiores qui alias", - "url": "https://via.placeholder.com/600/efb648", - "thumbnailUrl": "https://via.placeholder.com/150/efb648" - }, - { - "albumId": 35, - "id": 1750, - "title": "in totam veritatis itaque iusto eaque perspiciatis libero deleniti", - "url": "https://via.placeholder.com/600/31fdae", - "thumbnailUrl": "https://via.placeholder.com/150/31fdae" - } +[ + { + "albumId": 35, + "id": 1701, + "title": "est voluptatibus corporis modi est", + "url": "https://via.placeholder.com/600/443482", + "thumbnailUrl": "https://via.placeholder.com/150/443482" + }, + { + "albumId": 35, + "id": 1702, + "title": "voluptatem ut nulla", + "url": "https://via.placeholder.com/600/96324f", + "thumbnailUrl": "https://via.placeholder.com/150/96324f" + }, + { + "albumId": 35, + "id": 1703, + "title": "iste molestiae et non sint", + "url": "https://via.placeholder.com/600/e8322", + "thumbnailUrl": "https://via.placeholder.com/150/e8322" + }, + { + "albumId": 35, + "id": 1704, + "title": "voluptate cum fugit", + "url": "https://via.placeholder.com/600/c701dd", + "thumbnailUrl": "https://via.placeholder.com/150/c701dd" + }, + { + "albumId": 35, + "id": 1705, + "title": "tenetur itaque omnis est excepturi", + "url": "https://via.placeholder.com/600/7cce1c", + "thumbnailUrl": "https://via.placeholder.com/150/7cce1c" + }, + { + "albumId": 35, + "id": 1706, + "title": "est qui beatae debitis rerum dolore", + "url": "https://via.placeholder.com/600/4771f1", + "thumbnailUrl": "https://via.placeholder.com/150/4771f1" + }, + { + "albumId": 35, + "id": 1707, + "title": "sed quidem qui culpa enim", + "url": "https://via.placeholder.com/600/f9e3b5", + "thumbnailUrl": "https://via.placeholder.com/150/f9e3b5" + }, + { + "albumId": 35, + "id": 1708, + "title": "consequatur laudantium porro facilis earum quia vero quo", + "url": "https://via.placeholder.com/600/e8dd61", + "thumbnailUrl": "https://via.placeholder.com/150/e8dd61" + }, + { + "albumId": 35, + "id": 1709, + "title": "qui quo corrupti consequatur accusamus occaecati", + "url": "https://via.placeholder.com/600/be458b", + "thumbnailUrl": "https://via.placeholder.com/150/be458b" + }, + { + "albumId": 35, + "id": 1710, + "title": "molestiae harum aut", + "url": "https://via.placeholder.com/600/4e0df", + "thumbnailUrl": "https://via.placeholder.com/150/4e0df" + }, + { + "albumId": 35, + "id": 1711, + "title": "suscipit veniam id", + "url": "https://via.placeholder.com/600/3d4ccc", + "thumbnailUrl": "https://via.placeholder.com/150/3d4ccc" + }, + { + "albumId": 35, + "id": 1712, + "title": "nostrum maxime sed sunt accusamus qui vel", + "url": "https://via.placeholder.com/600/b881d4", + "thumbnailUrl": "https://via.placeholder.com/150/b881d4" + }, + { + "albumId": 35, + "id": 1713, + "title": "nemo doloremque itaque quis ad id", + "url": "https://via.placeholder.com/600/8f2cdc", + "thumbnailUrl": "https://via.placeholder.com/150/8f2cdc" + }, + { + "albumId": 35, + "id": 1714, + "title": "veniam autem deserunt et id explicabo vel ut", + "url": "https://via.placeholder.com/600/7e0946", + "thumbnailUrl": "https://via.placeholder.com/150/7e0946" + }, + { + "albumId": 35, + "id": 1715, + "title": "veritatis eligendi voluptatem optio enim libero unde rerum", + "url": "https://via.placeholder.com/600/71d928", + "thumbnailUrl": "https://via.placeholder.com/150/71d928" + }, + { + "albumId": 35, + "id": 1716, + "title": "libero perspiciatis excepturi ullam et", + "url": "https://via.placeholder.com/600/6b3985", + "thumbnailUrl": "https://via.placeholder.com/150/6b3985" + }, + { + "albumId": 35, + "id": 1717, + "title": "exercitationem sunt eum qui quibusdam non dolores et reiciendis", + "url": "https://via.placeholder.com/600/ff2e53", + "thumbnailUrl": "https://via.placeholder.com/150/ff2e53" + }, + { + "albumId": 35, + "id": 1718, + "title": "voluptate dolorem est", + "url": "https://via.placeholder.com/600/43166d", + "thumbnailUrl": "https://via.placeholder.com/150/43166d" + }, + { + "albumId": 35, + "id": 1719, + "title": "enim quis nostrum consectetur laborum numquam", + "url": "https://via.placeholder.com/600/cd6e87", + "thumbnailUrl": "https://via.placeholder.com/150/cd6e87" + }, + { + "albumId": 35, + "id": 1720, + "title": "cum odit suscipit eaque est facilis qui nam beatae", + "url": "https://via.placeholder.com/600/22335c", + "thumbnailUrl": "https://via.placeholder.com/150/22335c" + }, + { + "albumId": 35, + "id": 1721, + "title": "numquam facere quia totam atque assumenda", + "url": "https://via.placeholder.com/600/5b5f93", + "thumbnailUrl": "https://via.placeholder.com/150/5b5f93" + }, + { + "albumId": 35, + "id": 1722, + "title": "ut pariatur qui asperiores similique", + "url": "https://via.placeholder.com/600/117d9e", + "thumbnailUrl": "https://via.placeholder.com/150/117d9e" + }, + { + "albumId": 35, + "id": 1723, + "title": "est qui voluptatum ad", + "url": "https://via.placeholder.com/600/9807ac", + "thumbnailUrl": "https://via.placeholder.com/150/9807ac" + }, + { + "albumId": 35, + "id": 1724, + "title": "reiciendis tempore minima voluptas sint dolores", + "url": "https://via.placeholder.com/600/dab44b", + "thumbnailUrl": "https://via.placeholder.com/150/dab44b" + }, + { + "albumId": 35, + "id": 1725, + "title": "et sit dolor laudantium illo voluptatibus similique saepe nesciunt", + "url": "https://via.placeholder.com/600/3d2e3d", + "thumbnailUrl": "https://via.placeholder.com/150/3d2e3d" + }, + { + "albumId": 35, + "id": 1726, + "title": "qui placeat et nemo molestiae", + "url": "https://via.placeholder.com/600/af8e83", + "thumbnailUrl": "https://via.placeholder.com/150/af8e83" + }, + { + "albumId": 35, + "id": 1727, + "title": "non in quia rerum fugiat commodi", + "url": "https://via.placeholder.com/600/43efff", + "thumbnailUrl": "https://via.placeholder.com/150/43efff" + }, + { + "albumId": 35, + "id": 1728, + "title": "non sint est", + "url": "https://via.placeholder.com/600/6e1979", + "thumbnailUrl": "https://via.placeholder.com/150/6e1979" + }, + { + "albumId": 35, + "id": 1729, + "title": "deserunt perferendis sed rerum", + "url": "https://via.placeholder.com/600/2a7fbf", + "thumbnailUrl": "https://via.placeholder.com/150/2a7fbf" + }, + { + "albumId": 35, + "id": 1730, + "title": "modi incidunt sed ut", + "url": "https://via.placeholder.com/600/2fb19c", + "thumbnailUrl": "https://via.placeholder.com/150/2fb19c" + }, + { + "albumId": 35, + "id": 1731, + "title": "ratione harum expedita nihil nesciunt laudantium et ut", + "url": "https://via.placeholder.com/600/f6bb1b", + "thumbnailUrl": "https://via.placeholder.com/150/f6bb1b" + }, + { + "albumId": 35, + "id": 1732, + "title": "pariatur sunt eveniet", + "url": "https://via.placeholder.com/600/400978", + "thumbnailUrl": "https://via.placeholder.com/150/400978" + }, + { + "albumId": 35, + "id": 1733, + "title": "rerum qui repellendus neque delectus", + "url": "https://via.placeholder.com/600/924b68", + "thumbnailUrl": "https://via.placeholder.com/150/924b68" + }, + { + "albumId": 35, + "id": 1734, + "title": "sapiente hic omnis libero", + "url": "https://via.placeholder.com/600/59c019", + "thumbnailUrl": "https://via.placeholder.com/150/59c019" + }, + { + "albumId": 35, + "id": 1735, + "title": "aut illum porro vel harum est exercitationem nam", + "url": "https://via.placeholder.com/600/ef2d7c", + "thumbnailUrl": "https://via.placeholder.com/150/ef2d7c" + }, + { + "albumId": 35, + "id": 1736, + "title": "dolorum eaque eos", + "url": "https://via.placeholder.com/600/73462e", + "thumbnailUrl": "https://via.placeholder.com/150/73462e" + }, + { + "albumId": 35, + "id": 1737, + "title": "facere sed eum aspernatur nulla", + "url": "https://via.placeholder.com/600/34fe75", + "thumbnailUrl": "https://via.placeholder.com/150/34fe75" + }, + { + "albumId": 35, + "id": 1738, + "title": "ratione quia cumque", + "url": "https://via.placeholder.com/600/3c9376", + "thumbnailUrl": "https://via.placeholder.com/150/3c9376" + }, + { + "albumId": 35, + "id": 1739, + "title": "aut consequuntur occaecati non doloribus laborum animi enim", + "url": "https://via.placeholder.com/600/b730f7", + "thumbnailUrl": "https://via.placeholder.com/150/b730f7" + }, + { + "albumId": 35, + "id": 1740, + "title": "quidem aperiam recusandae assumenda nostrum", + "url": "https://via.placeholder.com/600/a226ba", + "thumbnailUrl": "https://via.placeholder.com/150/a226ba" + }, + { + "albumId": 35, + "id": 1741, + "title": "atque est voluptatum accusamus natus deleniti", + "url": "https://via.placeholder.com/600/e42e20", + "thumbnailUrl": "https://via.placeholder.com/150/e42e20" + }, + { + "albumId": 35, + "id": 1742, + "title": "iste non iure harum a", + "url": "https://via.placeholder.com/600/3fae7", + "thumbnailUrl": "https://via.placeholder.com/150/3fae7" + }, + { + "albumId": 35, + "id": 1743, + "title": "non placeat sequi sed numquam rerum delectus minima", + "url": "https://via.placeholder.com/600/e92278", + "thumbnailUrl": "https://via.placeholder.com/150/e92278" + }, + { + "albumId": 35, + "id": 1744, + "title": "exercitationem hic maiores expedita quae quia", + "url": "https://via.placeholder.com/600/133ca0", + "thumbnailUrl": "https://via.placeholder.com/150/133ca0" + }, + { + "albumId": 35, + "id": 1745, + "title": "dolorum mollitia atque velit corporis", + "url": "https://via.placeholder.com/600/bf8826", + "thumbnailUrl": "https://via.placeholder.com/150/bf8826" + }, + { + "albumId": 35, + "id": 1746, + "title": "consequatur ea cupiditate qui officiis amet est officia magnam", + "url": "https://via.placeholder.com/600/570847", + "thumbnailUrl": "https://via.placeholder.com/150/570847" + }, + { + "albumId": 35, + "id": 1747, + "title": "sequi in aut nam voluptatem perferendis", + "url": "https://via.placeholder.com/600/fd2e85", + "thumbnailUrl": "https://via.placeholder.com/150/fd2e85" + }, + { + "albumId": 35, + "id": 1748, + "title": "sunt quo exercitationem molestias corporis et soluta odio", + "url": "https://via.placeholder.com/600/3b5fa3", + "thumbnailUrl": "https://via.placeholder.com/150/3b5fa3" + }, + { + "albumId": 35, + "id": 1749, + "title": "fuga asperiores qui alias", + "url": "https://via.placeholder.com/600/efb648", + "thumbnailUrl": "https://via.placeholder.com/150/efb648" + }, + { + "albumId": 35, + "id": 1750, + "title": "in totam veritatis itaque iusto eaque perspiciatis libero deleniti", + "url": "https://via.placeholder.com/600/31fdae", + "thumbnailUrl": "https://via.placeholder.com/150/31fdae" + } ] \ No newline at end of file diff --git a/src/test/resources/__files/users.json b/src/test/resources/TestData/__files/users.json similarity index 96% rename from src/test/resources/__files/users.json rename to src/test/resources/TestData/__files/users.json index 0176a33..79b699a 100644 --- a/src/test/resources/__files/users.json +++ b/src/test/resources/TestData/__files/users.json @@ -1,232 +1,232 @@ -[ - { - "id": 1, - "name": "Leanne Graham", - "username": "Bret", - "email": "Sincere@april.biz", - "address": { - "street": "Kulas Light", - "suite": "Apt. 556", - "city": "Gwenborough", - "zipcode": "92998-3874", - "geo": { - "lat": "-37.3159", - "lng": "81.1496" - } - }, - "phone": "1-770-736-8031 x56442", - "website": "hildegard.org", - "company": { - "name": "Romaguera-Crona", - "catchPhrase": "Multi-layered client-server neural-net", - "bs": "harness real-time e-markets" - } - }, - { - "id": 2, - "name": "Ervin Howell", - "username": "Antonette", - "email": "Shanna@melissa.tv", - "address": { - "street": "Victor Plains", - "suite": "Suite 879", - "city": "Wisokyburgh", - "zipcode": "90566-7771", - "geo": { - "lat": "-43.9509", - "lng": "-34.4618" - } - }, - "phone": "010-692-6593 x09125", - "website": "anastasia.net", - "company": { - "name": "Deckow-Crist", - "catchPhrase": "Proactive didactic contingency", - "bs": "synergize scalable supply-chains" - } - }, - { - "id": 3, - "name": "Clementine Bauch", - "username": "Samantha", - "email": "Nathan@yesenia.net", - "address": { - "street": "Douglas Extension", - "suite": "Suite 847", - "city": "McKenziehaven", - "zipcode": "59590-4157", - "geo": { - "lat": "-68.6102", - "lng": "-47.0653" - } - }, - "phone": "1-463-123-4447", - "website": "ramiro.info", - "company": { - "name": "Romaguera-Jacobson", - "catchPhrase": "Face to face bifurcated interface", - "bs": "e-enable strategic applications" - } - }, - { - "id": 4, - "name": "Patricia Lebsack", - "username": "Karianne", - "email": "Julianne.OConner@kory.org", - "address": { - "street": "Hoeger Mall", - "suite": "Apt. 692", - "city": "South Elvis", - "zipcode": "53919-4257", - "geo": { - "lat": "29.4572", - "lng": "-164.2990" - } - }, - "phone": "493-170-9623 x156", - "website": "kale.biz", - "company": { - "name": "Robel-Corkery", - "catchPhrase": "Multi-tiered zero tolerance productivity", - "bs": "transition cutting-edge web services" - } - }, - { - "id": 5, - "name": "Chelsey Dietrich", - "username": "Kamren", - "email": "Lucio_Hettinger@annie.ca", - "address": { - "street": "Skiles Walks", - "suite": "Suite 351", - "city": "Roscoeview", - "zipcode": "33263", - "geo": { - "lat": "-31.8129", - "lng": "62.5342" - } - }, - "phone": "(254)954-1289", - "website": "demarco.info", - "company": { - "name": "Keebler LLC", - "catchPhrase": "User-centric fault-tolerant solution", - "bs": "revolutionize end-to-end systems" - } - }, - { - "id": 6, - "name": "Mrs. Dennis Schulist", - "username": "Leopoldo_Corkery", - "email": "Karley_Dach@jasper.info", - "address": { - "street": "Norberto Crossing", - "suite": "Apt. 950", - "city": "South Christy", - "zipcode": "23505-1337", - "geo": { - "lat": "-71.4197", - "lng": "71.7478" - } - }, - "phone": "1-477-935-8478 x6430", - "website": "ola.org", - "company": { - "name": "Considine-Lockman", - "catchPhrase": "Synchronised bottom-line interface", - "bs": "e-enable innovative applications" - } - }, - { - "id": 7, - "name": "Kurtis Weissnat", - "username": "Elwyn.Skiles", - "email": "Telly.Hoeger@billy.biz", - "address": { - "street": "Rex Trail", - "suite": "Suite 280", - "city": "Howemouth", - "zipcode": "58804-1099", - "geo": { - "lat": "24.8918", - "lng": "21.8984" - } - }, - "phone": "210.067.6132", - "website": "elvis.io", - "company": { - "name": "Johns Group", - "catchPhrase": "Configurable multimedia task-force", - "bs": "generate enterprise e-tailers" - } - }, - { - "id": 8, - "name": "Nicholas Runolfsdottir V", - "username": "Maxime_Nienow", - "email": "Sherwood@rosamond.me", - "address": { - "street": "Ellsworth Summit", - "suite": "Suite 729", - "city": "Aliyaview", - "zipcode": "45169", - "geo": { - "lat": "-14.3990", - "lng": "-120.7677" - } - }, - "phone": "586.493.6943 x140", - "website": "jacynthe.com", - "company": { - "name": "Abernathy Group", - "catchPhrase": "Implemented secondary concept", - "bs": "e-enable extensible e-tailers" - } - }, - { - "id": 9, - "name": "Glenna Reichert", - "username": "Delphine", - "email": "Chaim_McDermott@dana.io", - "address": { - "street": "Dayna Park", - "suite": "Suite 449", - "city": "Bartholomebury", - "zipcode": "76495-3109", - "geo": { - "lat": "24.6463", - "lng": "-168.8889" - } - }, - "phone": "(775)976-6794 x41206", - "website": "conrad.com", - "company": { - "name": "Yost and Sons", - "catchPhrase": "Switchable contextually-based project", - "bs": "aggregate real-time technologies" - } - }, - { - "id": 10, - "name": "Clementina DuBuque", - "username": "Moriah.Stanton", - "email": "Rey.Padberg@karina.biz", - "address": { - "street": "Kattie Turnpike", - "suite": "Suite 198", - "city": "Lebsackbury", - "zipcode": "31428-2261", - "geo": { - "lat": "-38.2386", - "lng": "57.2232" - } - }, - "phone": "024-648-3804", - "website": "ambrose.net", - "company": { - "name": "Hoeger LLC", - "catchPhrase": "Centralized empowering task-force", - "bs": "target end-to-end models" - } - } +[ + { + "id": 1, + "name": "Leanne Graham", + "username": "Bret", + "email": "Sincere@april.biz", + "address": { + "street": "Kulas Light", + "suite": "Apt. 556", + "city": "Gwenborough", + "zipcode": "92998-3874", + "geo": { + "lat": "-37.3159", + "lng": "81.1496" + } + }, + "phone": "1-770-736-8031 x56442", + "website": "hildegard.org", + "company": { + "name": "Romaguera-Crona", + "catchPhrase": "Multi-layered client-server neural-net", + "bs": "harness real-time e-markets" + } + }, + { + "id": 2, + "name": "Ervin Howell", + "username": "Antonette", + "email": "Shanna@melissa.tv", + "address": { + "street": "Victor Plains", + "suite": "Suite 879", + "city": "Wisokyburgh", + "zipcode": "90566-7771", + "geo": { + "lat": "-43.9509", + "lng": "-34.4618" + } + }, + "phone": "010-692-6593 x09125", + "website": "anastasia.net", + "company": { + "name": "Deckow-Crist", + "catchPhrase": "Proactive didactic contingency", + "bs": "synergize scalable supply-chains" + } + }, + { + "id": 3, + "name": "Clementine Bauch", + "username": "Samantha", + "email": "Nathan@yesenia.net", + "address": { + "street": "Douglas Extension", + "suite": "Suite 847", + "city": "McKenziehaven", + "zipcode": "59590-4157", + "geo": { + "lat": "-68.6102", + "lng": "-47.0653" + } + }, + "phone": "1-463-123-4447", + "website": "ramiro.info", + "company": { + "name": "Romaguera-Jacobson", + "catchPhrase": "Face to face bifurcated interface", + "bs": "e-enable strategic applications" + } + }, + { + "id": 4, + "name": "Patricia Lebsack", + "username": "Karianne", + "email": "Julianne.OConner@kory.org", + "address": { + "street": "Hoeger Mall", + "suite": "Apt. 692", + "city": "South Elvis", + "zipcode": "53919-4257", + "geo": { + "lat": "29.4572", + "lng": "-164.2990" + } + }, + "phone": "493-170-9623 x156", + "website": "kale.biz", + "company": { + "name": "Robel-Corkery", + "catchPhrase": "Multi-tiered zero tolerance productivity", + "bs": "transition cutting-edge web services" + } + }, + { + "id": 5, + "name": "Chelsey Dietrich", + "username": "Kamren", + "email": "Lucio_Hettinger@annie.ca", + "address": { + "street": "Skiles Walks", + "suite": "Suite 351", + "city": "Roscoeview", + "zipcode": "33263", + "geo": { + "lat": "-31.8129", + "lng": "62.5342" + } + }, + "phone": "(254)954-1289", + "website": "demarco.info", + "company": { + "name": "Keebler LLC", + "catchPhrase": "User-centric fault-tolerant solution", + "bs": "revolutionize end-to-end systems" + } + }, + { + "id": 6, + "name": "Mrs. Dennis Schulist", + "username": "Leopoldo_Corkery", + "email": "Karley_Dach@jasper.info", + "address": { + "street": "Norberto Crossing", + "suite": "Apt. 950", + "city": "South Christy", + "zipcode": "23505-1337", + "geo": { + "lat": "-71.4197", + "lng": "71.7478" + } + }, + "phone": "1-477-935-8478 x6430", + "website": "ola.org", + "company": { + "name": "Considine-Lockman", + "catchPhrase": "Synchronised bottom-line interface", + "bs": "e-enable innovative applications" + } + }, + { + "id": 7, + "name": "Kurtis Weissnat", + "username": "Elwyn.Skiles", + "email": "Telly.Hoeger@billy.biz", + "address": { + "street": "Rex Trail", + "suite": "Suite 280", + "city": "Howemouth", + "zipcode": "58804-1099", + "geo": { + "lat": "24.8918", + "lng": "21.8984" + } + }, + "phone": "210.067.6132", + "website": "elvis.io", + "company": { + "name": "Johns Group", + "catchPhrase": "Configurable multimedia task-force", + "bs": "generate enterprise e-tailers" + } + }, + { + "id": 8, + "name": "Nicholas Runolfsdottir V", + "username": "Maxime_Nienow", + "email": "Sherwood@rosamond.me", + "address": { + "street": "Ellsworth Summit", + "suite": "Suite 729", + "city": "Aliyaview", + "zipcode": "45169", + "geo": { + "lat": "-14.3990", + "lng": "-120.7677" + } + }, + "phone": "586.493.6943 x140", + "website": "jacynthe.com", + "company": { + "name": "Abernathy Group", + "catchPhrase": "Implemented secondary concept", + "bs": "e-enable extensible e-tailers" + } + }, + { + "id": 9, + "name": "Glenna Reichert", + "username": "Delphine", + "email": "Chaim_McDermott@dana.io", + "address": { + "street": "Dayna Park", + "suite": "Suite 449", + "city": "Bartholomebury", + "zipcode": "76495-3109", + "geo": { + "lat": "24.6463", + "lng": "-168.8889" + } + }, + "phone": "(775)976-6794 x41206", + "website": "conrad.com", + "company": { + "name": "Yost and Sons", + "catchPhrase": "Switchable contextually-based project", + "bs": "aggregate real-time technologies" + } + }, + { + "id": 10, + "name": "Clementina DuBuque", + "username": "Moriah.Stanton", + "email": "Rey.Padberg@karina.biz", + "address": { + "street": "Kattie Turnpike", + "suite": "Suite 198", + "city": "Lebsackbury", + "zipcode": "31428-2261", + "geo": { + "lat": "-38.2386", + "lng": "57.2232" + } + }, + "phone": "024-648-3804", + "website": "ambrose.net", + "company": { + "name": "Hoeger LLC", + "catchPhrase": "Centralized empowering task-force", + "bs": "target end-to-end models" + } + } ] \ No newline at end of file diff --git a/src/test/resources/TestData/CSVFile.csv b/src/test/resources/TestData/csv/CSVFile.csv similarity index 100% rename from src/test/resources/TestData/CSVFile.csv rename to src/test/resources/TestData/csv/CSVFile.csv diff --git a/src/test/resources/TestData/CSVFile2.csv b/src/test/resources/TestData/csv/CSVFile2.csv similarity index 100% rename from src/test/resources/TestData/CSVFile2.csv rename to src/test/resources/TestData/csv/CSVFile2.csv diff --git a/src/test/resources/TestData/ExcelFile.xlsx b/src/test/resources/TestData/excel/ExcelFile.xlsx similarity index 100% rename from src/test/resources/TestData/ExcelFile.xlsx rename to src/test/resources/TestData/excel/ExcelFile.xlsx diff --git a/src/test/resources/TestData/LoginData.xlsx b/src/test/resources/TestData/excel/LoginData.xlsx similarity index 100% rename from src/test/resources/TestData/LoginData.xlsx rename to src/test/resources/TestData/excel/LoginData.xlsx diff --git a/src/test/resources/images/automation.png b/src/test/resources/TestData/images/automation.png similarity index 100% rename from src/test/resources/images/automation.png rename to src/test/resources/TestData/images/automation.png diff --git a/src/test/resources/TestData/TestData.json b/src/test/resources/TestData/json/TestData.json similarity index 94% rename from src/test/resources/TestData/TestData.json rename to src/test/resources/TestData/json/TestData.json index bec7eb4..a90c706 100644 --- a/src/test/resources/TestData/TestData.json +++ b/src/test/resources/TestData/json/TestData.json @@ -1,13 +1,13 @@ -{ - "user": { - "email": "tomsmith", - "password": "SuperSecretPassword!" - }, - "password": "SuperSecretPassword!", - "email": "tomsmith", - "expectedResult_successMessage": "You logged into a secure area!" -} - - - - +{ + "user": { + "email": "tomsmith", + "password": "SuperSecretPassword!" + }, + "password": "SuperSecretPassword!", + "email": "tomsmith", + "expectedResult_successMessage": "You logged into a secure area!" +} + + + + diff --git a/src/test/resources/mappings/24848_xml.json b/src/test/resources/TestData/mappings/24848_xml.json similarity index 95% rename from src/test/resources/mappings/24848_xml.json rename to src/test/resources/TestData/mappings/24848_xml.json index c691534..4d05256 100644 --- a/src/test/resources/mappings/24848_xml.json +++ b/src/test/resources/TestData/mappings/24848_xml.json @@ -1,11 +1,11 @@ -{ - "request": { - "method": "GET", - "url": "/de/24848" - }, - "response": { - "status": 200, - "headers": { "Content-Type": "application/xml" }, - "bodyFileName": "24848.xml" - } +{ + "request": { + "method": "GET", + "url": "/de/24848" + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/xml" }, + "bodyFileName": "24848.xml" + } } \ No newline at end of file diff --git a/src/test/resources/mappings/90210_xml.json b/src/test/resources/TestData/mappings/90210_xml.json similarity index 95% rename from src/test/resources/mappings/90210_xml.json rename to src/test/resources/TestData/mappings/90210_xml.json index 379f104..60cd63b 100644 --- a/src/test/resources/mappings/90210_xml.json +++ b/src/test/resources/TestData/mappings/90210_xml.json @@ -1,11 +1,11 @@ -{ - "request": { - "method": "GET", - "url": "/us/90210" - }, - "response": { - "status": 200, - "headers": { "Content-Type": "application/xml" }, - "bodyFileName": "90210.xml" - } +{ + "request": { + "method": "GET", + "url": "/us/90210" + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/xml" }, + "bodyFileName": "90210.xml" + } } \ No newline at end of file diff --git a/src/test/resources/mappings/AddressDeserialization.json b/src/test/resources/TestData/mappings/AddressDeserialization.json similarity index 96% rename from src/test/resources/mappings/AddressDeserialization.json rename to src/test/resources/TestData/mappings/AddressDeserialization.json index 8a5ceab..ed7f7fc 100644 --- a/src/test/resources/mappings/AddressDeserialization.json +++ b/src/test/resources/TestData/mappings/AddressDeserialization.json @@ -1,11 +1,11 @@ -{ - "request": { - "method": "GET", - "url": "/address" - }, - "response": { - "status": 200, - "headers": { "Content-Type": "application/json" }, - "jsonBody": { "street": "My street", "houseNumber": 1,"zipCode": 1234, "city": "Amsterdam" } - } +{ + "request": { + "method": "GET", + "url": "/address" + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/json" }, + "jsonBody": { "street": "My street", "houseNumber": 1,"zipCode": 1234, "city": "Amsterdam" } + } } \ No newline at end of file diff --git a/src/test/resources/mappings/AddressSerialization.json b/src/test/resources/TestData/mappings/AddressSerialization.json similarity index 95% rename from src/test/resources/mappings/AddressSerialization.json rename to src/test/resources/TestData/mappings/AddressSerialization.json index 372e61e..1024748 100644 --- a/src/test/resources/mappings/AddressSerialization.json +++ b/src/test/resources/TestData/mappings/AddressSerialization.json @@ -1,11 +1,11 @@ -{ - "request": { - "method": "POST", - "url": "/address" - }, - "response": { - "status": 200, - "headers": { "Content-Type": "application/json" }, - "jsonBody": { "result": "OK" } - } +{ + "request": { + "method": "POST", + "url": "/address" + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/json" }, + "jsonBody": { "result": "OK" } + } } \ No newline at end of file diff --git a/src/test/resources/mappings/AlbumsJson.json b/src/test/resources/TestData/mappings/AlbumsJson.json similarity index 95% rename from src/test/resources/mappings/AlbumsJson.json rename to src/test/resources/TestData/mappings/AlbumsJson.json index 739b74c..6ce87e1 100644 --- a/src/test/resources/mappings/AlbumsJson.json +++ b/src/test/resources/TestData/mappings/AlbumsJson.json @@ -1,11 +1,11 @@ -{ - "request": { - "method": "GET", - "url": "/albums" - }, - "response": { - "status": 200, - "headers": { "Content-Type": "application/json" }, - "bodyFileName": "albums.json" - } +{ + "request": { + "method": "GET", + "url": "/albums" + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/json" }, + "bodyFileName": "albums.json" + } } \ No newline at end of file diff --git a/src/test/resources/mappings/CAY1A.json b/src/test/resources/TestData/mappings/CAY1A.json similarity index 97% rename from src/test/resources/mappings/CAY1A.json rename to src/test/resources/TestData/mappings/CAY1A.json index 64cfad8..049ba7d 100644 --- a/src/test/resources/mappings/CAY1A.json +++ b/src/test/resources/TestData/mappings/CAY1A.json @@ -1,11 +1,11 @@ -{ - "request": { - "method": "GET", - "url": "/ca/Y1A" - }, - "response": { - "status": 200, - "headers": { "Content-Type": "application/json" }, - "jsonBody": {"post code": "Y1A","country": "Canada","country abbreviation": "CA","places": [{"place name": "Whitehorse","longitude": "-118.4065","state": "Yukon","state abbreviation": "YT","latitude": "34.0901"}]} - } +{ + "request": { + "method": "GET", + "url": "/ca/Y1A" + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/json" }, + "jsonBody": {"post code": "Y1A","country": "Canada","country abbreviation": "CA","places": [{"place name": "Whitehorse","longitude": "-118.4065","state": "Yukon","state abbreviation": "YT","latitude": "34.0901"}]} + } } \ No newline at end of file diff --git a/src/test/resources/mappings/CarDeserialization.json b/src/test/resources/TestData/mappings/CarDeserialization.json similarity index 96% rename from src/test/resources/mappings/CarDeserialization.json rename to src/test/resources/TestData/mappings/CarDeserialization.json index e392e8c..4e7ba75 100644 --- a/src/test/resources/mappings/CarDeserialization.json +++ b/src/test/resources/TestData/mappings/CarDeserialization.json @@ -1,11 +1,11 @@ -{ - "request": { - "method": "GET", - "url": "/car/getcar/alfaromeogiulia" - }, - "response": { - "status": 200, - "headers": { "Content-Type": "application/json" }, - "jsonBody": { "make": "Alfa Romeo", "model": "Giulia", "modelYear": 2016 } - } +{ + "request": { + "method": "GET", + "url": "/car/getcar/alfaromeogiulia" + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/json" }, + "jsonBody": { "make": "Alfa Romeo", "model": "Giulia", "modelYear": 2016 } + } } \ No newline at end of file diff --git a/src/test/resources/mappings/CarSerialization.json b/src/test/resources/TestData/mappings/CarSerialization.json similarity index 96% rename from src/test/resources/mappings/CarSerialization.json rename to src/test/resources/TestData/mappings/CarSerialization.json index 8feedfe..ce4b69c 100644 --- a/src/test/resources/mappings/CarSerialization.json +++ b/src/test/resources/TestData/mappings/CarSerialization.json @@ -1,14 +1,14 @@ -{ - "request": { - "method": "POST", - "url": "/car/postcar", - "bodyPatterns":[{ - "equalToJson": { "make": "Ford", "model": "Focus", "modelYear": 2012 } - }] - }, - "response": { - "status": 200, - "headers": { "Content-Type": "application/json" }, - "jsonBody": { "result": "OK" } - } +{ + "request": { + "method": "POST", + "url": "/car/postcar", + "bodyPatterns":[{ + "equalToJson": { "make": "Ford", "model": "Focus", "modelYear": 2012 } + }] + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/json" }, + "jsonBody": { "result": "OK" } + } } \ No newline at end of file diff --git a/src/test/resources/mappings/CarsXml.json b/src/test/resources/TestData/mappings/CarsXml.json similarity index 95% rename from src/test/resources/mappings/CarsXml.json rename to src/test/resources/TestData/mappings/CarsXml.json index 51ec2ed..2099229 100644 --- a/src/test/resources/mappings/CarsXml.json +++ b/src/test/resources/TestData/mappings/CarsXml.json @@ -1,11 +1,11 @@ -{ - "request": { - "method": "GET", - "url": "/xml/cars" - }, - "response": { - "status": 200, - "headers": { "Content-Type": "application/xml" }, - "bodyFileName": "cars.xml" - } +{ + "request": { + "method": "GET", + "url": "/xml/cars" + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/xml" }, + "bodyFileName": "cars.xml" + } } \ No newline at end of file diff --git a/src/test/resources/mappings/DE24848.json b/src/test/resources/TestData/mappings/DE24848.json similarity index 98% rename from src/test/resources/mappings/DE24848.json rename to src/test/resources/TestData/mappings/DE24848.json index 05456ab..36424c2 100644 --- a/src/test/resources/mappings/DE24848.json +++ b/src/test/resources/TestData/mappings/DE24848.json @@ -1,12 +1,12 @@ -{ - "request": { - "method": "GET", - "url": "/de/24848" - }, - "response": { - "status": 200, - "headers": { "Content-Type": "application/json" }, - "jsonBody": {"post code": "24848","country": "Germany","country abbreviation": "DE","places": [{"place name": "Alt Bennebek","longitude": "9.4333","state":"Schleswig-Holstein","state abbreviation": "SH","latitude": "54.3833"},{"place name": "Klein Rheide","longitude": "9.4833","state":"Schleswig-Holstein","state abbreviation": "SH","latitude": "54.45"},{"place name": "Kropp","longitude": "9.5087","state": "Schleswig-Holstein","state abbreviation": "SH","latitude": "54.4111"},{"place name": "Klein Bennebek","longitude": "9.45","state": "Schleswig-Holstein","state abbreviation": "SH","latitude": "54.4"}]} - } -} - +{ + "request": { + "method": "GET", + "url": "/de/24848" + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/json" }, + "jsonBody": {"post code": "24848","country": "Germany","country abbreviation": "DE","places": [{"place name": "Alt Bennebek","longitude": "9.4333","state":"Schleswig-Holstein","state abbreviation": "SH","latitude": "54.3833"},{"place name": "Klein Rheide","longitude": "9.4833","state":"Schleswig-Holstein","state abbreviation": "SH","latitude": "54.45"},{"place name": "Kropp","longitude": "9.5087","state": "Schleswig-Holstein","state abbreviation": "SH","latitude": "54.4111"},{"place name": "Klein Bennebek","longitude": "9.45","state": "Schleswig-Holstein","state abbreviation": "SH","latitude": "54.4"}]} + } +} + diff --git a/src/test/resources/mappings/DE24848Xml.json b/src/test/resources/TestData/mappings/DE24848Xml.json similarity index 95% rename from src/test/resources/mappings/DE24848Xml.json rename to src/test/resources/TestData/mappings/DE24848Xml.json index 0ae79be..4169e74 100644 --- a/src/test/resources/mappings/DE24848Xml.json +++ b/src/test/resources/TestData/mappings/DE24848Xml.json @@ -1,11 +1,11 @@ -{ - "request": { - "method": "GET", - "url": "/xml/de/24848" - }, - "response": { - "status": 200, - "headers": { "Content-Type": "application/xml" }, - "bodyFileName": "24848.xml" - } +{ + "request": { + "method": "GET", + "url": "/xml/de/24848" + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/xml" }, + "bodyFileName": "24848.xml" + } } \ No newline at end of file diff --git a/src/test/resources/mappings/PhotosForAlbum.json b/src/test/resources/TestData/mappings/PhotosForAlbum.json similarity index 95% rename from src/test/resources/mappings/PhotosForAlbum.json rename to src/test/resources/TestData/mappings/PhotosForAlbum.json index 5734d89..41bf45e 100644 --- a/src/test/resources/mappings/PhotosForAlbum.json +++ b/src/test/resources/TestData/mappings/PhotosForAlbum.json @@ -1,11 +1,11 @@ -{ - "request": { - "method": "GET", - "url": "/albums/35/photos" - }, - "response": { - "status": 200, - "headers": { "Content-Type": "application/json" }, - "bodyFileName": "photosforalbum.json" - } +{ + "request": { + "method": "GET", + "url": "/albums/35/photos" + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/json" }, + "bodyFileName": "photosforalbum.json" + } } \ No newline at end of file diff --git a/src/test/resources/mappings/US12345.json b/src/test/resources/TestData/mappings/US12345.json similarity index 97% rename from src/test/resources/mappings/US12345.json rename to src/test/resources/TestData/mappings/US12345.json index 25d01ca..1e31bed 100644 --- a/src/test/resources/mappings/US12345.json +++ b/src/test/resources/TestData/mappings/US12345.json @@ -1,11 +1,11 @@ -{ - "request": { - "method": "GET", - "url": "/us/12345" - }, - "response": { - "status": 200, - "headers": { "Content-Type": "application/json" }, - "jsonBody": {"post code": "12345","country": "United States","country abbreviation": "US","places": [{"place name": "Schenectady","longitude": "-118.4065","state": "New York","state abbreviation": "NY","latitude": "34.0901"}]} - } +{ + "request": { + "method": "GET", + "url": "/us/12345" + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/json" }, + "jsonBody": {"post code": "12345","country": "United States","country abbreviation": "US","places": [{"place name": "Schenectady","longitude": "-118.4065","state": "New York","state abbreviation": "NY","latitude": "34.0901"}]} + } } \ No newline at end of file diff --git a/src/test/resources/mappings/US90210.json b/src/test/resources/TestData/mappings/US90210.json similarity index 97% rename from src/test/resources/mappings/US90210.json rename to src/test/resources/TestData/mappings/US90210.json index 026bb98..bbd4c2d 100644 --- a/src/test/resources/mappings/US90210.json +++ b/src/test/resources/TestData/mappings/US90210.json @@ -1,11 +1,11 @@ -{ - "request": { - "method": "GET", - "url": "/us/90210" - }, - "response": { - "status": 200, - "headers": { "Content-Type": "application/json" }, - "jsonBody": {"post code": "90210","country": "United States","country abbreviation": "US","places": [{"place name": "Beverly Hills","longitude": "-118.4065","state": "California","state abbreviation": "CA","latitude": "34.0901"}]} - } +{ + "request": { + "method": "GET", + "url": "/us/90210" + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/json" }, + "jsonBody": {"post code": "90210","country": "United States","country abbreviation": "US","places": [{"place name": "Beverly Hills","longitude": "-118.4065","state": "California","state abbreviation": "CA","latitude": "34.0901"}]} + } } \ No newline at end of file diff --git a/src/test/resources/mappings/US99999.json b/src/test/resources/TestData/mappings/US99999.json similarity index 96% rename from src/test/resources/mappings/US99999.json rename to src/test/resources/TestData/mappings/US99999.json index 17c89f5..f782c3b 100644 --- a/src/test/resources/mappings/US99999.json +++ b/src/test/resources/TestData/mappings/US99999.json @@ -1,11 +1,11 @@ -{ - "request": { - "method": "GET", - "url": "/us/99999" - }, - "response": { - "status": 404, - "headers": { "Content-Type": "application/json" }, - "jsonBody": {"error": "zip code 99999 does not exist for country code us"} - } +{ + "request": { + "method": "GET", + "url": "/us/99999" + }, + "response": { + "status": 404, + "headers": { "Content-Type": "application/json" }, + "jsonBody": {"error": "zip code 99999 does not exist for country code us"} + } } \ No newline at end of file diff --git a/src/test/resources/mappings/UsersJson.json b/src/test/resources/TestData/mappings/UsersJson.json similarity index 95% rename from src/test/resources/mappings/UsersJson.json rename to src/test/resources/TestData/mappings/UsersJson.json index 890c30a..3f27de7 100644 --- a/src/test/resources/mappings/UsersJson.json +++ b/src/test/resources/TestData/mappings/UsersJson.json @@ -1,11 +1,11 @@ -{ - "request": { - "method": "GET", - "url": "/users" - }, - "response": { - "status": 200, - "headers": { "Content-Type": "application/json" }, - "bodyFileName": "users.json" - } +{ + "request": { + "method": "GET", + "url": "/users" + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/json" }, + "bodyFileName": "users.json" + } } \ No newline at end of file diff --git a/src/test/resources/mappings/jsonFile.json b/src/test/resources/TestData/mappings/jsonFile.json similarity index 95% rename from src/test/resources/mappings/jsonFile.json rename to src/test/resources/TestData/mappings/jsonFile.json index ba552da..3ba7aba 100644 --- a/src/test/resources/mappings/jsonFile.json +++ b/src/test/resources/TestData/mappings/jsonFile.json @@ -1,62 +1,62 @@ - -[{ - "id": 1, - "first_name": "Lothaire", - "last_name": "Benazet", - "email": "lbenazet0@tinyurl.com", - "gender": "Male" -}, { - "id": 2, - "first_name": "Shellie", - "last_name": "Cowser", - "email": "scowser1@163.com", - "gender": "Female" -}, { - "id": 3, - "first_name": "Sharl", - "last_name": "Hesbrook", - "email": "shesbrook2@economist.com", - "gender": "Female" -}, { - "id": 4, - "first_name": "Merrili", - "last_name": "Acom", - "email": "macom3@goo.ne.jp", - "gender": "Female" -}, { - "id": 5, - "first_name": "Remus", - "last_name": "Downgate", - "email": "rdowngate4@shinystat.com", - "gender": "Male" -}, { - "id": 6, - "first_name": "Tatiana", - "last_name": "Tribble", - "email": "ttribble5@simplemachines.org", - "gender": "Female" -}, { - "id": 7, - "first_name": "Wood", - "last_name": "Hebbes", - "email": "whebbes6@psu.edu", - "gender": "Male" -}, { - "id": 8, - "first_name": "Kendall", - "last_name": "Bony", - "email": "kbony7@epa.gov", - "gender": "Male" -}, { - "id": 9, - "first_name": "Robinet", - "last_name": "Gooday", - "email": "rgooday8@boston.com", - "gender": "Male" -}, { - "id": 10, - "first_name": "Laural", - "last_name": "Krzysztofiak", - "email": "lkrzysztofiak9@sun.com", - "gender": "Female" -}] + +[{ + "id": 1, + "first_name": "Lothaire", + "last_name": "Benazet", + "email": "lbenazet0@tinyurl.com", + "gender": "Male" +}, { + "id": 2, + "first_name": "Shellie", + "last_name": "Cowser", + "email": "scowser1@163.com", + "gender": "Female" +}, { + "id": 3, + "first_name": "Sharl", + "last_name": "Hesbrook", + "email": "shesbrook2@economist.com", + "gender": "Female" +}, { + "id": 4, + "first_name": "Merrili", + "last_name": "Acom", + "email": "macom3@goo.ne.jp", + "gender": "Female" +}, { + "id": 5, + "first_name": "Remus", + "last_name": "Downgate", + "email": "rdowngate4@shinystat.com", + "gender": "Male" +}, { + "id": 6, + "first_name": "Tatiana", + "last_name": "Tribble", + "email": "ttribble5@simplemachines.org", + "gender": "Female" +}, { + "id": 7, + "first_name": "Wood", + "last_name": "Hebbes", + "email": "whebbes6@psu.edu", + "gender": "Male" +}, { + "id": 8, + "first_name": "Kendall", + "last_name": "Bony", + "email": "kbony7@epa.gov", + "gender": "Male" +}, { + "id": 9, + "first_name": "Robinet", + "last_name": "Gooday", + "email": "rgooday8@boston.com", + "gender": "Male" +}, { + "id": 10, + "first_name": "Laural", + "last_name": "Krzysztofiak", + "email": "lkrzysztofiak9@sun.com", + "gender": "Female" +}] diff --git a/src/test/resources/mappings/location.json b/src/test/resources/TestData/mappings/location.json similarity index 93% rename from src/test/resources/mappings/location.json rename to src/test/resources/TestData/mappings/location.json index 73d3fa3..c305870 100644 --- a/src/test/resources/mappings/location.json +++ b/src/test/resources/TestData/mappings/location.json @@ -1,9 +1,9 @@ -{ - "request": { - "method": "POST", - "url": "/lv/1050" - }, - "response": { - "status": 200 - } +{ + "request": { + "method": "POST", + "url": "/lv/1050" + }, + "response": { + "status": 200 + } } \ No newline at end of file diff --git a/src/test/resources/downloadFiles2/CSVFile.csv b/src/test/resources/downloadFiles2/CSVFile.csv deleted file mode 100644 index 5a1b230..0000000 --- a/src/test/resources/downloadFiles2/CSVFile.csv +++ /dev/null @@ -1,15 +0,0 @@ -Variable,Data1,Data2,Data3,Data4,Data5 -search2,Test1,Test2,Test4,Test10,Test -search3,Test2,Test3,Test4,Test5,Test6 -search4,Test3,Test4,Test5,Test6,Test7 -search5,Test4,Test5,Test6,Test7,Test8 -search6,Test5,Test6,Test7,Test8,Test9 -search7,Test6,Test7,Test8,Test9,Test10 -search8,Test7,Test8,Test9,Test10,Test11 -search9,Test8,Test9,Test10,Test11,Test12 -search10,Test9,Test10,Test11,Test12,Test13 -search11,Test10,Test11,Test12,Test13,Test14 -search12,Test11,Test12,Test13,Test14,Test15 -search13,Test12,Test13,Test14,Test15,Test16 -search14,Test13,Test14,Test15,Test16,Test17 -search15,Test14,Test15,Test16,Test17,Test18 \ No newline at end of file diff --git a/src/test/resources/downloadFiles2/java.jpg b/src/test/resources/downloadFiles2/java.jpg deleted file mode 100644 index e06f9a6049ba2f340f319defa87eab14a150ea66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53900 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