From ae64140a45a04f7f5b3e311ad03e6313b668b7bc Mon Sep 17 00:00:00 2001 From: Zeeshan Date: Wed, 8 Oct 2025 23:02:30 +0530 Subject: [PATCH] Add viewport resizing option for responsive dom --- .../java/io/github/lambdatest/SmartUI.java | 318 +++++++++++++++--- .../lambdatest/constants/DeviceSpecs.java | 245 ++++++++++++++ 2 files changed, 513 insertions(+), 50 deletions(-) create mode 100644 src/main/java/io/github/lambdatest/constants/DeviceSpecs.java diff --git a/src/main/java/io/github/lambdatest/SmartUI.java b/src/main/java/io/github/lambdatest/SmartUI.java index 2f91ade..474cfc5 100644 --- a/src/main/java/io/github/lambdatest/SmartUI.java +++ b/src/main/java/io/github/lambdatest/SmartUI.java @@ -1,17 +1,26 @@ package io.github.lambdatest; import io.github.lambdatest.constants.Constants; +import io.github.lambdatest.constants.DeviceSpecs; import io.github.lambdatest.exceptions.SmartUIException; import io.github.lambdatest.utils.LoggerUtil; +import org.openqa.selenium.chromium.HasCdp; import java.net.HttpURLConnection; import java.net.URL; import java.io.BufferedReader; import java.io.InputStreamReader; +import java.io.FileReader; +import java.io.IOException; +import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + /** * Main SmartUI class for server management and snapshot functionality */ @@ -19,6 +28,7 @@ public class SmartUI { private final SmartUIConfig config; private Process serverProcess; private boolean isServerRunning = false; + private final Map configMap = new HashMap<>(); private static final Logger log = LoggerUtil.createLogger("lambdatest-java-sdk"); private static final String SMARTUI_CLI_COMMAND = "smartui"; @@ -28,6 +38,11 @@ public SmartUI(SmartUIConfig config) { if (config.getProjectToken() == null || config.getProjectToken().trim().isEmpty()) { throw new IllegalArgumentException("Project token is required"); } + + String configFile = config.getConfigFile(); + if (configFile != null && !configFile.trim().isEmpty()) { + parseConfigFile(configFile); + } } public void startServer() throws SmartUIException { @@ -79,7 +94,8 @@ public void stopServer() throws SmartUIException { } } - public void takeSnapshot(org.openqa.selenium.WebDriver driver, String snapshotName, Map options) throws SmartUIException { + public void takeSnapshot(org.openqa.selenium.WebDriver driver, String snapshotName, Map options) + throws SmartUIException { if (!isServerRunning) { throw new SmartUIException("Cannot take snapshot: SmartUI server is not running"); } @@ -106,12 +122,18 @@ public void takeSnapshot(org.openqa.selenium.WebDriver driver, String snapshotNa System.setProperty(Constants.SMARTUI_SERVER_ADDRESS, config.getServerAddress()); String testType = config.getTestType(); - if (testType != null && !testType.trim().isEmpty()){ - SmartUISnapshot.smartuiSnapshot(driver, snapshotName, options, testType); - } else { - SmartUISnapshot.smartuiSnapshot(driver, snapshotName, options); + Object resizeViewports = options.remove("resizeViewports"); + + captureSnapshot(driver, snapshotName, options, testType); + + if (resizeViewports instanceof java.util.List) { + java.util.List viewportList = (java.util.List) resizeViewports; + processWebViewports(driver, snapshotName, options, testType, viewportList); + processMobileDevices(driver, snapshotName, options, testType, viewportList); } + clearDeviceMetrics(driver); + log.info("Snapshot captured successfully: " + snapshotName); } catch (Exception e) { @@ -124,7 +146,7 @@ public void takeSnapshot(org.openqa.selenium.WebDriver driver, String snapshotNa public void takeSnapshot(org.openqa.selenium.WebDriver driver, String snapshotName) throws SmartUIException { takeSnapshot(driver, snapshotName, new HashMap<>()); } - + private boolean isSmartUICLIInstalled() { try { ProcessBuilder processBuilder = new ProcessBuilder(); @@ -133,7 +155,7 @@ private boolean isSmartUICLIInstalled() { } else { processBuilder.command("which", SMARTUI_CLI_COMMAND); } - + Process process = processBuilder.start(); int exitCode = process.waitFor(); return exitCode == 0; @@ -142,7 +164,7 @@ private boolean isSmartUICLIInstalled() { return false; } } - + private void installSmartUICLI() throws SmartUIException { log.info("SmartUI CLI not found. Attempting to install..."); try { @@ -152,24 +174,24 @@ private void installSmartUICLI() throws SmartUIException { } else { processBuilder.command("npm", "install", "-g", "@lambdatest/smartui-cli@latest"); } - + Process process = processBuilder.start(); int exitCode = process.waitFor(); - + if (exitCode != 0) { throw new SmartUIException("Failed to install SmartUI CLI. Exit code: " + exitCode); } - + log.info("SmartUI CLI installed successfully"); } catch (Exception e) { throw new SmartUIException("Failed to install SmartUI CLI: " + e.getMessage(), e); } } - + private void startServerWithRetry() throws SmartUIException { int maxRetries = 1; Exception lastException = null; - + for (int attempt = 0; attempt <= maxRetries; attempt++) { try { startServerProcess(); @@ -187,25 +209,25 @@ private void startServerWithRetry() throws SmartUIException { } } } - + throw new SmartUIException("Failed to start server after " + (maxRetries + 1) + " attempts", lastException); } - + private void startServerProcess() throws SmartUIException { try { ProcessBuilder processBuilder = new ProcessBuilder(); java.util.List command = new java.util.ArrayList<>(); - + if (System.getProperty("os.name").toLowerCase().contains("win")) { command.add("cmd"); command.add("/c"); } - + command.add(SMARTUI_CLI_COMMAND); command.add("exec:start"); command.add("-P"); command.add(String.valueOf(config.getPort())); - + if (config.getBuildName() != null && !config.getBuildName().trim().isEmpty()) { command.add("--buildName"); command.add(config.getBuildName()); @@ -215,52 +237,52 @@ private void startServerProcess() throws SmartUIException { command.add("--config"); command.add(config.getConfigFile()); } - + Map env = processBuilder.environment(); env.put("PROJECT_TOKEN", config.getProjectToken()); env.put("SMARTUI_SERVER_ADDRESS", "http://localhost:" + config.getPort()); - + logCliContext("start", command, env); - + processBuilder.command(command); processBuilder.redirectErrorStream(true); - + Process process = processBuilder.start(); streamProcessOutput(process, "[CLI start] "); - + Thread.sleep(2000); - + if (!pingServer()) { log.warning("CLI start command executed, but server is not responding yet"); } else { log.info("CLI start command executed successfully, server is responding"); } - + } catch (Exception e) { throw new SmartUIException("Failed to start server process: " + e.getMessage(), e); } } - + private void waitForServerReady() throws SmartUIException { long startTime = System.currentTimeMillis(); - long timeout = 20000; - + long timeout = 20000; + while (System.currentTimeMillis() - startTime < timeout) { if (pingServer()) { log.info("Server is now ready and responding"); return; } try { - Thread.sleep(1000); + Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new SmartUIException("Interrupted while waiting for server", e); } } - + throw new SmartUIException("Server did not become ready within 20 seconds"); } - + public boolean pingServer() { try { URL url = new URL(config.getServerAddress() + "/healthcheck"); @@ -268,12 +290,12 @@ public boolean pingServer() { connection.setRequestMethod("GET"); connection.setConnectTimeout(5000); connection.setReadTimeout(5000); - + connection.setRequestProperty("User-Agent", "SmartUI-Java-SDK/1.0"); - + int responseCode = connection.getResponseCode(); return responseCode == 200; - + } catch (Exception e) { return false; } @@ -297,26 +319,27 @@ public boolean isServerHealthy() { if (!isServerRunning) { return false; } - + if (!pingServer()) { return false; } - + if (serverProcess != null && !serverProcess.isAlive()) { log.warning("Server process is no longer alive"); isServerRunning = false; return false; } - + return true; } catch (Exception e) { log.warning("Error checking server health: " + e.getMessage()); return false; } } - + /** * Stop server using SmartUI CLI command + * * @param port The port where server is running * @return true if CLI command succeeded, false otherwise */ @@ -324,26 +347,26 @@ public boolean stopServerViaCLI(int port) { try { ProcessBuilder stopProcessBuilder = new ProcessBuilder(); java.util.List stopCommand = new java.util.ArrayList<>(); - + if (System.getProperty("os.name").toLowerCase().contains("win")) { stopCommand.add("cmd"); stopCommand.add("/c"); } - + stopCommand.add(SMARTUI_CLI_COMMAND); stopCommand.add("exec:stop"); - + Map env = stopProcessBuilder.environment(); env.put("PROJECT_TOKEN", config.getProjectToken()); env.put("SMARTUI_SERVER_ADDRESS", "http://localhost:" + port); - + logCliContext("stop", stopCommand, env); - + stopProcessBuilder.directory(new java.io.File(System.getProperty("user.dir"))); stopProcessBuilder.redirectErrorStream(true); stopProcessBuilder.command(stopCommand); Process stopProcess = stopProcessBuilder.start(); - + StringBuilder output = new StringBuilder(); Thread outputThread = new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(stopProcess.getInputStream()))) { @@ -358,13 +381,13 @@ public boolean stopServerViaCLI(int port) { }); outputThread.setDaemon(true); outputThread.start(); - + int exitCode = stopProcess.waitFor(); - + outputThread.join(1000); - + String outputString = output.toString(); - + if (exitCode == 0) { if (outputString.contains("Server stopped successfully")) { log.info("Server stopped successfully"); @@ -377,7 +400,7 @@ public boolean stopServerViaCLI(int port) { log.warning("CLI stop command failed with exit code: " + exitCode); return false; } - + } catch (Exception e) { log.warning("Failed to execute CLI stop command: " + e.getMessage()); return false; @@ -409,7 +432,8 @@ private void logCliContext(String action, java.util.List command, Map getConfigMap() { + return configMap; + } + + private void parseConfigFile(String filePath) { + try { + File configFile = new File(filePath); + if (!configFile.exists()) { + log.warning("Config file not found: " + filePath); + return; + } + + if (!configFile.isFile()) { + log.warning("Config path is not a file: " + filePath); + return; + } + + try (FileReader reader = new FileReader(configFile)) { + JsonObject jsonObject = JsonParser.parseReader(reader).getAsJsonObject(); + + if (jsonObject.has("web")) { + JsonObject webConfig = jsonObject.getAsJsonObject("web"); + Map webMap = new HashMap<>(); + + if (webConfig.has("browsers")) { + webMap.put("browsers", new Gson().fromJson(webConfig.get("browsers"), java.util.List.class)); + } + if (webConfig.has("viewports")) { + webMap.put("viewports", new Gson().fromJson(webConfig.get("viewports"), java.util.List.class)); + } + + configMap.put("web", webMap); + } + + if (jsonObject.has("mobile")) { + JsonObject mobileConfig = jsonObject.getAsJsonObject("mobile"); + Map mobileMap = new HashMap<>(); + + if (mobileConfig.has("devices")) { + mobileMap.put("devices", + new Gson().fromJson(mobileConfig.get("devices"), java.util.List.class)); + } + if (mobileConfig.has("fullPage")) { + mobileMap.put("fullPage", mobileConfig.get("fullPage").getAsBoolean()); + } + if (mobileConfig.has("orientation")) { + mobileMap.put("orientation", mobileConfig.get("orientation").getAsString()); + } + + configMap.put("mobile", mobileMap); + } + + log.info("Successfully parsed config file: " + filePath); + + } catch (IOException e) { + log.warning("Failed to read config file: " + filePath + " - " + e.getMessage()); + } catch (Exception e) { + log.warning("Failed to parse config file: " + filePath + " - " + e.getMessage()); + } + + } catch (Exception e) { + log.warning("Error processing config file: " + filePath + " - " + e.getMessage()); + } + } + + private void captureSnapshot(org.openqa.selenium.WebDriver driver, String snapshotName, Map options, String testType) throws Exception { + if (testType != null && !testType.trim().isEmpty()) { + SmartUISnapshot.smartuiSnapshot(driver, snapshotName, options, testType); + } else { + SmartUISnapshot.smartuiSnapshot(driver, snapshotName, options); + } + } + + private void processWebViewports(org.openqa.selenium.WebDriver driver, String snapshotName, Map options, String testType, java.util.List viewportList) throws Exception { + if (!configMap.containsKey("web")) { + return; + } + + Map webConfig = (Map) configMap.get("web"); + if (!webConfig.containsKey("viewports")) { + return; + } + + java.util.List configViewportsRaw = (java.util.List) webConfig.get("viewports"); + java.util.List configViewports = extractWholeNumbers(configViewportsRaw); + + for (Object viewport : viewportList) { + String viewportStr = viewport.toString(); + if (configViewports.contains(viewportStr)) { + setDeviceMetricsAndRefresh(driver, Integer.parseInt(viewportStr), 1080, 1, false); + + Map optionsCopy = new HashMap<>(options); + Map webViewportConfig = new HashMap<>(); + webViewportConfig.put("viewports", java.util.Arrays.asList(viewport)); + optionsCopy.put("web", webViewportConfig); + + captureSnapshot(driver, snapshotName, optionsCopy, testType); + } + } + } + + private void processMobileDevices(org.openqa.selenium.WebDriver driver, String snapshotName, Map options, String testType, java.util.List viewportList) throws Exception { + if (!configMap.containsKey("mobile")) { + return; + } + + Map mobileConfig = (Map) configMap.get("mobile"); + if (!mobileConfig.containsKey("devices")) { + return; + } + + java.util.List configDevices = (java.util.List) mobileConfig.get("devices"); + + for (Object viewport : viewportList) { + if (configDevices.contains(viewport)) { + DeviceSpecs.DeviceSpec deviceSpec = DeviceSpecs.getDeviceSpec(viewport.toString()); + if (deviceSpec != null) { + setDeviceMetricsAndRefresh(driver, deviceSpec.getWidth(), deviceSpec.getHeight(), 3, true); + + Map optionsCopy = new HashMap<>(options); + Map mobileDeviceConfig = new HashMap<>(); + mobileDeviceConfig.put("devices", java.util.Arrays.asList(viewport)); + + if (mobileConfig.containsKey("fullPage")) { + mobileDeviceConfig.put("fullPage", mobileConfig.get("fullPage")); + } + if (mobileConfig.containsKey("orientation")) { + mobileDeviceConfig.put("orientation", mobileConfig.get("orientation")); + } + + optionsCopy.put("mobile", mobileDeviceConfig); + captureSnapshot(driver, snapshotName, optionsCopy, testType); + } else { + log.warning("Device spec not found for: " + viewport); + } + } + } + } + + private java.util.List extractWholeNumbers(java.util.List rawList) { + java.util.List result = new java.util.ArrayList<>(); + for (Object vp : rawList) { + if (vp instanceof java.util.List) { + java.util.List vpList = (java.util.List) vp; + if (!vpList.isEmpty()) { + String vpStr = vpList.get(0).toString().split("\\.")[0]; + result.add(vpStr); + } + } else { + String vpStr = vp.toString().split("\\.")[0]; + result.add(vpStr); + } + } + return result; + } + + private void setDeviceMetricsAndRefresh(org.openqa.selenium.WebDriver driver, int width, int height, int deviceScaleFactor, boolean mobile) { + try { + if (driver instanceof HasCdp) { + Map cdpParams = new HashMap<>(); + cdpParams.put("width", width); + cdpParams.put("height", height); + cdpParams.put("deviceScaleFactor", deviceScaleFactor); + cdpParams.put("mobile", mobile); + + ((HasCdp) driver).executeCdpCommand("Emulation.setDeviceMetricsOverride", cdpParams); + driver.navigate().refresh(); + try { + Thread.sleep(2000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + } catch (Exception e) { + log.warning("Failed to set device metrics: " + e.getMessage()); + } + } + + private void clearDeviceMetrics(org.openqa.selenium.WebDriver driver) { + try { + if (driver instanceof HasCdp) { + ((HasCdp) driver).executeCdpCommand("Emulation.clearDeviceMetricsOverride", new HashMap<>()); + driver.navigate().refresh(); + try { + Thread.sleep(2000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + } catch (Exception e) { + log.warning("Failed to clear device metrics: " + e.getMessage()); + } + } } diff --git a/src/main/java/io/github/lambdatest/constants/DeviceSpecs.java b/src/main/java/io/github/lambdatest/constants/DeviceSpecs.java new file mode 100644 index 0000000..d02487e --- /dev/null +++ b/src/main/java/io/github/lambdatest/constants/DeviceSpecs.java @@ -0,0 +1,245 @@ +package io.github.lambdatest.constants; + +import java.util.HashMap; +import java.util.Map; + +public class DeviceSpecs { + + public static class DeviceSpec { + private final String os; + private final int width; + private final int height; + + public DeviceSpec(String os, int width, int height) { + this.os = os; + this.width = width; + this.height = height; + } + + public String getOs() { return os; } + public int getWidth() { return width; } + public int getHeight() { return height; } + } + + private static final Map DEVICE_SPECS = new HashMap<>(); + + static { + DEVICE_SPECS.put("Blackberry KEY2 LE", new DeviceSpec("android", 412, 618)); + DEVICE_SPECS.put("Galaxy A12", new DeviceSpec("android", 360, 800)); + DEVICE_SPECS.put("Galaxy A21s", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("Galaxy A22", new DeviceSpec("android", 358, 857)); + DEVICE_SPECS.put("Galaxy A31", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("Galaxy A32", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("Galaxy A51", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("Galaxy A7", new DeviceSpec("android", 412, 846)); + DEVICE_SPECS.put("Galaxy A70", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("Galaxy A8", new DeviceSpec("android", 360, 740)); + DEVICE_SPECS.put("Galaxy A8 Plus", new DeviceSpec("android", 412, 846)); + DEVICE_SPECS.put("Galaxy J7 Prime", new DeviceSpec("android", 360, 640)); + DEVICE_SPECS.put("Galaxy M12", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("Galaxy M31", new DeviceSpec("android", 412, 892)); + DEVICE_SPECS.put("Galaxy Note10", new DeviceSpec("android", 412, 869)); + DEVICE_SPECS.put("Galaxy Note10 Plus", new DeviceSpec("android", 412, 869)); + DEVICE_SPECS.put("Galaxy Note20", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("Galaxy Note20 Ultra", new DeviceSpec("android", 412, 869)); + DEVICE_SPECS.put("Galaxy S10", new DeviceSpec("android", 360, 760)); + DEVICE_SPECS.put("Galaxy S10 Plus", new DeviceSpec("android", 412, 869)); + DEVICE_SPECS.put("Galaxy S10e", new DeviceSpec("android", 412, 740)); + DEVICE_SPECS.put("Galaxy S20", new DeviceSpec("android", 360, 800)); + DEVICE_SPECS.put("Galaxy S20 FE", new DeviceSpec("android", 412, 914)); + DEVICE_SPECS.put("Galaxy S20 Ultra", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("Galaxy S20 Plus", new DeviceSpec("android", 384, 854)); + DEVICE_SPECS.put("Galaxy S21", new DeviceSpec("android", 360, 800)); + DEVICE_SPECS.put("Galaxy S21 FE", new DeviceSpec("android", 360, 780)); + DEVICE_SPECS.put("Galaxy S21 Ultra", new DeviceSpec("android", 384, 854)); + DEVICE_SPECS.put("Galaxy S21 Plus", new DeviceSpec("android", 360, 800)); + DEVICE_SPECS.put("Galaxy S22", new DeviceSpec("android", 360, 780)); + DEVICE_SPECS.put("Galaxy S22 Ultra", new DeviceSpec("android", 384, 854)); + DEVICE_SPECS.put("Galaxy S23", new DeviceSpec("android", 360, 645)); + DEVICE_SPECS.put("Galaxy S23 Plus", new DeviceSpec("android", 360, 648)); + DEVICE_SPECS.put("Galaxy S23 Ultra", new DeviceSpec("android", 384, 689)); + DEVICE_SPECS.put("Galaxy S24", new DeviceSpec("android", 360, 780)); + DEVICE_SPECS.put("Galaxy S24 Plus", new DeviceSpec("android", 384, 832)); + DEVICE_SPECS.put("Galaxy S24 Ultra", new DeviceSpec("android", 384, 832)); + DEVICE_SPECS.put("Galaxy S7", new DeviceSpec("android", 360, 640)); + DEVICE_SPECS.put("Galaxy S7 Edge", new DeviceSpec("android", 360, 640)); + DEVICE_SPECS.put("Galaxy S8", new DeviceSpec("android", 360, 740)); + DEVICE_SPECS.put("Galaxy S8 Plus", new DeviceSpec("android", 360, 740)); + DEVICE_SPECS.put("Galaxy S9", new DeviceSpec("android", 360, 740)); + DEVICE_SPECS.put("Galaxy S9 Plus", new DeviceSpec("android", 360, 740)); + DEVICE_SPECS.put("Galaxy Tab A7 Lite", new DeviceSpec("android", 534, 894)); + DEVICE_SPECS.put("Galaxy Tab A8", new DeviceSpec("android", 800, 1280)); + DEVICE_SPECS.put("Galaxy Tab S3", new DeviceSpec("android", 1024, 768)); + DEVICE_SPECS.put("Galaxy Tab S4", new DeviceSpec("android", 712, 1138)); + DEVICE_SPECS.put("Galaxy Tab S7", new DeviceSpec("android", 800, 1192)); + DEVICE_SPECS.put("Galaxy Tab S8", new DeviceSpec("android", 753, 1205)); + DEVICE_SPECS.put("Galaxy Tab S8 Plus", new DeviceSpec("android", 825, 1318)); + DEVICE_SPECS.put("Huawei Mate 20 Pro", new DeviceSpec("android", 360, 780)); + DEVICE_SPECS.put("Huawei P20 Pro", new DeviceSpec("android", 360, 747)); + DEVICE_SPECS.put("Huawei P30", new DeviceSpec("android", 360, 780)); + DEVICE_SPECS.put("Huawei P30 Pro", new DeviceSpec("android", 360, 780)); + DEVICE_SPECS.put("Microsoft Surface Duo", new DeviceSpec("android", 1114, 705)); + DEVICE_SPECS.put("Moto G7 Play", new DeviceSpec("android", 360, 760)); + DEVICE_SPECS.put("Moto G9 Play", new DeviceSpec("android", 393, 786)); + DEVICE_SPECS.put("Moto G Stylus (2022)", new DeviceSpec("android", 432, 984)); + DEVICE_SPECS.put("Nexus 5", new DeviceSpec("android", 360, 640)); + DEVICE_SPECS.put("Nexus 5X", new DeviceSpec("android", 412, 732)); + DEVICE_SPECS.put("Nokia 5", new DeviceSpec("android", 360, 640)); + DEVICE_SPECS.put("Nothing Phone (1)", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("OnePlus 10 Pro", new DeviceSpec("android", 412, 919)); + DEVICE_SPECS.put("OnePlus 11", new DeviceSpec("android", 360, 804)); + DEVICE_SPECS.put("OnePlus 6", new DeviceSpec("android", 412, 869)); + DEVICE_SPECS.put("OnePlus 6T", new DeviceSpec("android", 412, 892)); + DEVICE_SPECS.put("OnePlus 7", new DeviceSpec("android", 412, 892)); + DEVICE_SPECS.put("OnePlus 7T", new DeviceSpec("android", 412, 914)); + DEVICE_SPECS.put("OnePlus 8", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("OnePlus 9", new DeviceSpec("android", 411, 915)); + DEVICE_SPECS.put("OnePlus 9 Pro", new DeviceSpec("android", 412, 919)); + DEVICE_SPECS.put("OnePlus Nord", new DeviceSpec("android", 412, 914)); + DEVICE_SPECS.put("OnePlus Nord 2", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("OnePlus Nord CE", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("Oppo A12", new DeviceSpec("android", 360, 760)); + DEVICE_SPECS.put("Oppo A15", new DeviceSpec("android", 360, 800)); + DEVICE_SPECS.put("Oppo A54", new DeviceSpec("android", 360, 800)); + DEVICE_SPECS.put("Oppo A5s", new DeviceSpec("android", 360, 760)); + DEVICE_SPECS.put("Oppo F17", new DeviceSpec("android", 360, 800)); + DEVICE_SPECS.put("Oppo K10", new DeviceSpec("android", 360, 804)); + DEVICE_SPECS.put("Pixel 3", new DeviceSpec("android", 412, 823)); + DEVICE_SPECS.put("Pixel 3 XL", new DeviceSpec("android", 412, 846)); + DEVICE_SPECS.put("Pixel 3a", new DeviceSpec("android", 412, 823)); + DEVICE_SPECS.put("Pixel 4", new DeviceSpec("android", 392, 830)); + DEVICE_SPECS.put("Pixel 4 XL", new DeviceSpec("android", 412, 823)); + DEVICE_SPECS.put("Pixel 4a", new DeviceSpec("android", 393, 851)); + DEVICE_SPECS.put("Pixel 5", new DeviceSpec("android", 393, 851)); + DEVICE_SPECS.put("Pixel 6", new DeviceSpec("android", 393, 786)); + DEVICE_SPECS.put("Pixel 6 Pro", new DeviceSpec("android", 412, 892)); + DEVICE_SPECS.put("Pixel 7", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("Pixel 7 Pro", new DeviceSpec("android", 412, 892)); + DEVICE_SPECS.put("Pixel 8", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("Pixel 8 Pro", new DeviceSpec("android", 448, 998)); + DEVICE_SPECS.put("Poco M2 Pro", new DeviceSpec("android", 393, 873)); + DEVICE_SPECS.put("POCO X3 Pro", new DeviceSpec("android", 393, 873)); + DEVICE_SPECS.put("Realme 5i", new DeviceSpec("android", 360, 800)); + DEVICE_SPECS.put("Realme 7i", new DeviceSpec("android", 360, 800)); + DEVICE_SPECS.put("Realme 8i", new DeviceSpec("android", 360, 804)); + DEVICE_SPECS.put("Realme C21Y", new DeviceSpec("android", 360, 800)); + DEVICE_SPECS.put("Realme C21", new DeviceSpec("android", 360, 800)); + DEVICE_SPECS.put("Realme GT2 Pro", new DeviceSpec("android", 360, 804)); + DEVICE_SPECS.put("Redmi 8", new DeviceSpec("android", 360, 760)); + DEVICE_SPECS.put("Redmi 9", new DeviceSpec("android", 360, 800)); + DEVICE_SPECS.put("Redmi 9C", new DeviceSpec("android", 360, 800)); + DEVICE_SPECS.put("Redmi Note 10 Pro", new DeviceSpec("android", 393, 873)); + DEVICE_SPECS.put("Redmi Note 8", new DeviceSpec("android", 393, 851)); + DEVICE_SPECS.put("Redmi Note 8 Pro", new DeviceSpec("android", 393, 851)); + DEVICE_SPECS.put("Redmi Note 9", new DeviceSpec("android", 393, 851)); + DEVICE_SPECS.put("Redmi Note 9 Pro Max", new DeviceSpec("android", 393, 873)); + DEVICE_SPECS.put("Redmi Y2", new DeviceSpec("android", 360, 720)); + DEVICE_SPECS.put("Tecno Spark 7", new DeviceSpec("android", 360, 800)); + DEVICE_SPECS.put("Vivo Y22", new DeviceSpec("android", 385, 860)); + DEVICE_SPECS.put("Vivo T1", new DeviceSpec("android", 393, 873)); + DEVICE_SPECS.put("Vivo V7", new DeviceSpec("android", 360, 720)); + DEVICE_SPECS.put("Vivo Y11", new DeviceSpec("android", 360, 722)); + DEVICE_SPECS.put("Vivo Y12", new DeviceSpec("android", 360, 722)); + DEVICE_SPECS.put("Vivo Y20g", new DeviceSpec("android", 385, 854)); + DEVICE_SPECS.put("Vivo Y50", new DeviceSpec("android", 393, 786)); + DEVICE_SPECS.put("Xiaomi 12 Pro", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("Xperia Z5", new DeviceSpec("android", 360, 640)); + DEVICE_SPECS.put("Xperia Z5 Dual", new DeviceSpec("android", 360, 640)); + DEVICE_SPECS.put("Zenfone 6", new DeviceSpec("android", 412, 892)); + DEVICE_SPECS.put("iPad 10.2 (2019)", new DeviceSpec("ios", 810, 1080)); + DEVICE_SPECS.put("iPad 10.2 (2020)", new DeviceSpec("ios", 834, 1194)); + DEVICE_SPECS.put("iPad 10.2 (2021)", new DeviceSpec("ios", 810, 1080)); + DEVICE_SPECS.put("iPad 9.7 (2017)", new DeviceSpec("ios", 768, 1024)); + DEVICE_SPECS.put("iPad Air (2019)", new DeviceSpec("ios", 834, 1112)); + DEVICE_SPECS.put("iPad Air (2020)", new DeviceSpec("ios", 820, 1180)); + DEVICE_SPECS.put("iPad Air (2022)", new DeviceSpec("ios", 820, 1180)); + DEVICE_SPECS.put("iPad mini (2019)", new DeviceSpec("ios", 768, 1024)); + DEVICE_SPECS.put("iPad mini (2021)", new DeviceSpec("ios", 744, 1133)); + DEVICE_SPECS.put("iPad Pro 11 (2021)", new DeviceSpec("ios", 834, 1194)); + DEVICE_SPECS.put("iPad Pro 11 (2022)", new DeviceSpec("ios", 834, 1194)); + DEVICE_SPECS.put("iPad Pro 12.9 (2018)", new DeviceSpec("ios", 1024, 1366)); + DEVICE_SPECS.put("iPad Pro 12.9 (2020)", new DeviceSpec("ios", 1024, 1366)); + DEVICE_SPECS.put("iPad Pro 12.9 (2021)", new DeviceSpec("ios", 1024, 1366)); + DEVICE_SPECS.put("iPad Pro 12.9 (2022)", new DeviceSpec("ios", 1024, 1366)); + DEVICE_SPECS.put("iPhone 11", new DeviceSpec("ios", 375, 812)); + DEVICE_SPECS.put("iPhone 11 Pro", new DeviceSpec("ios", 375, 812)); + DEVICE_SPECS.put("iPhone 11 Pro Max", new DeviceSpec("ios", 414, 896)); + DEVICE_SPECS.put("iPhone 12", new DeviceSpec("ios", 390, 844)); + DEVICE_SPECS.put("iPhone 12 Mini", new DeviceSpec("ios", 375, 812)); + DEVICE_SPECS.put("iPhone 12 Pro", new DeviceSpec("ios", 390, 844)); + DEVICE_SPECS.put("iPhone 12 Pro Max", new DeviceSpec("ios", 428, 926)); + DEVICE_SPECS.put("iPhone 13", new DeviceSpec("ios", 390, 844)); + DEVICE_SPECS.put("iPhone 13 Mini", new DeviceSpec("ios", 390, 844)); + DEVICE_SPECS.put("iPhone 13 Pro", new DeviceSpec("ios", 390, 844)); + DEVICE_SPECS.put("iPhone 13 Pro Max", new DeviceSpec("ios", 428, 926)); + DEVICE_SPECS.put("iPhone 14", new DeviceSpec("ios", 390, 844)); + DEVICE_SPECS.put("iPhone 14 Plus", new DeviceSpec("ios", 428, 926)); + DEVICE_SPECS.put("iPhone 14 Pro", new DeviceSpec("ios", 390, 844)); + DEVICE_SPECS.put("iPhone 14 Pro Max", new DeviceSpec("ios", 428, 928)); + DEVICE_SPECS.put("iPhone 15", new DeviceSpec("ios", 393, 852)); + DEVICE_SPECS.put("iPhone 15 Plus", new DeviceSpec("ios", 430, 932)); + DEVICE_SPECS.put("iPhone 15 Pro", new DeviceSpec("ios", 393, 852)); + DEVICE_SPECS.put("iPhone 15 Pro Max", new DeviceSpec("ios", 430, 932)); + DEVICE_SPECS.put("iPhone 6", new DeviceSpec("ios", 375, 667)); + DEVICE_SPECS.put("iPhone 6s", new DeviceSpec("ios", 375, 667)); + DEVICE_SPECS.put("iPhone 6s Plus", new DeviceSpec("ios", 414, 736)); + DEVICE_SPECS.put("iPhone 7", new DeviceSpec("ios", 375, 667)); + DEVICE_SPECS.put("iPhone 7 Plus", new DeviceSpec("ios", 414, 736)); + DEVICE_SPECS.put("iPhone 8", new DeviceSpec("ios", 375, 667)); + DEVICE_SPECS.put("iPhone 8 Plus", new DeviceSpec("ios", 414, 736)); + DEVICE_SPECS.put("iPhone SE (2016)", new DeviceSpec("ios", 320, 568)); + DEVICE_SPECS.put("iPhone SE (2020)", new DeviceSpec("ios", 375, 667)); + DEVICE_SPECS.put("iPhone SE (2022)", new DeviceSpec("ios", 375, 667)); + DEVICE_SPECS.put("iPhone X", new DeviceSpec("ios", 375, 812)); + DEVICE_SPECS.put("iPhone XR", new DeviceSpec("ios", 414, 896)); + DEVICE_SPECS.put("iPhone XS", new DeviceSpec("ios", 375, 812)); + DEVICE_SPECS.put("iPhone XS Max", new DeviceSpec("ios", 414, 896)); + DEVICE_SPECS.put("Galaxy A10s", new DeviceSpec("android", 360, 640)); + DEVICE_SPECS.put("Galaxy A11", new DeviceSpec("android", 412, 732)); + DEVICE_SPECS.put("Galaxy A13", new DeviceSpec("android", 412, 732)); + DEVICE_SPECS.put("Galaxy A52s 5G", new DeviceSpec("android", 384, 718)); + DEVICE_SPECS.put("Galaxy A53 5G", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("Galaxy Tab A 10.1 (2019)", new DeviceSpec("android", 800, 1280)); + DEVICE_SPECS.put("Galaxy Tab S9", new DeviceSpec("android", 753, 1069)); + DEVICE_SPECS.put("Honor X9a 5G", new DeviceSpec("android", 360, 678)); + DEVICE_SPECS.put("Huawei P30 Lite", new DeviceSpec("android", 360, 647)); + DEVICE_SPECS.put("Huawei P50 Pro", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("iPad Pro 13 (2024)", new DeviceSpec("ios", 1032, 1376)); + DEVICE_SPECS.put("iPad Pro 11 (2024)", new DeviceSpec("ios", 834, 1210)); + DEVICE_SPECS.put("iPad Air 13 (2024)", new DeviceSpec("ios", 1024, 1366)); + DEVICE_SPECS.put("iPad Air 11 (2024)", new DeviceSpec("ios", 820, 1180)); + DEVICE_SPECS.put("iPad 10.9 (2022)", new DeviceSpec("ios", 820, 1180)); + DEVICE_SPECS.put("iPhone 16", new DeviceSpec("ios", 393, 852)); + DEVICE_SPECS.put("iPhone 16 Plus", new DeviceSpec("ios", 430, 932)); + DEVICE_SPECS.put("iPhone 16 Pro", new DeviceSpec("ios", 402, 874)); + DEVICE_SPECS.put("iPhone 16 Pro Max", new DeviceSpec("ios", 440, 956)); + DEVICE_SPECS.put("Motorola Edge 40", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("Motorola Edge 30", new DeviceSpec("android", 432, 814)); + DEVICE_SPECS.put("Moto G22", new DeviceSpec("android", 412, 767)); + DEVICE_SPECS.put("Moto G54 5G", new DeviceSpec("android", 432, 810)); + DEVICE_SPECS.put("Moto G71 5G", new DeviceSpec("android", 412, 732)); + DEVICE_SPECS.put("Pixel Tablet", new DeviceSpec("android", 800, 1100)); + DEVICE_SPECS.put("Pixel 6a", new DeviceSpec("android", 412, 766)); + DEVICE_SPECS.put("Pixel 7a", new DeviceSpec("android", 412, 766)); + DEVICE_SPECS.put("Pixel 9", new DeviceSpec("android", 412, 924)); + DEVICE_SPECS.put("Pixel 9 Pro", new DeviceSpec("android", 412, 915)); + DEVICE_SPECS.put("Pixel 9 Pro XL", new DeviceSpec("android", 448, 998)); + DEVICE_SPECS.put("Redmi 9A", new DeviceSpec("android", 360, 800)); + DEVICE_SPECS.put("Redmi Note 13 Pro", new DeviceSpec("android", 412, 869)); + DEVICE_SPECS.put("Aquos Sense 5G", new DeviceSpec("android", 393, 731)); + DEVICE_SPECS.put("Xperia 10 IV", new DeviceSpec("android", 412, 832)); + DEVICE_SPECS.put("Honeywell CT40", new DeviceSpec("android", 360, 512)); + } + + public static DeviceSpec getDeviceSpec(String deviceName) { + return DEVICE_SPECS.get(deviceName); + } + + public static boolean isDeviceSupported(String deviceName) { + return DEVICE_SPECS.containsKey(deviceName); + } + + public static Map getAllDeviceSpecs() { + return new HashMap<>(DEVICE_SPECS); + } +}