diff --git a/build.gradle b/build.gradle
index 62106df..96b1e80 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ plugins {
}
group = 'io.github.lambdatest'
-version = '1.0.12'
+version = '1.0.13-beta.1'
description = 'lambdatest-java-sdk'
repositories {
@@ -21,7 +21,7 @@ dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'io.netty:netty-transport-native-epoll:4.1.104.Final'
implementation 'io.netty:netty-transport-native-kqueue:4.1.104.Final'
-
+ implementation 'io.appium:java-client:7.6.0'
// New dependencies from POM file
implementation 'org.apache.httpcomponents:httpmime:4.5.13'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.1'
@@ -83,7 +83,7 @@ afterEvaluate {
mavenJava(MavenPublication) {
groupId = 'io.github.lambdatest'
artifactId = 'lambdatest-java-sdk'
- version = '1.0.12'
+ version = '1.0.13-beta.1'
pom {
name.set('LambdaTest Java SDK')
diff --git a/pom.xml b/pom.xml
index 24e6dc5..93849e7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
4.0.0
io.github.lambdatest
lambdatest-java-sdk
- 1.0.12
+ 1.0.13-beta.1
lambdatest-java-sdk
LambdaTest SDK in Java
https://www.lambdatest.com
@@ -58,6 +58,11 @@
selenium-java
4.1.2
+
+ io.appium
+ java-client
+ 8.0.0
+
com.google.code.gson
gson
diff --git a/src/main/java/io/github/lambdatest/SmartUIAppSnapshot.java b/src/main/java/io/github/lambdatest/SmartUIAppSnapshot.java
index 32c328b..7d2258f 100644
--- a/src/main/java/io/github/lambdatest/SmartUIAppSnapshot.java
+++ b/src/main/java/io/github/lambdatest/SmartUIAppSnapshot.java
@@ -3,17 +3,13 @@
import com.google.gson.Gson;
import io.github.lambdatest.constants.Constants;
import io.github.lambdatest.models.*;
+import io.github.lambdatest.utils.FullPageScreenshotUtil;
import io.github.lambdatest.utils.GitUtils;
import io.github.lambdatest.utils.SmartUIUtil;
-import org.openqa.selenium.Dimension;
-import org.openqa.selenium.OutputType;
-import org.openqa.selenium.TakesScreenshot;
-import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.*;
import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
import java.util.logging.Logger;
import io.github.lambdatest.utils.LoggerUtil;
@@ -81,67 +77,124 @@ private String getProjectToken(Map options) {
}
throw new IllegalArgumentException(Constants.Errors.PROJECT_TOKEN_UNSET);
}
+ private void validateMandatoryParams(WebDriver driver, String screenshotName, String deviceName) {
+ if (driver == null) {
+ log.severe(Constants.Errors.SELENIUM_DRIVER_NULL + " during take snapshot");
+ throw new IllegalArgumentException(Constants.Errors.SELENIUM_DRIVER_NULL);
+ }
+ if (screenshotName == null || screenshotName.isEmpty()) {
+ log.info(Constants.Errors.SNAPSHOT_NAME_NULL);
+ throw new IllegalArgumentException(Constants.Errors.SNAPSHOT_NAME_NULL);
+ }
+ if (deviceName == null || deviceName.isEmpty()) {
+ throw new IllegalArgumentException(Constants.Errors.DEVICE_NAME_NULL);
+ }
+ }
+
+ private String getOptionValue(Map options, String key) {
+ if (options != null && options.containsKey(key)) {
+ String value = options.get(key);
+ return value != null ? value.trim() : "";
+ }
+ return "";
+ }
+
+ private UploadSnapshotRequest initializeUploadRequest(String screenshotName, String viewport) {
+ UploadSnapshotRequest request = new UploadSnapshotRequest();
+ request.setScreenshotName(screenshotName);
+ request.setProjectToken(projectToken);
+ request.setViewport(viewport);
+ log.info("Viewport set to :" + viewport);
+ if (Objects.nonNull(buildData)) {
+ request.setBuildId(buildData.getBuildId());
+ request.setBuildName(buildData.getName());
+ }
+ return request;
+ }
+
+ private UploadSnapshotRequest configureDeviceNameAndPlatform(UploadSnapshotRequest request, String deviceName, String platform) {
+ String browserName = deviceName.toLowerCase().startsWith("i") ? "iOS" : "Android";
+ String platformName = (platform == null || platform.isEmpty()) ? browserName : platform;
+ request.setOs(platformName);
+ request.setDeviceName(deviceName + " " + platformName);
+ assert platform != null;
+ request.setBrowserName(platform.toLowerCase().contains("ios") ? "safari" : "chrome");
+ return request;
+ }
- public void smartuiAppSnapshot(WebDriver appiumDriver, String screenshotName, Map options)
+ public void smartuiAppSnapshot(WebDriver driver, String screenshotName, Map options)
throws Exception {
try {
- if (appiumDriver == null) {
- log.severe(Constants.Errors.SELENIUM_DRIVER_NULL + " during take snapshot");
- throw new IllegalArgumentException(Constants.Errors.SELENIUM_DRIVER_NULL);
+ String deviceName = getOptionValue(options, "deviceName");
+ String platform = getOptionValue(options, "platform");
+ validateMandatoryParams(driver, screenshotName, deviceName);
+ Dimension d = driver.manage().window().getSize();
+ int width = d.getWidth(), height = d.getHeight();
+ UploadSnapshotRequest initReq = initializeUploadRequest(screenshotName, width + "x" + height);
+ UploadSnapshotRequest uploadSnapshotRequest = configureDeviceNameAndPlatform(initReq, deviceName, platform);
+ String screenshotHash = UUID.randomUUID().toString();
+ uploadSnapshotRequest.setScreenshotHash(screenshotHash);
+ String uploadChunk = getOptionValue(options, "uploadChunk");
+ String pageCount = getOptionValue(options, "pageCount"); int userInputtedPageCount=0;
+ if(!pageCount.isEmpty()) {
+ userInputtedPageCount = Integer.parseInt(pageCount);
}
- if (screenshotName == null || screenshotName.isEmpty()) {
- log.info(Constants.Errors.SNAPSHOT_NAME_NULL);
- throw new IllegalArgumentException(Constants.Errors.SNAPSHOT_NAME_NULL);
+ if(!uploadChunk.isEmpty() && uploadChunk.toLowerCase().contains("true")) {
+ uploadSnapshotRequest.setUploadChunk("true");
+ } else {
+ uploadSnapshotRequest.setUploadChunk("false");
}
+ String navBarHeight = getOptionValue(options, "navigationBarHeight");
+ String statusBarHeight = getOptionValue(options, "statusBarHeight");
- TakesScreenshot takesScreenshot = (TakesScreenshot) appiumDriver;
- File screenshot = takesScreenshot.getScreenshotAs(OutputType.FILE);
- log.info("Screenshot captured: " + screenshotName);
-
- UploadSnapshotRequest uploadSnapshotRequest = new UploadSnapshotRequest();
- uploadSnapshotRequest.setScreenshotName(screenshotName);
- uploadSnapshotRequest.setProjectToken(projectToken);
- Dimension d = appiumDriver.manage().window().getSize();
- int w = d.getWidth(), h = d.getHeight();
- uploadSnapshotRequest.setViewport(w + "x" + h);
- log.info("Device viewport set to: " + uploadSnapshotRequest.getViewport());
- String platform = "", deviceName = "", browserName = "";
- if (options != null && options.containsKey("platform")) {
- platform = options.get("platform").trim();
- }
- if (options != null && options.containsKey("deviceName")) {
- deviceName = options.get("deviceName").trim();
+ if(!navBarHeight.isEmpty()) {
+ uploadSnapshotRequest.setNavigationBarHeight(navBarHeight);
}
- if (deviceName == null || deviceName.isEmpty()) {
- throw new IllegalArgumentException(Constants.Errors.DEVICE_NAME_NULL);
+ if(!statusBarHeight.isEmpty()) {
+ uploadSnapshotRequest.setStatusBarHeight(statusBarHeight);
}
- if (platform == null || platform.isEmpty()) {
- if (deviceName.toLowerCase().startsWith("i")) {
- browserName = "iOS";
- } else {
- browserName = "Android";
- }
+ String cropFooter = getOptionValue(options, "cropFooter");
+ if (!cropFooter.isEmpty()) {
+ uploadSnapshotRequest.setCropFooter(cropFooter.toLowerCase());
}
- uploadSnapshotRequest.setOs(platform != null && !platform.isEmpty() ? platform : browserName);
- if (platform != null && !platform.isEmpty()) {
- uploadSnapshotRequest.setDeviceName(deviceName + " " + platform);
- } else {
- uploadSnapshotRequest.setDeviceName(deviceName + " " + browserName);
+ String cropStatusBar = getOptionValue(options, "cropStatusBar");
+ if (!cropStatusBar.isEmpty()) {
+ uploadSnapshotRequest.setCropStatusBar(cropStatusBar.toLowerCase());
}
- if (platform.toLowerCase().contains("ios")) {
- uploadSnapshotRequest.setBrowserName("safari");
+ String fullPage = getOptionValue(options, "fullPage").toLowerCase();
+ if(!Boolean.parseBoolean(fullPage)){
+ if(!pageCount.isEmpty()){
+ throw new IllegalArgumentException(Constants.Errors.PAGE_COUNT_ERROR);
+ }
+ TakesScreenshot takesScreenshot = (TakesScreenshot) driver;
+ File screenshot = takesScreenshot.getScreenshotAs(OutputType.FILE);
+ log.info("Screenshot captured: " + screenshotName);
+ uploadSnapshotRequest.setFullPage("false");
+ util.uploadScreenshot(screenshot, uploadSnapshotRequest, this.buildData);
+
} else {
- uploadSnapshotRequest.setBrowserName("chrome");
- }
- if (Objects.nonNull(buildData)) {
- uploadSnapshotRequest.setBuildId(buildData.getBuildId());
- uploadSnapshotRequest.setBuildName(buildData.getName());
+ uploadSnapshotRequest.setFullPage("true");
+ FullPageScreenshotUtil fullPageCapture = new FullPageScreenshotUtil(driver, screenshotName);
+ List ssDir = fullPageCapture.captureFullPage(userInputtedPageCount);
+ if(ssDir.isEmpty()){
+ throw new RuntimeException(Constants.Errors.SMARTUI_SNAPSHOT_FAILED);
+ }
+ int pageCountInSsDir = ssDir.size(); int i;
+ if(pageCountInSsDir == 1) { //when page count is set to 1 as user for fullPage
+ uploadSnapshotRequest.setFullPage("false");
+ util.uploadScreenshot(ssDir.get(0), uploadSnapshotRequest, this.buildData);
+ return;
+ }
+ for( i = 0; i < pageCountInSsDir -1; ++i){
+ uploadSnapshotRequest.setIsLastChunk("false");
+ uploadSnapshotRequest.setChunkCount(i);
+ util.uploadScreenshot(ssDir.get(i), uploadSnapshotRequest, this.buildData);
+ }
+ uploadSnapshotRequest.setIsLastChunk("true");
+ uploadSnapshotRequest.setChunkCount(i);
+ util.uploadScreenshot(ssDir.get(pageCountInSsDir-1), uploadSnapshotRequest, this.buildData);
}
- UploadSnapshotResponse uploadSnapshotResponse = util.uploadScreenshot(screenshot, uploadSnapshotRequest,
- this.buildData);
- log.info("For uploading: " + uploadSnapshotRequest.toString() + " received response: "
- + uploadSnapshotResponse.getData());
} catch (Exception e) {
log.severe(Constants.Errors.UPLOAD_SNAPSHOT_FAILED + " due to: " + e.getMessage());
throw new Exception("Couldnt upload image to Smart UI due to: " + e.getMessage());
diff --git a/src/main/java/io/github/lambdatest/constants/Constants.java b/src/main/java/io/github/lambdatest/constants/Constants.java
index aa47ff7..6e4d423 100644
--- a/src/main/java/io/github/lambdatest/constants/Constants.java
+++ b/src/main/java/io/github/lambdatest/constants/Constants.java
@@ -1,14 +1,22 @@
package io.github.lambdatest.constants;
+import static io.github.lambdatest.constants.Constants.SmartUIRoutes.SMARTUI_CLIENT_API_URL;
+
public interface Constants {
String SMARTUI_SERVER_ADDRESS = "SMARTUI_SERVER_ADDRESS";
public static final String PROJECT_TOKEN = "projectToken";
+ public final String TEST_TYPE = "lambdatest-java-app-sdk";
String LOCAL_SERVER_HOST = "http://localhost:8080";
+ public static String getHostUrlFromEnvOrDefault() {
+ String envUrl = System.getenv("SMARTUI_CLIENT_API_URL");
+ return (envUrl != null && !envUrl.isEmpty()) ? envUrl : SMARTUI_CLIENT_API_URL;
+ }
+
//SmartUI API routes
interface SmartUIRoutes {
- public static final String HOST_URL = "https://api.lambdatest.com/visualui/1.0";
+ public static final String SMARTUI_CLIENT_API_URL = "https://api.lambdatest.com/visualui/1.0";
public static final String SMARTUI_HEALTHCHECK_ROUTE = "/healthcheck";
public static final String SMARTUI_DOMSERIALIZER_ROUTE = "/domserializer";
public static final String SMARTUI_SNAPSHOT_ROUTE = "/snapshot";
@@ -44,6 +52,7 @@ interface LogEnvVars {
interface Errors {
public static final String SELENIUM_DRIVER_NULL = "An instance of the selenium driver object is required.";
public static final String SNAPSHOT_NAME_NULL = "The `snapshotName` argument is required.";
+ public static final String SNAPSHOT_NOT_FOUND = "Screenshot not found.";
public static final String SMARTUI_NOT_RUNNING = "SmartUI server is not running.";
public static final String JAVA_SCRIPT_NOT_SUPPORTED = "The driver does not support JavaScript execution.";
public static final String EMPTY_RESPONSE_DOMSERIALIZER = "Response from fetchDOMSerializer is null or empty.";
@@ -60,6 +69,7 @@ interface Errors {
public static final String PROJECT_TOKEN_UNSET = "projectToken cant be empty";
public static final String USER_AUTH_ERROR = "User authentication failed";
public static final String STOP_BUILD_FAILED = "Failed to stop build";
+ public static final String PAGE_COUNT_ERROR = "Page Count Value is invalid";
public static final String NULL_OPTIONS_OBJECT = "Options object is null or missing in request.";
public static final String DEVICE_NAME_NULL = "Device name is a mandatory parameter.";
}
diff --git a/src/main/java/io/github/lambdatest/models/UploadSnapshotRequest.java b/src/main/java/io/github/lambdatest/models/UploadSnapshotRequest.java
index 885c922..faa4215 100644
--- a/src/main/java/io/github/lambdatest/models/UploadSnapshotRequest.java
+++ b/src/main/java/io/github/lambdatest/models/UploadSnapshotRequest.java
@@ -13,7 +13,16 @@ public class UploadSnapshotRequest {
private String buildId;
private String buildName;
private String screenshotName;
+ private String screenshotHash;
private String deviceName;
+ private String cropFooter;
+ private String cropStatusBar;
+ private String fullPage;
+ private String isLastChunk;
+ private Integer chunkCount;
+ private String uploadChunk;
+ private String navigationBarHeight;
+ private String statusBarHeight;
// Default constructor
public UploadSnapshotRequest() {
@@ -22,7 +31,9 @@ public UploadSnapshotRequest() {
// All Args constructor
public UploadSnapshotRequest(String screenshot, String browserName, String os, String viewport,
String projectToken, String buildId, String buildName,
- String screenshotName, String deviceName) {
+ String screenshotName, String screenshotHash ,String deviceName,String fullPage, String cropFooter,
+ String cropStatusBar, String isLastChunk, Integer chunkCount, String uploadChunk,
+ String navigationBarHeight, String statusBarHeight) {
this.browserName = browserName;
this.os = os;
this.viewport = viewport;
@@ -30,7 +41,16 @@ public UploadSnapshotRequest(String screenshot, String browserName, String os, S
this.buildId = buildId;
this.buildName = buildName;
this.screenshotName = screenshotName;
+ this.screenshotHash = screenshotHash;
this.deviceName = deviceName;
+ this.cropFooter = cropFooter;
+ this.cropStatusBar = cropStatusBar;
+ this.fullPage = fullPage;
+ this.isLastChunk = isLastChunk;
+ this.chunkCount = chunkCount;
+ this.uploadChunk = uploadChunk;
+ this.navigationBarHeight = navigationBarHeight;
+ this.statusBarHeight = statusBarHeight;
}
// Getters and setters
@@ -54,6 +74,36 @@ public String getViewport() {
return viewport;
}
+ public String getCropFooter() { return cropFooter; }
+
+ public void setCropFooter(String cropFooter) {
+ this.cropFooter = cropFooter;
+ }
+
+ public String getCropStatusBar() { return cropStatusBar; }
+
+ public String getFullPage() { return fullPage; }
+
+ public void setFullPage(String fullPage) {
+ this.fullPage = fullPage;
+ }
+
+ public String getIsLastChunk() { return isLastChunk; }
+
+ public void setIsLastChunk(String isLastChunk) {
+ this.isLastChunk = isLastChunk;
+ }
+
+ public String getUploadChunk() { return uploadChunk; }
+
+ public void setUploadChunk(String uploadChunk) {
+ this.uploadChunk = uploadChunk;
+ }
+
+ public void setCropStatusBar(String cropStatusBar) {
+ this.cropStatusBar = cropStatusBar;
+ }
+
public void setViewport(String viewport) {
this.viewport = viewport;
}
@@ -90,6 +140,14 @@ public void setScreenshotName(String screenshotName) {
this.screenshotName = screenshotName;
}
+ public String getScreenshotHash() {
+ return screenshotHash;
+ }
+
+ public void setScreenshotHash(String screenshotHash) {
+ this.screenshotHash = screenshotHash;
+ }
+
public String getDeviceName() {
return deviceName;
}
@@ -97,4 +155,28 @@ public String getDeviceName() {
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
+
+ public void setChunkCount(int chunkCount) {
+ this.chunkCount = chunkCount;
+ }
+
+ public Integer getChunkCount() {
+ return chunkCount;
+ }
+
+ public String getNavigationBarHeight() {
+ return navigationBarHeight;
+ }
+
+ public void setNavigationBarHeight(String navigationBarHeight) {
+ this.navigationBarHeight = navigationBarHeight;
+ }
+
+ public String getStatusBarHeight() {
+ return statusBarHeight;
+ }
+
+ public void setStatusBarHeight(String statusBarHeight) {
+ this.statusBarHeight = statusBarHeight;
+ }
}
diff --git a/src/main/java/io/github/lambdatest/utils/FullPageScreenshotUtil.java b/src/main/java/io/github/lambdatest/utils/FullPageScreenshotUtil.java
new file mode 100644
index 0000000..06d52cf
--- /dev/null
+++ b/src/main/java/io/github/lambdatest/utils/FullPageScreenshotUtil.java
@@ -0,0 +1,153 @@
+package io.github.lambdatest.utils;
+
+import io.appium.java_client.AppiumDriver;
+import io.appium.java_client.PerformsTouchActions;
+import io.appium.java_client.TouchAction;
+import io.appium.java_client.touch.WaitOptions;
+import io.appium.java_client.touch.offset.PointOption;
+import org.openqa.selenium.*;
+import org.openqa.selenium.interactions.PointerInput;
+import org.openqa.selenium.interactions.Sequence;
+import org.openqa.selenium.remote.RemoteWebElement;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.time.Duration;
+import java.util.*;
+import java.util.logging.Logger;
+
+public class FullPageScreenshotUtil {
+ private final WebDriver driver;
+ private final String saveDirectoryName;
+ private final Logger log = LoggerUtil.createLogger("lambdatest-java-app-sdk");
+
+ public FullPageScreenshotUtil(WebDriver driver, String saveDirectoryName) {
+ this.driver = driver;
+ this.saveDirectoryName = saveDirectoryName;
+
+ // Ensure the directory exists
+ File dir = new File(saveDirectoryName);
+ if (!dir.exists()) {
+ dir.mkdirs();
+ }
+ }
+
+ private String prevPageSource = "";
+ private int samePageCounter = 1; //Init with value 1 , finalise at 3
+ private int maxCount = 10;
+ public List captureFullPage(int pageCount) {
+ if(pageCount<=0){
+ pageCount = maxCount;
+ }
+ if (pageCount < maxCount) {
+ maxCount = pageCount;
+ }
+ int chunkCount = 0;
+ boolean isLastScroll = false;
+ List screenshotDir = new ArrayList<>();
+ while (!isLastScroll && chunkCount < maxCount) {
+ File screenshotFile= captureAndSaveScreenshot(this.saveDirectoryName,chunkCount);
+ if(screenshotFile != null) {
+ screenshotDir.add(screenshotFile);
+ chunkCount++;
+ }
+ //Perform scroll
+ scrollDown();
+ log.info("Scrolling attempt # " + chunkCount);
+ // Detect end of page
+ isLastScroll = hasReachedBottom();
+ }
+ log.info("Finished capturing all screenshots for full page.");
+ return screenshotDir;
+ }
+
+ private File captureAndSaveScreenshot(String ssDir, int index) {
+ File destinationFile = new File(ssDir + "/" + ssDir +"_" + index + ".png");
+ try {
+ File screenshotFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
+ Files.copy(screenshotFile.toPath(), destinationFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ log.info("Saved screenshot: " + destinationFile.getAbsolutePath());
+ } catch (IOException e) {
+ log.warning("Error saving screenshot: " + e.getMessage());
+ }
+ return destinationFile;
+ }
+
+ private void scrollDown() {
+ Dimension screenSize = driver.manage().window().getSize();
+ int screenHeight = screenSize.getHeight();
+ int screenWidth = screenSize.getWidth();
+
+ // Define start and end points for scrolling
+ int startX = 4; //start from 4 pixels from the left, to avoid click on action items/webview
+ int startY = (int) (screenHeight * 0.70); // Start at 70% of the screen height
+ int endY = (int) (screenHeight * 0.45); // Scroll up to 25%
+ int scrollHeight = startY - endY;
+
+ try {
+ // Try iOS style swipe
+ JavascriptExecutor javascriptExecutorIos = (JavascriptExecutor) driver;
+ Map swipeObj = new HashMap<>();
+ swipeObj.put("fromX", startX);
+ swipeObj.put("fromY", startY);
+ swipeObj.put("toX", startX);
+ swipeObj.put("toY", endY);
+ swipeObj.put("duration", 0.8);
+ javascriptExecutorIos.executeScript("mobile: dragFromToForDuration", swipeObj);
+
+ } catch (Exception iosException) {
+ try {
+ // If iOS swipe fails, assume it's Android and do scrollGesture
+ JavascriptExecutor jsExecutorAndroid = (JavascriptExecutor) driver;
+ Map scrollParams = new HashMap<>();
+ scrollParams.put("left", startX);
+ scrollParams.put("top", endY);
+ scrollParams.put("width", screenWidth - startX);
+ scrollParams.put("height", scrollHeight);
+ scrollParams.put("direction", "down");
+ scrollParams.put("percent", 1.0);
+ scrollParams.put("speed", 2500);
+ jsExecutorAndroid.executeScript("mobile:scrollGesture", scrollParams);
+ } catch (Exception e) {
+ log.warning("Error during Android scroll operation: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ try {
+ Thread.sleep(1000);
+ } catch (Exception e) {
+ log.warning("Error during scroll operation: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ private boolean hasReachedBottom() {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ String currentPageSource = driver.getPageSource();
+ if (currentPageSource == null) {
+ log.warning("Page source is null");
+ return false;
+ }
+ if (currentPageSource.equals(prevPageSource)) {
+ samePageCounter++;
+ log.info("Same page content detected, counter: " + samePageCounter);
+ if (samePageCounter >= 3) {
+ log.info("Reached the bottom of the page — no new content found.");
+ samePageCounter = 0;
+ return true;
+ }
+ } else {
+ prevPageSource = currentPageSource;
+ samePageCounter = 0;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/lambdatest/utils/GitUtils.java b/src/main/java/io/github/lambdatest/utils/GitUtils.java
index 92cdd09..fd8b1e6 100644
--- a/src/main/java/io/github/lambdatest/utils/GitUtils.java
+++ b/src/main/java/io/github/lambdatest/utils/GitUtils.java
@@ -7,14 +7,9 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.util.Arrays;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Map;
-import io.github.lambdatest.utils.LoggerUtil;
+import java.util.*;
import java.util.logging.Logger;
-import java.util.stream.Collectors;
public class GitUtils {
@@ -25,7 +20,8 @@ public static GitInfo getGitInfo(Map envVars) {
if (gitInfoFilePath != null) {
return readGitInfoFromFile(gitInfoFilePath, envVars);
} else {
- return fetchGitInfoFromCommands(envVars);
+ GitInfo gitInfo = fetchGitInfoFromCommands(envVars);
+ return gitInfo;
}
}
@@ -55,11 +51,10 @@ private static GitInfo fetchGitInfoFromCommands(Map envVars) {
String command = String.format(
"git log -1 --pretty=format:\"%s\" && git rev-parse --abbrev-ref HEAD && git tag --contains HEAD",
String.join(splitCharacter, prettyFormat));
-
List outputLines = executeCommand(command);
if (outputLines.isEmpty()) {
- return null;
+ return new GitInfo("", "", "", "", "", "");
}
String[] res = String.join("\n", outputLines).split(splitCharacter);
@@ -91,14 +86,39 @@ private static String getGitHubURL(Map envVars, String commitId)
}
private static List executeCommand(String command) {
+ List outputLines = new ArrayList<>();
try {
- Process process = Runtime.getRuntime().exec(new String[] { "/bin/sh", "-c", command });
- return new BufferedReader(new InputStreamReader(process.getInputStream()))
- .lines()
- .collect(Collectors.toList());
- } catch (IOException e) {
+ String os = System.getProperty("os.name").toLowerCase();
+ Process process;
+ if (os.contains("win")) {
+ // For Windows, use cmd.exe to execute the command
+ process = Runtime.getRuntime().exec(new String[] { "cmd.exe", "/c", command });
+ } else {
+ // For Unix-like systems (Linux, macOS), use /bin/sh
+ process = Runtime.getRuntime().exec(new String[] { "/bin/sh", "-c", command });
+ }
+ // Read both the output and error streams
+ try (BufferedReader inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+ BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
+ String line;
+ // Capture standard output (stdout)
+ while ((line = inputReader.readLine()) != null) {
+ outputLines.add(line);
+ }
+ // Capture error output (stderr)
+ while ((line = errorReader.readLine()) != null) {
+ log.severe("Error: " + line);
+ }
+ }
+ // Wait for the command to complete
+ int exitCode = process.waitFor();
+ if (exitCode != 0) {
+ log.severe("Command failed with exit code: " + exitCode);
+ }
+ } catch (IOException | InterruptedException e) {
log.severe("Error executing command: " + e.getMessage());
return new ArrayList();
}
+ return outputLines;
}
}
diff --git a/src/main/java/io/github/lambdatest/utils/HttpClientUtil.java b/src/main/java/io/github/lambdatest/utils/HttpClientUtil.java
index e5fb583..f229204 100644
--- a/src/main/java/io/github/lambdatest/utils/HttpClientUtil.java
+++ b/src/main/java/io/github/lambdatest/utils/HttpClientUtil.java
@@ -25,20 +25,18 @@
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
-import java.util.logging.Level;
import java.util.logging.Logger;
-import io.github.lambdatest.utils.LoggerUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustAllStrategy;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import javax.net.ssl.SSLContext;
+import static io.github.lambdatest.constants.Constants.TEST_TYPE;
+
public class HttpClientUtil {
private final CloseableHttpClient httpClient;
private Logger log = LoggerUtil.createLogger("lambdatest-java-sdk");
@@ -255,7 +253,8 @@ private void checkResponseStatus(HttpResponse response) throws IOException {
public boolean isUserAuthenticated(String projectToken) throws Exception {
try {
- String url = Constants.SmartUIRoutes.HOST_URL + Constants.SmartUIRoutes.SMARTUI_AUTH_ROUTE;
+ String hostUrl = Constants.getHostUrlFromEnvOrDefault();
+ String url = hostUrl + Constants.SmartUIRoutes.SMARTUI_AUTH_ROUTE;
HttpGet request = new HttpGet(url);
request.setHeader(Constants.PROJECT_TOKEN, projectToken);
log.info("Authenticating user for projectToken :" + projectToken);
@@ -295,7 +294,8 @@ public String postSnapshot(String data) throws IOException {
}
public String createSmartUIBuild(String createBuildRequest, Map headers) throws IOException {
- return postWithHeader(Constants.SmartUIRoutes.HOST_URL + Constants.SmartUIRoutes.SMARTUI_CREATE_BUILD,
+ String hostUrl = Constants.getHostUrlFromEnvOrDefault();
+ return postWithHeader(hostUrl + Constants.SmartUIRoutes.SMARTUI_CREATE_BUILD,
createBuildRequest, headers);
}
@@ -307,46 +307,109 @@ public void stopBuild(String buildId, Map headers) throws IOExce
headers.put(Constants.PROJECT_TOKEN, projectToken);
}
}
+ String hostUrl = Constants.getHostUrlFromEnvOrDefault();
String response = delete(
- Constants.SmartUIRoutes.HOST_URL + Constants.SmartUIRoutes.SMARTUI_FINALISE_BUILD_ROUTE + buildId,
+ hostUrl + Constants.SmartUIRoutes.SMARTUI_FINALISE_BUILD_ROUTE + buildId + "&testType="+ TEST_TYPE,
headers);
}
- public String uploadScreenshot(String url, File screenshot, UploadSnapshotRequest uploadScreenshotRequest,
- BuildData data) throws IOException {
+ public String uploadScreenshot(String url, File screenshot, UploadSnapshotRequest request,
+ BuildData data) throws IOException {
+ HttpPost uploadRequest = new HttpPost(url);
+ uploadRequest.setHeader("projectToken", request.getProjectToken());
+
+ // Build the multipart request entity
+ MultipartEntityBuilder builder = MultipartEntityBuilder.create();
+ builder.setMode(HttpMultipartMode.STRICT);
+
+ // Add the required fields
+ builder.addBinaryBody("screenshot", screenshot, ContentType.create("image/png"), request.getScreenshotName());
+ builder.addTextBody("buildId", data.getBuildId());
+ builder.addTextBody("buildName", data.getName());
+ builder.addTextBody("baseline", Boolean.toString(data.getBaseline()));
+ builder.addTextBody("screenshotName", request.getScreenshotName());
+ builder.addTextBody("browser", request.getBrowserName());
+ builder.addTextBody("deviceName", request.getDeviceName());
+ builder.addTextBody("os", request.getOs());
+ builder.addTextBody("viewport", request.getViewport());
+ builder.addTextBody("uploadChunk", request.getUploadChunk());
+ builder.addTextBody("projectType", TEST_TYPE);
+ builder.addTextBody("screenshotHash", request.getScreenshotHash());
+
+ // Add optional fields if present
+ if (Objects.nonNull(request.getFullPage())) {
+ builder.addTextBody("fullPage", request.getFullPage());
+ }
+ if (Objects.nonNull(request.getIsLastChunk())) {
+ builder.addTextBody("isLastChunk", request.getIsLastChunk());
+ }
+ if (Objects.nonNull(request.getChunkCount())) {
+ builder.addTextBody("chunkCount", String.valueOf(request.getChunkCount()));
+ }
- try {
- HttpPost uploadRequest = new HttpPost(url);
- uploadRequest.setHeader("projectToken", uploadScreenshotRequest.getProjectToken());
-
- MultipartEntityBuilder builder = MultipartEntityBuilder.create();
- builder.setMode(HttpMultipartMode.STRICT);
-
- builder.addBinaryBody("screenshot", screenshot, ContentType.create("image/png"), screenshot.getName());
- builder.addTextBody("buildId", uploadScreenshotRequest.getBuildId());
- builder.addTextBody("buildName", uploadScreenshotRequest.getBuildName());
- builder.addTextBody("screenshotName", uploadScreenshotRequest.getScreenshotName());
- builder.addTextBody("browser", uploadScreenshotRequest.getBrowserName());
- builder.addTextBody("deviceName", uploadScreenshotRequest.getDeviceName());
- builder.addTextBody("os", uploadScreenshotRequest.getOs());
- builder.addTextBody("viewport", uploadScreenshotRequest.getViewport());
- builder.addTextBody("projectType", "lambdatest-java-app-sdk");
- if (data.getBaseline()) {
- builder.addTextBody("baseline", "true");
- } else {
- builder.addTextBody("baseline", "false");
+ // Handle status bar height
+ String statusBarHeight = "";
+ if (request.getStatusBarHeight() == null) {
+ builder.addTextBody("statusBarHeight", statusBarHeight);
+ } else {
+ statusBarHeight = request.getStatusBarHeight();
+ //only set cropStatusBar to false when it exists and statusBarHeight is valid
+ if (request.getCropStatusBar() != null && Boolean.parseBoolean(request.getCropStatusBar())
+ && isValidNumber(statusBarHeight)) {
+ request.setCropStatusBar("false"); // Overwrite since we have custom value from user
}
+ request.setCropStatusBar("false");
+ builder.addTextBody("statusBarHeight", statusBarHeight);
+ }
- HttpEntity multipart = builder.build();
- uploadRequest.setEntity(multipart);
+ if (request.getCropStatusBar() != null) {
+ builder.addTextBody("cropStatusBar", request.getCropStatusBar());
+ }
- try (CloseableHttpResponse response = httpClient.execute(uploadRequest)) {
- return EntityUtils.toString(response.getEntity());
+ // Handle navigation bar height
+ String navigationBarHeight = "";
+ if (request.getNavigationBarHeight() == null) {
+ builder.addTextBody("navigationBarHeight", navigationBarHeight);
+ } else {
+ navigationBarHeight = request.getNavigationBarHeight();
+ //only set cropFooter to false when it exists and navigationBarHeight is valid
+ if (request.getCropFooter() != null && Boolean.parseBoolean(request.getCropFooter())
+ && isValidNumber(navigationBarHeight)) {
+ request.setCropFooter("false"); // Overwrite since we have custom value from user
}
+ request.setCropFooter("false");
+ builder.addTextBody("navigationBarHeight", navigationBarHeight);
+ }
+
+ if (request.getCropFooter() != null) {
+ builder.addTextBody("cropFooter", request.getCropFooter());
+ }
+
+ // Execute the request
+ HttpEntity multipart = builder.build();
+ uploadRequest.setEntity(multipart);
+ try (CloseableHttpResponse response = httpClient.execute(uploadRequest)) {
+ return EntityUtils.toString(response.getEntity());
} catch (IOException e) {
- log.warning("Exception occurred in uploading screenshot: " +
- e.getMessage());
- return "An error occurred while processing your request.";
+
+ log.warning("Exception occurred in uploading screenshot: " + e.getMessage());
+ throw new IOException("Failed to upload screenshot", e);
+ }
+ }
+
+ private boolean isValidNumber(String value) {
+ if (value == null || value.isEmpty()) {
+ return false;
+ }
+ try {
+ int strVal = Integer.parseInt(value);
+ if(strVal >=1) {
+ return true;
+ } else {
+ throw new NumberFormatException("Invalid value for cropping, pls provide a valid value");
+ }
+ } catch (NumberFormatException e) {
+ return false;
}
}
diff --git a/src/main/java/io/github/lambdatest/utils/SmartUIUtil.java b/src/main/java/io/github/lambdatest/utils/SmartUIUtil.java
index 62da079..330f788 100644
--- a/src/main/java/io/github/lambdatest/utils/SmartUIUtil.java
+++ b/src/main/java/io/github/lambdatest/utils/SmartUIUtil.java
@@ -2,13 +2,13 @@
import java.io.File;
import java.util.*;
-import java.util.logging.Level;
import java.util.logging.Logger;
import io.github.lambdatest.models.*;
import com.google.gson.Gson;
import io.github.lambdatest.constants.Constants;
+
public class SmartUIUtil {
private final HttpClientUtil httpClient;
private final Logger log = LoggerUtil.createLogger("lambdatest-java-sdk");
@@ -86,20 +86,22 @@ public static String getSmartUIServerAddress() {
}
}
- public UploadSnapshotResponse uploadScreenshot(File screenshotFile, UploadSnapshotRequest uploadScreenshotRequest,
- BuildData buildData) throws Exception {
+ public void uploadScreenshot(File screenshotFile, UploadSnapshotRequest uploadScreenshotRequest,
+ BuildData buildData) throws Exception {
UploadSnapshotResponse uploadAPIResponse = new UploadSnapshotResponse();
try {
- String url = Constants.SmartUIRoutes.HOST_URL + Constants.SmartUIRoutes.SMARTUI_UPLOAD_SCREENSHOT_ROUTE;
- String uploadScreenshotResponse = httpClient.uploadScreenshot(url, screenshotFile, uploadScreenshotRequest,
- buildData);
+ if(Objects.isNull(screenshotFile)){
+ throw new RuntimeException(Constants.Errors.SNAPSHOT_NOT_FOUND);
+ }
+ String hostUrl = Constants.getHostUrlFromEnvOrDefault();
+ String url = hostUrl + Constants.SmartUIRoutes.SMARTUI_UPLOAD_SCREENSHOT_ROUTE;
+ String uploadScreenshotResponse = httpClient.uploadScreenshot(url, screenshotFile, uploadScreenshotRequest, buildData);
uploadAPIResponse = gson.fromJson(uploadScreenshotResponse, UploadSnapshotResponse.class);
if (Objects.isNull(uploadAPIResponse))
throw new IllegalStateException("Failed to upload screenshot to SmartUI");
} catch (Exception e) {
throw new Exception("Couldn't upload image to SmartUI because of error : " + e.getMessage());
}
- return uploadAPIResponse;
}
public BuildResponse build(GitInfo git, String projectToken, Map options) throws Exception {
@@ -112,7 +114,6 @@ public BuildResponse build(GitInfo git, String projectToken, Map
if (options != null && options.containsKey("buildName")) {
String buildNameStr = options.get("buildName");
- // Check if value is non-null and a valid String
if (buildNameStr != null && !buildNameStr.trim().isEmpty()) {
createBuildRequest.setBuildName(buildNameStr);
log.info("Build name set from options: " + buildNameStr);
@@ -124,11 +125,12 @@ public BuildResponse build(GitInfo git, String projectToken, Map
} else {
createBuildRequest.setBuildName("smartui-" + UUID.randomUUID().toString().substring(0, 10));
}
+
if (Objects.nonNull(git)) {
createBuildRequest.setGit(git);
}
String createBuildJson = gson.toJson(createBuildRequest);
- Map header = new HashMap();
+ Map header = new HashMap<>();
header.put(Constants.PROJECT_TOKEN, projectToken);
String createBuildResponse = httpClient.createSmartUIBuild(createBuildJson, header);
BuildResponse buildData = gson.fromJson(createBuildResponse, BuildResponse.class);
@@ -140,7 +142,7 @@ public BuildResponse build(GitInfo git, String projectToken, Map
public void stopBuild(String buildId, String projectToken) throws Exception {
try {
- Map headers = new HashMap();
+ Map headers = new HashMap<>();
headers.put(Constants.PROJECT_TOKEN, projectToken);
httpClient.stopBuild(buildId, headers);
} catch (Exception e) {