Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions hide_keyboard/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.testsigma.addons</groupId>
<artifactId>hide_keyboard</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<testsigma.sdk.version>1.2.26_cloud</testsigma.sdk.version>
<junit.jupiter.version>5.12.1</junit.jupiter.version>
<testsigma.addon.maven.plugin>1.0.0</testsigma.addon.maven.plugin>
<maven.source.plugin.version>3.2.1</maven.source.plugin.version>
<lombok.version>1.18.30</lombok.version>
<testng.version>7.10.2</testng.version>
<selenium.version>4.33.0</selenium.version>
<appium.java.client.version>9.4.0</appium.java.client.version>
<jackson.version>2.13.0</jackson.version>
<maven.shade.plugin.version>3.2.4</maven.shade.plugin.version>

</properties>

<dependencies>
<dependency>
<groupId>com.testsigma</groupId>
<artifactId>testsigma-java-sdk</artifactId>
<version>${testsigma.sdk.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.appium/java-client -->
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>${appium.java.client.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>

</dependencies>
<build>
<finalName>hide_keyboard</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven.shade.plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven.source.plugin.version}</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
134 changes: 134 additions & 0 deletions hide_keyboard/src/main/java/com/testsigma/addons/ios/HideKeyboard.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package com.testsigma.addons.ios;

import com.testsigma.sdk.IOSAction;
import com.testsigma.sdk.Result;
import com.testsigma.sdk.annotation.Action;
import io.appium.java_client.AppiumBy;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.ios.IOSDriver;
import lombok.Data;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.Point;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Pause;
import org.openqa.selenium.interactions.PointerInput;
import org.openqa.selenium.interactions.Sequence;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;

import static java.time.Duration.ofMillis;
import static org.openqa.selenium.interactions.PointerInput.Kind.TOUCH;
import static org.openqa.selenium.interactions.PointerInput.MouseButton.LEFT;
import static org.openqa.selenium.interactions.PointerInput.Origin.viewport;

@Data
@Action(actionText = "Hide the iOS keyboard",
description = "Hides the on-screen keyboard on an iOS device.",
applicationType = com.testsigma.sdk.ApplicationType.IOS)
public class HideKeyboard extends IOSAction {

private static final String SUCCESS_MESSAGE = "Hide Keyboard executed successfully.";
private static final String FAILURE_MESSAGE = "Unable to hide keyboard. Please try executing \"Tap on element\" outside keyboard.";

@Override
public Result execute() {
logger.info("Attempting to hide the iOS keyboard");
boolean keyboardShown = true;
for (int i = 0; i < 4; i++) {
long start = System.currentTimeMillis();
if (i == 0) {
switchToActiveElementAndPressEnter();
} else if (i == 1) {
hideKeyboardByTappingOutsideKeyboard();
} else if (i == 2) {
clickOnReturnKeys();
} else {
clickOnHideKeyBoardAccessibilityID();
}
logger.info("Hiding keyboard using strategy " + i + " took " + (System.currentTimeMillis() - start) / 1000.0 + " seconds");
if (!isKeyboardShown()) {
keyboardShown = false;
break;
}
}
if (!keyboardShown) {
setSuccessMessage(SUCCESS_MESSAGE);
return Result.SUCCESS;
} else {
setErrorMessage(FAILURE_MESSAGE);
return Result.FAILED;
}
}

private void switchToActiveElementAndPressEnter() {
try {
driver.switchTo().activeElement().sendKeys(Keys.RETURN);
logger.info("Hid keyboard by switching to active element and pressing Enter");
} catch (Exception e) {
logger.info("Could not hide keyboard by switching to active element and pressing Enter: " + e.getMessage());
}
}

private void hideKeyboardByTappingOutsideKeyboard() {
String keyboardClassName = "XCUIElementTypeKeyboard";
logger.info("Trying to hide keyboard by tapping above keyboard element (class: " + keyboardClassName + ")");
try {
WebElement keyboard = driver.findElement(By.className(keyboardClassName));
Point loc = keyboard.getLocation();
PointerInput finger = new PointerInput(TOUCH, "finger");
Sequence tap = new Sequence(finger, 1)
.addAction(finger.createPointerMove(ofMillis(0), viewport(), loc.getX() + 2, loc.getY() - 2))
.addAction(finger.createPointerDown(LEFT.asArg()))
.addAction(new Pause(finger, ofMillis(1)))
.addAction(finger.createPointerUp(LEFT.asArg()));
((AppiumDriver) driver).perform(Arrays.asList(tap));
try { Thread.sleep(200); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); }
logger.info("Tapped above keyboard successfully");
} catch (Exception e) {
logger.info("Failed to hide keyboard by tapping above keyboard: " + e.getMessage());
}
}

private void clickOnReturnKeys() {
logger.info("Trying to hide keyboard by clicking Return/Done/Search/Next/Go keys");
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(1));
List.of("Return", "return", "done", "Done", "search", "Search", "Next", "next", "Go", "go").forEach(button -> {
try {
driver.findElement(By.xpath("//*[contains(@name, '" + button + "')]")).click();
} catch (Exception e) {
logger.info("XPath click failed for key '" + button + "': " + e.getMessage());
}
try {
driver.findElement(AppiumBy.name(button)).click();
} catch (Exception e) {
logger.info("Name lookup failed for key '" + button + "': " + e.getMessage());
}
});
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(0));
Comment on lines +97 to +110
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore implicit wait safely; avoid leaking driver timeout state.

Lines 110 and 127 unconditionally set implicit wait to 0. If another timeout was configured before this action, it gets clobbered and can affect later steps.

Suggested fix pattern
- driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(1));
- // ... operations ...
- driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(0));
+ Duration previous = driver.manage().timeouts().getImplicitWaitTimeout();
+ try {
+   driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(1));
+   // ... operations ...
+ } finally {
+   driver.manage().timeouts().implicitlyWait(previous);
+ }

Also applies to: 125-127

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hide_keyboard/src/main/java/com/testsigma/addons/ios/HideKeyboard.java`
around lines 97 - 110, The implicit wait is being overwritten to 0
unconditionally; wrap the short-lived change in a try/finally and restore the
prior implicit-wait after the click attempts: before calling
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(1)) capture the
current implicit wait into a variable (e.g., prevImplicit), set the 1-second
timeout, perform the List.of(...).forEach(...) clicks, and in a finally block
restore the saved prevImplicit via
driver.manage().timeouts().implicitlyWait(prevImplicit); update the code around
the driver.manage().timeouts().implicitlyWait(...) calls in HideKeyboard (the
block that sets to 1s and later to 0) so the original timeout is always
reinstated even on exceptions.

}

private void clickOnHideKeyBoardAccessibilityID() {
logger.info("Trying to hide keyboard via 'Hide keyboard' accessibility ID");
try {
driver.findElement(AppiumBy.accessibilityId("Hide keyboard")).click();
logger.info("Clicked 'Hide keyboard' accessibility ID successfully");
} catch (Exception e) {
logger.info("Failed to click 'Hide keyboard' accessibility ID: " + e.getMessage());
}
}

private boolean isKeyboardShown() {
try {
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(1));
boolean shown = ((IOSDriver) driver).isKeyboardShown();
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(0));
return shown;
} catch (Exception e) {
logger.info("Exception while checking keyboard visibility: " + e.getMessage());
return false;
}
Comment on lines +123 to +132
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not treat keyboard-check exceptions as “keyboard hidden.”

In Line 131, returning false on exception can produce a false SUCCESS in execute(). This should fail closed (or surface failure) instead of masking driver/check errors.

Suggested fix
 private boolean isKeyboardShown() {
     try {
         driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(1));
         boolean shown = ((IOSDriver) driver).isKeyboardShown();
         driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(0));
         return shown;
     } catch (Exception e) {
         logger.info("Exception while checking keyboard visibility: " + e.getMessage());
-        return false;
+        return true; // fail-closed: don't report hidden when check failed
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hide_keyboard/src/main/java/com/testsigma/addons/ios/HideKeyboard.java`
around lines 123 - 132, The isKeyboardShown() method currently catches all
exceptions and returns false, which masks driver errors and can cause execute()
to incorrectly treat failures as success; change isKeyboardShown() to stop
swallowing exceptions—catch only expected exceptions if needed but otherwise log
the error and rethrow (e.g., throw new RuntimeException(e)) so execute()
surfaces the failure instead of receiving a false; keep the implicit wait
adjustments and ensure any thrown exception carries context about the keyboard
check.

}
}
1 change: 1 addition & 0 deletions hide_keyboard/src/main/resources/testsigma-sdk.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
testsigma-sdk.api.key=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyNDBkMjhiNS0xNmUwLThlNmYtOWQ0ZS05MjYxMGNiZTcyYzciLCJ1bmlxdWVJZCI6IjYxODUiLCJpZGVudGl0eUFjY291bnRVVUlkIjoiNDMifQ.Ktf9Ejac32ZAAKDwJFa9NerBc-xymkf_msZ0sr2C-o7gQ6tI1F8UVdfUYHwjGDY2P3R_4_7JZ4dIQ-lnL5Xceg
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Remove the committed API key from source control before merge.

Line 1 exposes a real credential in plaintext. For a PUBLIC addon, this is a release blocker and a credential-compromise risk. Replace this with runtime secret injection (env var / secure vault / CI secret), rotate this key immediately, and purge it from git history.

Suggested secure change
-testsigma-sdk.api.key=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyNDBkMjhiNS0xNmUwLThlNmYtOWQ0ZS05MjYxMGNiZTcyYzciLCJ1bmlxdWVJZCI6IjYxODUiLCJpZGVudGl0eUFjY291bnRVVUlkIjoiNDMifQ.Ktf9Ejac32ZAAKDwJFa9NerBc-xymkf_msZ0sr2C-o7gQ6tI1F8UVdfUYHwjGDY2P3R_4_7JZ4dIQ-lnL5Xceg
+testsigma-sdk.api.key=${TESTSIGMA_SDK_API_KEY}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
testsigma-sdk.api.key=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyNDBkMjhiNS0xNmUwLThlNmYtOWQ0ZS05MjYxMGNiZTcyYzciLCJ1bmlxdWVJZCI6IjYxODUiLCJpZGVudGl0eUFjY291bnRVVUlkIjoiNDMifQ.Ktf9Ejac32ZAAKDwJFa9NerBc-xymkf_msZ0sr2C-o7gQ6tI1F8UVdfUYHwjGDY2P3R_4_7JZ4dIQ-lnL5Xceg
testsigma-sdk.api.key=${TESTSIGMA_SDK_API_KEY}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hide_keyboard/src/main/resources/testsigma-sdk.properties` at line 1, Remove
the hard-coded API key value for testsigma-sdk.api.key from
testsigma-sdk.properties and replace it with a runtime secret reference (e.g.,
read from an environment variable or secret manager); update any code that reads
testsigma-sdk.api.key to fallback to process/env lookup (or equivalent in your
runtime) and ensure CI injects the secret. After replacing the file, rotate the
exposed credential immediately and purge the leaked value from git history (git
filter-repo/BFG) before merging to prevent further exposure.