Skip to content

Commit c220864

Browse files
omacrangerLogan Graham
andauthored
[Java][INT-372] Rework / add fallbacks for native ignore regions on mobile (#247)
Co-authored-by: Logan Graham <[email protected]>
1 parent 30e2bbd commit c220864

File tree

7 files changed

+244
-7
lines changed

7 files changed

+244
-7
lines changed

.github/workflows/java-build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,5 @@ jobs:
7474
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
7575
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
7676
run: |
77+
curl -u "$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" --location --request POST 'https://api.us-west-1.saucelabs.com/v1/storage/upload' --form 'payload=@"../tests/assets/SauceLabs-Demo-App.Simulator.ipa"' --form 'name="visual-sdks-ios-simulator.ipa"' -o /dev/null
7778
./mvnw -q test -Dtest=com.saucelabs.visual.integration.**
2.67 MB
Binary file not shown.

tests/assets/SauceLabs-Demo-App.ipa

4.98 MB
Binary file not shown.

visual-java/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@
6060
<version>4.32.0</version>
6161
<scope>provided</scope>
6262
</dependency>
63+
<dependency>
64+
<groupId>io.appium</groupId>
65+
<artifactId>java-client</artifactId>
66+
<version>9.5.0</version>
67+
<scope>test</scope>
68+
</dependency>
6369
<dependency>
6470
<groupId>com.saucelabs</groupId>
6571
<artifactId>saucebindings-junit5</artifactId>

visual-java/src/main/java/com/saucelabs/visual/utils/BulkDriverHelper.java

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,48 @@
22

33
import com.saucelabs.visual.exception.InvalidSelectorException;
44
import com.saucelabs.visual.graphql.type.SelectorIn;
5-
import java.util.*;
6-
import org.openqa.selenium.*;
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.stream.Collectors;
9+
import org.openqa.selenium.By;
10+
import org.openqa.selenium.Rectangle;
11+
import org.openqa.selenium.UnsupportedCommandException;
12+
import org.openqa.selenium.WebElement;
13+
import org.openqa.selenium.remote.RemoteWebDriver;
714

815
public class BulkDriverHelper {
9-
private final JavascriptExecutor driver;
16+
private final RemoteWebDriver driver;
1017

11-
public BulkDriverHelper(JavascriptExecutor javascriptExecutor) {
12-
this.driver = javascriptExecutor;
18+
public BulkDriverHelper(RemoteWebDriver driver) {
19+
this.driver = driver;
1320
}
1421

1522
public List<Rectangle> getRects(List<WebElement> elements) {
23+
try {
24+
return this.getRectsJS(elements);
25+
} catch (UnsupportedCommandException e) {
26+
return this.getRectsNative(elements);
27+
}
28+
}
29+
30+
public List<Boolean> areDisplayed(List<WebElement> elements) {
31+
try {
32+
return this.areDisplayedJS(elements);
33+
} catch (UnsupportedCommandException e) {
34+
return this.areDisplayedNative(elements);
35+
}
36+
}
37+
38+
public List<List<WebElement>> resolveElements(List<SelectorIn> selectors) {
39+
try {
40+
return this.resolveElementsJS(selectors);
41+
} catch (UnsupportedCommandException e) {
42+
return this.resolveElementsNative(selectors);
43+
}
44+
}
45+
46+
public List<Rectangle> getRectsJS(List<WebElement> elements) {
1647
final String script =
1748
"return Array.from(arguments[0]).map(function (element) {"
1849
+ " if (!element) throw new Error('element cannot be null');"
@@ -43,7 +74,7 @@ public List<Rectangle> getRects(List<WebElement> elements) {
4374
return result;
4475
}
4576

46-
public List<Boolean> areDisplayed(List<WebElement> elements) {
77+
private List<Boolean> areDisplayedJS(List<WebElement> elements) {
4778
final String script =
4879
"return Array.from(arguments[0]).map(function (element) {"
4980
+ " if (!element) throw new Error('element cannot be null');"
@@ -53,7 +84,7 @@ public List<Boolean> areDisplayed(List<WebElement> elements) {
5384
return (List<Boolean>) driver.executeScript(script, elements);
5485
}
5586

56-
public List<List<WebElement>> resolveElements(List<SelectorIn> selectors) {
87+
private List<List<WebElement>> resolveElementsJS(List<SelectorIn> selectors) {
5788
final String script =
5889
"return Array.from(arguments[0]).map(function (xpath) {"
5990
+ " var it = document.evaluate(xpath, document, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);"
@@ -79,4 +110,31 @@ public List<List<WebElement>> resolveElements(List<SelectorIn> selectors) {
79110

80111
return (List<List<WebElement>>) driver.executeScript(script, xpaths);
81112
}
113+
114+
private List<Rectangle> getRectsNative(List<WebElement> elements) {
115+
return elements.stream().map(WebElement::getRect).collect(Collectors.toList());
116+
}
117+
118+
private List<Boolean> areDisplayedNative(List<WebElement> elements) {
119+
return elements.stream().map(WebElement::isDisplayed).collect(Collectors.toList());
120+
}
121+
122+
private List<List<WebElement>> resolveElementsNative(List<SelectorIn> selectors) {
123+
return selectors.stream()
124+
.map(
125+
selector -> {
126+
By bySelector;
127+
128+
switch (selector.getType()) {
129+
case XPATH:
130+
bySelector = By.xpath(selector.getValue());
131+
break;
132+
default:
133+
throw new InvalidSelectorException(selector, "Unsupported selector type");
134+
}
135+
136+
return driver.findElements(bySelector);
137+
})
138+
.collect(Collectors.toList());
139+
}
82140
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package com.saucelabs.visual.integration;
2+
3+
import com.saucelabs.visual.VisualApi;
4+
import io.appium.java_client.AppiumBy;
5+
import io.appium.java_client.ios.IOSDriver;
6+
import java.net.MalformedURLException;
7+
import java.net.URL;
8+
import java.util.HashMap;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.Optional;
12+
import org.junit.jupiter.api.AfterAll;
13+
import org.junit.jupiter.api.BeforeAll;
14+
import org.openqa.selenium.MutableCapabilities;
15+
import org.openqa.selenium.WebElement;
16+
import org.openqa.selenium.remote.RemoteWebDriver;
17+
18+
public class IntegrationBaseNative {
19+
protected static final String username = System.getenv("SAUCE_USERNAME");
20+
protected static final String accessKey = System.getenv("SAUCE_ACCESS_KEY");
21+
protected static RemoteWebDriver driver;
22+
protected static VisualApi visual;
23+
24+
@BeforeAll
25+
public static void setUp() throws MalformedURLException {
26+
MutableCapabilities caps = new MutableCapabilities();
27+
caps.setCapability("platformName", "iOS");
28+
caps.setCapability("appium:deviceName", "iPhone Simulator");
29+
caps.setCapability("appium:platformVersion", "17.5");
30+
caps.setCapability("appium:automationName", "XCUITest");
31+
caps.setCapability("appium:app", "storage:filename=visual-sdks-ios-simulator.ipa");
32+
Map<String, Object> sauceOptions = new HashMap<>();
33+
sauceOptions.put("appiumVersion", "2.1.3");
34+
sauceOptions.put("armRequired", true);
35+
sauceOptions.put("name", "visual-sdks java-integration-test");
36+
sauceOptions.put("deviceOrientation", "PORTRAIT");
37+
caps.setCapability("sauce:options", sauceOptions);
38+
driver = new IOSDriver(getDriverUrl(), caps);
39+
visual =
40+
new VisualApi.Builder(driver, username, accessKey)
41+
.withBuild("Java integration tests")
42+
.withProject("visual-java-integration")
43+
.withCaptureDom(true)
44+
.build();
45+
}
46+
47+
@AfterAll
48+
public static void tearDown() {
49+
driver.quit();
50+
}
51+
52+
static URL getDriverUrl() throws MalformedURLException {
53+
if (username == null
54+
|| accessKey == null
55+
|| username.trim().isEmpty()
56+
|| accessKey.trim().isEmpty()) {
57+
String err =
58+
"Sauce Labs credentials not found. Please set SAUCE_USERNAME and SAUCE_ACCESS_KEY in your environment";
59+
throw new RuntimeException(err);
60+
}
61+
String dataCenter = Optional.ofNullable(System.getenv("SAUCE_REGION")).orElse("us-west-1");
62+
return new URL(
63+
"https://"
64+
+ username
65+
+ ":"
66+
+ accessKey
67+
+ "@ondemand."
68+
+ (dataCenter.isEmpty() ? "us-west-1" : dataCenter)
69+
+ ".saucelabs.com:443/wd/hub");
70+
}
71+
72+
protected static class MenuPage {
73+
public WebElement getLoginButton() {
74+
return driver.findElement(AppiumBy.accessibilityId("LogOut-menu-item"));
75+
}
76+
77+
public WebElement getMenuButton() {
78+
return driver.findElement(AppiumBy.accessibilityId("More-tab-item"));
79+
}
80+
81+
public void open() {
82+
getMenuButton().click();
83+
}
84+
85+
public void clickLoginButton() {
86+
getLoginButton().click();
87+
}
88+
}
89+
90+
protected static class LoginPage {
91+
public WebElement getBobUserButton() {
92+
return driver.findElement(AppiumBy.accessibilityId("[email protected]"));
93+
}
94+
95+
public WebElement getVisualUserButton() {
96+
return driver.findElement(AppiumBy.accessibilityId("[email protected]"));
97+
}
98+
99+
public WebElement getLoginButton() {
100+
return driver.findElement(AppiumBy.accessibilityId("Login"));
101+
}
102+
103+
public void clickBobUserButton() {
104+
getBobUserButton().click();
105+
}
106+
107+
public void clickVisualUserButton() {
108+
getVisualUserButton().click();
109+
}
110+
111+
public void clickLoginButton() {
112+
getLoginButton().click();
113+
}
114+
}
115+
116+
protected static class CatalogPage {
117+
118+
public List<WebElement> getProductImages() {
119+
return driver.findElements(AppiumBy.accessibilityId("Product Image"));
120+
}
121+
122+
public List<WebElement> getVisibleProductPrices() {
123+
return driver.findElements(AppiumBy.accessibilityId("Product Price")).subList(0, 4);
124+
}
125+
126+
public WebElement getCatalogContent() {
127+
return driver.findElement(
128+
AppiumBy.xpath(
129+
"//XCUIElementTypeOther[@name=\"Catalog-screen\"]/XCUIElementTypeOther[2]"));
130+
}
131+
132+
public WebElement getFullPageCatalog() {
133+
return driver.findElement(AppiumBy.xpath("//XCUIElementTypeCollectionView"));
134+
}
135+
}
136+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.saucelabs.visual.integration;
2+
3+
import com.saucelabs.visual.CheckOptions;
4+
import com.saucelabs.visual.junit5.TestMetaInfoExtension;
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
import org.junit.jupiter.api.Test;
8+
import org.junit.jupiter.api.extension.ExtendWith;
9+
import org.openqa.selenium.WebElement;
10+
11+
@ExtendWith({TestMetaInfoExtension.class})
12+
public class IosIT extends IntegrationBaseNative {
13+
@Test
14+
void checkAppCatalog() {
15+
visual.sauceVisualCheck("Startup");
16+
17+
MenuPage menuPage = new MenuPage();
18+
menuPage.open();
19+
menuPage.clickLoginButton();
20+
21+
LoginPage loginPage = new LoginPage();
22+
loginPage.clickBobUserButton();
23+
loginPage.clickLoginButton();
24+
25+
CatalogPage catalogPage = new CatalogPage();
26+
27+
WebElement firstImage = catalogPage.getProductImages().get(0);
28+
List<WebElement> prices = catalogPage.getVisibleProductPrices();
29+
List<WebElement> ignore = new ArrayList<>();
30+
ignore.add(firstImage);
31+
ignore.addAll(prices);
32+
33+
visual.sauceVisualCheck(
34+
"App Catalog", new CheckOptions.Builder().withIgnoreElements(ignore).build());
35+
}
36+
}

0 commit comments

Comments
 (0)