From a88aa0d6934af29b19b266dd49a8f0f24f90f55c Mon Sep 17 00:00:00 2001 From: Kerem Date: Mon, 4 Nov 2024 12:15:23 +0100 Subject: [PATCH 01/14] add diffing options --- .../saucelabs/visual/VisualCheckOptions.java | 19 +++++++++++++++++-- .../com/saucelabs/visual/VisualClient.java | 2 ++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java index e633f903..ce3ec671 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java @@ -5,6 +5,7 @@ import android.view.View; import com.saucelabs.visual.espresso.GetViewAction; +import com.saucelabs.visual.graphql.type.DiffingMethod; import com.saucelabs.visual.graphql.type.DiffingOptionsIn; import com.saucelabs.visual.graphql.type.RegionIn; import com.saucelabs.visual.model.Region; @@ -24,6 +25,7 @@ public class VisualCheckOptions { private final Boolean captureDom; private final DiffingOptionsIn diffingOptions; private final View clipElement; + private final DiffingMethod diffingMethod; private VisualCheckOptions( String testName, @@ -31,13 +33,15 @@ private VisualCheckOptions( List ignoreRegions, Boolean captureDom, DiffingOptionsIn diffingOptions, - View clipElement) { + View clipElement, + DiffingMethod diffingMethod) { this.testName = testName; this.suiteName = suiteName; this.ignoreRegions = ignoreRegions; this.captureDom = captureDom; this.diffingOptions = diffingOptions; this.clipElement = clipElement; + this.diffingMethod = diffingMethod; } public static final class Builder { @@ -47,6 +51,7 @@ public static final class Builder { private Boolean captureDom; private DiffingOptionsIn diffingOptions; private View clipElement; + private DiffingMethod diffingMethod; public Builder testName(String testName) { this.testName = testName; @@ -101,6 +106,11 @@ public Builder clipElement(View view) { return this; } + public Builder diffingMethod(DiffingMethod diffingMethod) { + this.diffingMethod = diffingMethod; + return this; + } + public VisualCheckOptions build() { return new VisualCheckOptions( testName, @@ -108,7 +118,8 @@ public VisualCheckOptions build() { ignoreRegions, captureDom, diffingOptions, - clipElement); + clipElement, + diffingMethod); } } @@ -157,6 +168,10 @@ public View getClipElement() { return clipElement; } + public DiffingMethod getDiffingMethod() { + return diffingMethod == null ? DiffingMethod.BALANCED : diffingMethod; + } + public static Builder builder() { return new Builder(); } diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java index 90f33d87..b32c0fbd 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java @@ -6,6 +6,7 @@ import com.saucelabs.visual.graphql.CreateSnapshotMutation; import com.saucelabs.visual.graphql.CreateSnapshotUploadMutation; import com.saucelabs.visual.graphql.GraphQLClient; +import com.saucelabs.visual.graphql.type.DiffingMethod; import com.saucelabs.visual.graphql.type.OperatingSystem; import com.saucelabs.visual.graphql.type.SnapshotIn; @@ -169,6 +170,7 @@ public CreateSnapshotMutation.Data sauceVisualCheck(String snapshotName, VisualC .device(Build.DEVICE) .ignoreRegions(options.getIgnoreRegions()) .diffingOptions(options.getDiffingOptions()) + .diffingMethod(options.getDiffingMethod()) .build(); return visualApi.createSnapshot(input); } From f8760db9d24b31d4feaee740cb5fc27e2e376d4f Mon Sep 17 00:00:00 2001 From: Kerem Date: Mon, 4 Nov 2024 15:28:23 +0100 Subject: [PATCH 02/14] fixes --- visual-espresso/visual/build.gradle | 19 ++++++++++++------- .../saucelabs/visual/espresso/SmokeTest.java | 4 ++-- .../com/saucelabs/visual/VisualClient.java | 3 +-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/visual-espresso/visual/build.gradle b/visual-espresso/visual/build.gradle index 040b6bd7..5352d071 100644 --- a/visual-espresso/visual/build.gradle +++ b/visual-espresso/visual/build.gradle @@ -15,8 +15,8 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" - buildConfigField "String", "SAUCE_USERNAME", getProperty("SAUCE_USERNAME", "\"${System.getenv('SAUCE_USERNAME')}\"") - buildConfigField "String", "SAUCE_ACCESS_KEY", getProperty("SAUCE_ACCESS_KEY", "\"${System.getenv('SAUCE_ACCESS_KEY')}\"") + buildConfigField "String", "SAUCE_USERNAME", "\"${getProperty("SAUCE_USERNAME", "")}\"" + buildConfigField "String", "SAUCE_ACCESS_KEY", "\"${getProperty("SAUCE_ACCESS_KEY", "")}\"" buildConfigField "String", "VERSION_NAME", "\"${android.defaultConfig.versionName}\"" } @@ -40,12 +40,17 @@ android { } String getProperty(String name, String defaultValue) { - if (!rootProject.file("./local.properties").exists()) { - return defaultValue + if (rootProject.file("./local.properties").exists()) { + def properties = new Properties() + properties.load(rootProject.file("./local.properties").newDataInputStream()) + if (properties.containsKey(name)) { + return properties[name] + } + } + if (System.getenv(name) != null && !System.getenv(name).isBlank()) { + return System.getenv(name) } - def properties = new Properties() - properties.load(rootProject.file("./local.properties").newDataInputStream()) - return properties.containsKey(name) ? properties[name] : defaultValue + return defaultValue } dependencies { diff --git a/visual-espresso/visual/src/androidTest/java/com/saucelabs/visual/espresso/SmokeTest.java b/visual-espresso/visual/src/androidTest/java/com/saucelabs/visual/espresso/SmokeTest.java index 4b541c9f..a5feee79 100644 --- a/visual-espresso/visual/src/androidTest/java/com/saucelabs/visual/espresso/SmokeTest.java +++ b/visual-espresso/visual/src/androidTest/java/com/saucelabs/visual/espresso/SmokeTest.java @@ -22,7 +22,7 @@ public class SmokeTest { BuildConfig.SAUCE_USERNAME, BuildConfig.SAUCE_ACCESS_KEY) .buildName("Smoke test") - .projectName("Espresso") + .projectName("Espresso SDK") .branchName("main") .build(); @@ -35,7 +35,7 @@ public void check() { @Test public void checkWithIgnoreRegions() { Region region = Region.builder() - .x(100).y(100).width(100).height(100) + .x(200).y(200).width(100).height(100) .build(); VisualCheckOptions options = new VisualCheckOptions.Builder() .ignore(region).build(); diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java index b32c0fbd..3e678ab8 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java @@ -6,7 +6,6 @@ import com.saucelabs.visual.graphql.CreateSnapshotMutation; import com.saucelabs.visual.graphql.CreateSnapshotUploadMutation; import com.saucelabs.visual.graphql.GraphQLClient; -import com.saucelabs.visual.graphql.type.DiffingMethod; import com.saucelabs.visual.graphql.type.OperatingSystem; import com.saucelabs.visual.graphql.type.SnapshotIn; @@ -167,7 +166,7 @@ public CreateSnapshotMutation.Data sauceVisualCheck(String snapshotName, VisualC .suiteName(options.resolveSuiteName()) .operatingSystem(OperatingSystem.ANDROID) .operatingSystemVersion(Build.VERSION.RELEASE) - .device(Build.DEVICE) + .device(Build.MANUFACTURER + " " + Build.MODEL) .ignoreRegions(options.getIgnoreRegions()) .diffingOptions(options.getDiffingOptions()) .diffingMethod(options.getDiffingMethod()) From ca2fd53f38a805d58dfdf7f803a84d9715760e8c Mon Sep 17 00:00:00 2001 From: Kerem Date: Tue, 5 Nov 2024 12:01:12 +0100 Subject: [PATCH 03/14] espresso fps --- .../java/com/saucelabs/visual/VisualApi.java | 26 +++++++++------ .../saucelabs/visual/VisualCheckOptions.java | 32 +++++++++++++++++-- .../com/saucelabs/visual/VisualClient.java | 3 +- .../visual/utils/SnapshotHelper.java | 30 +++++++++++++---- 4 files changed, 72 insertions(+), 19 deletions(-) diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java index b5b51277..4e708a24 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java @@ -39,19 +39,27 @@ VisualBuild createBuild(BuildAttributes buildAttributes) { return new VisualBuild(d); } - CreateSnapshotUploadMutation.Data uploadSnapshot(String buildId, boolean captureDom, View clipElement) { + CreateSnapshotUploadMutation.Data uploadSnapshot(String buildId, boolean captureDom, View clipElement, View scrollView) { SnapshotUploadIn input = SnapshotUploadIn.builder().buildUuid(buildId).build(); CreateSnapshotUploadMutation m = CreateSnapshotUploadMutation.builder().input(input).build(); - CreateSnapshotUploadMutation.Data d = graphQLClient.executeMutation(m); + CreateSnapshotUploadMutation.Data data = graphQLClient.executeMutation(m); + if (captureDom) { - byte[] dom = SnapshotHelper.getInstance().getDom(); - SnapshotHelper.getInstance().uploadToUrl(d.result.domUploadUrl, dom, true); + byte[] dom = SnapshotHelper.getInstance().captureDom(); + SnapshotHelper.getInstance().uploadDom(data.result.domUploadUrl, dom); + } + + byte[] screenshot; + if(clipElement != null) { + screenshot = SnapshotHelper.getInstance().captureView(clipElement); + } else if (scrollView != null) { + screenshot = SnapshotHelper.getInstance().captureView(scrollView); + } else { + screenshot = SnapshotHelper.getInstance().captureScreen(); } - byte[] screenshot = clipElement != null - ? SnapshotHelper.getInstance().getScreenshot(clipElement) - : SnapshotHelper.getInstance().getScreenshot(); - SnapshotHelper.getInstance().uploadToUrl(d.result.imageUploadUrl, screenshot, false); - return d; + SnapshotHelper.getInstance().uploadScreenshot(data.result.imageUploadUrl, screenshot); + + return data; } CreateSnapshotMutation.Data createSnapshot(SnapshotIn snapshotIn) { diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java index ce3ec671..d5626257 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java @@ -3,8 +3,12 @@ import static androidx.test.espresso.Espresso.onView; import android.view.View; +import android.widget.ScrollView; + +import androidx.core.widget.NestedScrollView; import com.saucelabs.visual.espresso.GetViewAction; +import com.saucelabs.visual.exception.VisualApiException; import com.saucelabs.visual.graphql.type.DiffingMethod; import com.saucelabs.visual.graphql.type.DiffingOptionsIn; import com.saucelabs.visual.graphql.type.RegionIn; @@ -26,6 +30,7 @@ public class VisualCheckOptions { private final DiffingOptionsIn diffingOptions; private final View clipElement; private final DiffingMethod diffingMethod; + private final View scrollView; private VisualCheckOptions( String testName, @@ -34,7 +39,8 @@ private VisualCheckOptions( Boolean captureDom, DiffingOptionsIn diffingOptions, View clipElement, - DiffingMethod diffingMethod) { + DiffingMethod diffingMethod, + View scrollView) { this.testName = testName; this.suiteName = suiteName; this.ignoreRegions = ignoreRegions; @@ -42,6 +48,7 @@ private VisualCheckOptions( this.diffingOptions = diffingOptions; this.clipElement = clipElement; this.diffingMethod = diffingMethod; + this.scrollView = scrollView; } public static final class Builder { @@ -52,6 +59,7 @@ public static final class Builder { private DiffingOptionsIn diffingOptions; private View clipElement; private DiffingMethod diffingMethod; + private View scrollView; public Builder testName(String testName) { this.testName = testName; @@ -111,6 +119,18 @@ public Builder diffingMethod(DiffingMethod diffingMethod) { return this; } + public Builder fullPageScreenshot(Matcher viewMatcher) { + GetViewAction action = new GetViewAction(); + onView(viewMatcher).perform(action); + this.scrollView = action.getView(); + return this; + } + + public Builder fullPageScreenshot(View scrollView) { + this.scrollView = scrollView; + return this; + } + public VisualCheckOptions build() { return new VisualCheckOptions( testName, @@ -119,7 +139,8 @@ public VisualCheckOptions build() { captureDom, diffingOptions, clipElement, - diffingMethod); + diffingMethod, + scrollView); } } @@ -172,6 +193,13 @@ public DiffingMethod getDiffingMethod() { return diffingMethod == null ? DiffingMethod.BALANCED : diffingMethod; } + public View getScrollView() { + if(!(scrollView instanceof ScrollView || scrollView instanceof NestedScrollView)) { + throw new VisualApiException("Full page screenshot only supports NestedScrollView or ScrollView instances"); + } + return scrollView; + } + public static Builder builder() { return new Builder(); } diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java index 3e678ab8..1bf1e984 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java @@ -156,7 +156,8 @@ public CreateSnapshotMutation.Data sauceVisualCheck(String snapshotName, VisualC CreateSnapshotUploadMutation.Data data = visualApi.uploadSnapshot( this.build.getId(), captureDom == Boolean.TRUE, - options.getClipElement() + options.getClipElement(), + options.getScrollView() ); SnapshotIn input = SnapshotIn.builder() .buildUuid(this.build.getId()) diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java index d7e51d93..72f4bdf1 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java @@ -7,7 +7,10 @@ import android.graphics.Canvas; import android.util.Base64; import android.view.View; +import android.widget.FrameLayout; +import android.widget.ScrollView; +import androidx.core.widget.NestedScrollView; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; @@ -48,12 +51,17 @@ public static SnapshotHelper getInstance() { return instance; } - public byte[] getScreenshot(View view) { + public byte[] captureView(View view) { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { // Get the width and height of the view int width = view.getWidth(); - int height = view.getHeight(); - + int height; + if(view instanceof ScrollView || view instanceof NestedScrollView) { + height = ((FrameLayout)view).getChildAt(0).getHeight(); + } + else { + height = view.getHeight(); + } // Create a bitmap with the same size as the view Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); @@ -70,7 +78,7 @@ public byte[] getScreenshot(View view) { } } - public byte[] getScreenshot() { + public byte[] captureScreen() { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); Bitmap screenshot = uiAutomation.takeScreenshot(); @@ -81,7 +89,7 @@ public byte[] getScreenshot() { } } - public byte[] getDom() { + public byte[] captureDom() { UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { device.dumpWindowHierarchy(os); @@ -144,7 +152,15 @@ private byte[] convertXMLToByteArray(DOMSource source) { } } - public void uploadToUrl(String uploadUrl, byte[] file, boolean captureDom) { + public void uploadDom(String uploadUrl, byte[] file) { + uploadToUrl(uploadUrl, file, true); + } + + public void uploadScreenshot(String uploadUrl, byte[] file) { + uploadToUrl(uploadUrl, file, false); + } + + private void uploadToUrl(String uploadUrl, byte[] file, boolean isDom) { HttpURLConnection connection = null; OutputStream os = null; try { @@ -156,7 +172,7 @@ public void uploadToUrl(String uploadUrl, byte[] file, boolean captureDom) { // Set headers connection.setRequestProperty("Content-MD5", md5Hash); - String contentType = captureDom ? "text/html" : "image/png"; + String contentType = isDom ? "text/html" : "image/png"; connection.setRequestProperty("Content-Type", contentType); connection.setDoOutput(true); From 7ff6f170bc4aeb9a1816c787d8e950f46e08fb21 Mon Sep 17 00:00:00 2001 From: Kerem Date: Tue, 5 Nov 2024 12:43:11 +0100 Subject: [PATCH 04/14] espresso fps --- .../main/java/com/saucelabs/visual/VisualApi.java | 14 +++++--------- .../com/saucelabs/visual/VisualCheckOptions.java | 2 +- .../java/com/saucelabs/visual/VisualClient.java | 5 +++-- .../saucelabs/visual/graphql/GraphQLClient.java | 6 ++---- .../com/saucelabs/visual/utils/SnapshotHelper.java | 10 +++++----- 5 files changed, 16 insertions(+), 21 deletions(-) diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java index 4e708a24..433ed045 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java @@ -39,7 +39,7 @@ VisualBuild createBuild(BuildAttributes buildAttributes) { return new VisualBuild(d); } - CreateSnapshotUploadMutation.Data uploadSnapshot(String buildId, boolean captureDom, View clipElement, View scrollView) { + CreateSnapshotUploadMutation.Data uploadSnapshot(String buildId, boolean captureDom, View view) { SnapshotUploadIn input = SnapshotUploadIn.builder().buildUuid(buildId).build(); CreateSnapshotUploadMutation m = CreateSnapshotUploadMutation.builder().input(input).build(); CreateSnapshotUploadMutation.Data data = graphQLClient.executeMutation(m); @@ -49,14 +49,10 @@ CreateSnapshotUploadMutation.Data uploadSnapshot(String buildId, boolean capture SnapshotHelper.getInstance().uploadDom(data.result.domUploadUrl, dom); } - byte[] screenshot; - if(clipElement != null) { - screenshot = SnapshotHelper.getInstance().captureView(clipElement); - } else if (scrollView != null) { - screenshot = SnapshotHelper.getInstance().captureView(scrollView); - } else { - screenshot = SnapshotHelper.getInstance().captureScreen(); - } + byte[] screenshot = view != null + ? SnapshotHelper.getInstance().captureView(view) + : SnapshotHelper.getInstance().captureScreen(); + SnapshotHelper.getInstance().uploadScreenshot(data.result.imageUploadUrl, screenshot); return data; diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java index d5626257..d19c3f8e 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java @@ -194,7 +194,7 @@ public DiffingMethod getDiffingMethod() { } public View getScrollView() { - if(!(scrollView instanceof ScrollView || scrollView instanceof NestedScrollView)) { + if (!(scrollView instanceof ScrollView || scrollView instanceof NestedScrollView)) { throw new VisualApiException("Full page screenshot only supports NestedScrollView or ScrollView instances"); } return scrollView; diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java index 1bf1e984..64d3605d 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java @@ -1,6 +1,7 @@ package com.saucelabs.visual; import android.os.Build; +import android.view.View; import com.saucelabs.visual.VisualBuild.BuildAttributes; import com.saucelabs.visual.graphql.CreateSnapshotMutation; @@ -153,11 +154,11 @@ public CreateSnapshotMutation.Data sauceVisualCheck(String snapshotName) { */ public CreateSnapshotMutation.Data sauceVisualCheck(String snapshotName, VisualCheckOptions options) { Boolean captureDom = options.getCaptureDom() != null ? options.getCaptureDom() : this.captureDom; + View view = options.getClipElement() != null ? options.getClipElement() : options.getScrollView(); CreateSnapshotUploadMutation.Data data = visualApi.uploadSnapshot( this.build.getId(), captureDom == Boolean.TRUE, - options.getClipElement(), - options.getScrollView() + view ); SnapshotIn input = SnapshotIn.builder() .buildUuid(this.build.getId()) diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/graphql/GraphQLClient.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/graphql/GraphQLClient.java index 6c6e52c8..14972d92 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/graphql/GraphQLClient.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/graphql/GraphQLClient.java @@ -50,8 +50,7 @@ public D executeQuery(Query q) { try { ApolloResponse response = single.blockingGet(); return handleResponse(response); - } - catch (ApolloHttpException e) { + } catch (ApolloHttpException e) { throw new VisualApiException(e.getMessage()); } } @@ -63,8 +62,7 @@ public D executeMutation(Mutation m) { try { ApolloResponse response = single.blockingGet(); return handleResponse(response); - } - catch (ApolloHttpException e) { + } catch (ApolloHttpException e) { throw new VisualApiException(e.getMessage()); } } diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java index 72f4bdf1..65e376f1 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java @@ -53,15 +53,15 @@ public static SnapshotHelper getInstance() { public byte[] captureView(View view) { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { - // Get the width and height of the view + int width = view.getWidth(); int height; - if(view instanceof ScrollView || view instanceof NestedScrollView) { - height = ((FrameLayout)view).getChildAt(0).getHeight(); - } - else { + if (view instanceof ScrollView || view instanceof NestedScrollView) { + height = ((FrameLayout) view).getChildAt(0).getHeight(); + } else { height = view.getHeight(); } + // Create a bitmap with the same size as the view Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); From dc7bbcb8e6954a2bfbaa3b59848301ef8bb4b6e0 Mon Sep 17 00:00:00 2001 From: Kerem Date: Tue, 5 Nov 2024 13:44:43 +0100 Subject: [PATCH 05/14] espresso fps --- .../main/java/com/saucelabs/visual/VisualApi.java | 14 +++++++++----- .../com/saucelabs/visual/VisualCheckOptions.java | 4 ++-- .../java/com/saucelabs/visual/VisualClient.java | 4 ++-- .../com/saucelabs/visual/utils/SnapshotHelper.java | 4 ++-- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java index 433ed045..eef69793 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java @@ -39,7 +39,7 @@ VisualBuild createBuild(BuildAttributes buildAttributes) { return new VisualBuild(d); } - CreateSnapshotUploadMutation.Data uploadSnapshot(String buildId, boolean captureDom, View view) { + CreateSnapshotUploadMutation.Data uploadSnapshot(String buildId, boolean captureDom, View clipElement, View scrollView) { SnapshotUploadIn input = SnapshotUploadIn.builder().buildUuid(buildId).build(); CreateSnapshotUploadMutation m = CreateSnapshotUploadMutation.builder().input(input).build(); CreateSnapshotUploadMutation.Data data = graphQLClient.executeMutation(m); @@ -49,10 +49,14 @@ CreateSnapshotUploadMutation.Data uploadSnapshot(String buildId, boolean capture SnapshotHelper.getInstance().uploadDom(data.result.domUploadUrl, dom); } - byte[] screenshot = view != null - ? SnapshotHelper.getInstance().captureView(view) - : SnapshotHelper.getInstance().captureScreen(); - + byte[] screenshot; + if(clipElement != null) { + screenshot = SnapshotHelper.getInstance().captureView(clipElement, false); + } else if(scrollView != null) { + screenshot = SnapshotHelper.getInstance().captureView(scrollView, true); + } else { + screenshot = SnapshotHelper.getInstance().captureScreen(); + } SnapshotHelper.getInstance().uploadScreenshot(data.result.imageUploadUrl, screenshot); return data; diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java index d19c3f8e..a74ad35d 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java @@ -170,7 +170,7 @@ public String resolveSuiteName() { } public List getIgnoreRegions() { - if (clipElement == null) { + if (clipElement == null && scrollView == null) { ignoreRegions.add(BarRegionHelper.getStatusBarRegion()); ignoreRegions.add(BarRegionHelper.getNavigationBarRegion()); } @@ -194,7 +194,7 @@ public DiffingMethod getDiffingMethod() { } public View getScrollView() { - if (!(scrollView instanceof ScrollView || scrollView instanceof NestedScrollView)) { + if (scrollView != null && !(scrollView instanceof ScrollView || scrollView instanceof NestedScrollView)) { throw new VisualApiException("Full page screenshot only supports NestedScrollView or ScrollView instances"); } return scrollView; diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java index 64d3605d..dacd7289 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java @@ -154,11 +154,11 @@ public CreateSnapshotMutation.Data sauceVisualCheck(String snapshotName) { */ public CreateSnapshotMutation.Data sauceVisualCheck(String snapshotName, VisualCheckOptions options) { Boolean captureDom = options.getCaptureDom() != null ? options.getCaptureDom() : this.captureDom; - View view = options.getClipElement() != null ? options.getClipElement() : options.getScrollView(); CreateSnapshotUploadMutation.Data data = visualApi.uploadSnapshot( this.build.getId(), captureDom == Boolean.TRUE, - view + options.getClipElement(), + options.getScrollView() ); SnapshotIn input = SnapshotIn.builder() .buildUuid(this.build.getId()) diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java index 65e376f1..35155ec0 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java @@ -51,12 +51,12 @@ public static SnapshotHelper getInstance() { return instance; } - public byte[] captureView(View view) { + public byte[] captureView(View view, boolean isFullPage) { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { int width = view.getWidth(); int height; - if (view instanceof ScrollView || view instanceof NestedScrollView) { + if (isFullPage && view instanceof FrameLayout) { height = ((FrameLayout) view).getChildAt(0).getHeight(); } else { height = view.getHeight(); From 7f699063b93d062541fd3cf863d467a828e09b97 Mon Sep 17 00:00:00 2001 From: Kerem Date: Tue, 5 Nov 2024 18:56:42 +0100 Subject: [PATCH 06/14] espresso fps --- visual-espresso/visual/build.gradle | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/visual-espresso/visual/build.gradle b/visual-espresso/visual/build.gradle index 5352d071..f3878a2f 100644 --- a/visual-espresso/visual/build.gradle +++ b/visual-espresso/visual/build.gradle @@ -32,6 +32,8 @@ android { } publishing { singleVariant("release") { + withSourcesJar() + withJavadocJar() } } buildFeatures { @@ -77,8 +79,6 @@ publishing { version = android.defaultConfig.versionName afterEvaluate { from components.release - artifact sourcesJar - artifact javadocJar } pom { name = "${artifactName}" @@ -111,27 +111,6 @@ publishing { } } -// Task for generating sources JAR -tasks.register('sourcesJar', Jar) { - archiveClassifier.set("sources") - from android.sourceSets.main.java.srcDirs -} - -// Task for generating Javadoc JAR -tasks.register('javadocJar', Jar) { - archiveClassifier.set("javadoc") - dependsOn "androidJavadocs" - from androidJavadocs.destinationDir -} - -// Android-specific Javadoc generation task -tasks.register('androidJavadocs', Javadoc) { - source = android.sourceSets.main.java.srcDirs - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) - destinationDir = file("../javadoc/") - failOnError false -} - apollo { service("service") { packageName.set("com.saucelabs.visual.graphql") From a2fcd4bbeb63739ee11883d145f32bf2a2f88124 Mon Sep 17 00:00:00 2001 From: Kerem Date: Wed, 6 Nov 2024 14:12:14 +0100 Subject: [PATCH 07/14] enrich documentation --- .../saucelabs/visual/VisualCheckOptions.java | 68 ++++++++++++++++++- .../visual/junit/TestMetaInfoRule.java | 6 ++ .../com/saucelabs/visual/model/Region.java | 3 + 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java index a74ad35d..0006017f 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java @@ -61,16 +61,37 @@ public static final class Builder { private DiffingMethod diffingMethod; private View scrollView; + /** + * Specify the test name for your tests + *
+ * Use this method in case {@link com.saucelabs.visual.junit.TestMetaInfoRule} + * doesn't work for some reason. + * @param testName The name of your test + * @return Builder instance + */ public Builder testName(String testName) { this.testName = testName; return this; } + /** + * Specify the suite name for your tests + *
+ * Use this method in case {@link com.saucelabs.visual.junit.TestMetaInfoRule} + * doesn't work for some reason. + * @param suiteName The name of your suite + * @return Builder instance + */ public Builder suiteName(String suiteName) { this.suiteName = suiteName; return this; } + /** + * Specify a set of regions to be ignored by Sauce Visual + * @param regions Regions to be ignored + * @return Builder instance + */ public Builder ignore(Region... regions) { for (Region region : regions) { this.ignoreRegions.add(RegionInFactory.fromRegion(region)); @@ -78,6 +99,11 @@ public Builder ignore(Region... regions) { return this; } + /** + * Specify a set of {@link Matcher}s that resolve to views to be ignored by Sauce Visual + * @param viewMatchers View matchers to be ignored + * @return Builder instance + */ @SafeVarargs public final Builder ignore(Matcher... viewMatchers) { List result = new ArrayList<>(); @@ -88,6 +114,11 @@ public final Builder ignore(Matcher... viewMatchers) { return this; } + /** + * Specify a set of {@link View}s to be ignored by Sauce Visual + * @param views Views to be ignored + * @return Builder instance + */ public Builder ignore(View... views) { List result = new ArrayList<>(); for (View view : views) { @@ -97,11 +128,21 @@ public Builder ignore(View... views) { return this; } + /** + * Specify if the DOM (Android hierarchy) needs to be captured. + * @param captureDom True to capture the DOM + * @return Builder instance + */ public Builder captureDom(boolean captureDom) { this.captureDom = captureDom; return this; } + /** + * Specify a {@link Matcher} that will be resolved to a {@link View} that needs clipping + * @param viewMatcher View matchers to be clipped + * @return Builder instance + */ public Builder clipElement(Matcher viewMatcher) { GetViewAction action = new GetViewAction(); onView(viewMatcher).perform(action); @@ -109,23 +150,46 @@ public Builder clipElement(Matcher viewMatcher) { return this; } + /** + * Specify a {@link View} that needs clipping + * @param view View to be clipped + * @return Builder instance + */ public Builder clipElement(View view) { this.clipElement = view; return this; } + /** + * Specify the {@link DiffingMethod} to be used + *
+ * Default is BALANCED + * @param diffingMethod DiffingMethod to be used + * @return Builder instance + */ public Builder diffingMethod(DiffingMethod diffingMethod) { this.diffingMethod = diffingMethod; return this; } - public Builder fullPageScreenshot(Matcher viewMatcher) { + /** + * Specify a {@link Matcher} that resolves to a {@link NestedScrollView} or {@link ScrollView} + * that will be used for full page screenshots + * @param scrollViewMatcher View matchers to be used for full page screenshot + * @return Builder instance + */ + public Builder fullPageScreenshot(Matcher scrollViewMatcher) { GetViewAction action = new GetViewAction(); - onView(viewMatcher).perform(action); + onView(scrollViewMatcher).perform(action); this.scrollView = action.getView(); return this; } + /** + * Specify a {@link NestedScrollView} or {@link ScrollView} that will be used for full page screenshots + * @param scrollView View to be used for full page screenshot + * @return Builder instance + */ public Builder fullPageScreenshot(View scrollView) { this.scrollView = scrollView; return this; diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/junit/TestMetaInfoRule.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/junit/TestMetaInfoRule.java index 42c772c9..b729b267 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/junit/TestMetaInfoRule.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/junit/TestMetaInfoRule.java @@ -6,6 +6,12 @@ import org.junit.runner.Description; import org.junit.runners.model.Statement; +/** + * Initialize an instance of this rule to set the test name and suite name automatically + * for Sauce Visual Espresso tests. + * Usage: + *
@Rule public TestMetaInfoRule testMetaInfoRule = new TestMetaInfoRule();
+ */ public class TestMetaInfoRule implements TestRule { @Override diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/model/Region.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/model/Region.java index 892eefd5..605a2561 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/model/Region.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/model/Region.java @@ -1,5 +1,8 @@ package com.saucelabs.visual.model; +/** + * Denotes a Sauce Visual region that can be used as an ignore region + */ public class Region { private final String name; private final Integer x; From 3cd1a196fe369982dd307a2a5037f9b7c408aa84 Mon Sep 17 00:00:00 2001 From: Kerem Date: Wed, 6 Nov 2024 15:26:48 +0100 Subject: [PATCH 08/14] fix ignore region view matcher + FPS --- .../saucelabs/visual/VisualCheckOptions.java | 5 ++- .../visual/utils/RegionInFactory.java | 34 ++++++++++++++----- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java index 0006017f..b749ea38 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java @@ -108,7 +108,10 @@ public Builder ignore(Region... regions) { public final Builder ignore(Matcher... viewMatchers) { List result = new ArrayList<>(); for (Matcher viewMatcher : viewMatchers) { - result.add(RegionInFactory.fromViewMatcher(viewMatcher)); + RegionIn region = scrollView != null + ? RegionInFactory.fromViewMatcher(viewMatcher, scrollView) + : RegionInFactory.fromViewMatcher(viewMatcher); + result.add(region); } this.ignoreRegions.addAll(result); return this; diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java index df43d71b..f95c1c5d 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java @@ -15,7 +15,21 @@ public class RegionInFactory { public static RegionIn fromViewMatcher(Matcher viewMatcher) { - return fromViewMatcher(viewMatcher, null); + return fromViewMatcher(viewMatcher, null, null); + } + + public static RegionIn fromViewMatcher(Matcher viewMatcher, View parentView) { + return fromViewMatcher(viewMatcher, null, parentView); + } + + public static RegionIn fromViewMatcher(Matcher viewMatcher, DiffingOptionsIn diffingOptions) { + return fromViewMatcher(viewMatcher, diffingOptions, null); + } + + public static RegionIn fromViewMatcher(Matcher viewMatcher, DiffingOptionsIn diffingOptions, View parentView) { + GetViewAction action = new GetViewAction(); + onView(viewMatcher).perform(action); + return fromView(action.getView(), diffingOptions, parentView); } public static RegionIn fromView(View view) { @@ -26,21 +40,23 @@ public static RegionIn fromRegion(Region region) { return fromRegion(region, null); } - public static RegionIn fromViewMatcher(Matcher viewMatcher, DiffingOptionsIn diffingOptions) { - GetViewAction action = new GetViewAction(); - onView(viewMatcher).perform(action); - return fromView(action.getView(), diffingOptions); + public static RegionIn fromView(View view, DiffingOptionsIn diffingOptions) { + return fromView(view, diffingOptions, null); } - public static RegionIn fromView(View view, DiffingOptionsIn diffingOptions) { + public static RegionIn fromView(View view, DiffingOptionsIn diffingOptions, View parentView) { int[] loc = new int[2]; - view.getLocationOnScreen(loc); // Get the coordinates of the view on the screen + view.getLocationOnScreen(loc); + int[] parentLoc = new int[2]; + if(parentView != null) { + parentView.getLocationOnScreen(parentLoc); + } String resourceName = view.getResources().getResourceEntryName(view.getId()); return RegionIn.builder() .diffingOptions(diffingOptions) .name(resourceName) - .x(loc[0]) - .y(loc[1]) + .x(loc[0] - parentLoc[0]) + .y(loc[1] - parentLoc[1]) .width(view.getWidth()) .height(view.getHeight()) .build(); From 738c0b0ee18d81886f253ac5e23d80d9ea634f6c Mon Sep 17 00:00:00 2001 From: Kerem Date: Wed, 6 Nov 2024 15:28:10 +0100 Subject: [PATCH 09/14] lint --- .../src/main/java/com/saucelabs/visual/VisualApi.java | 4 ++-- .../java/com/saucelabs/visual/VisualCheckOptions.java | 11 +++++++++++ .../main/java/com/saucelabs/visual/VisualClient.java | 1 - .../com/saucelabs/visual/utils/RegionInFactory.java | 2 +- .../com/saucelabs/visual/utils/SnapshotHelper.java | 2 -- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java index eef69793..b64397ca 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java @@ -50,9 +50,9 @@ CreateSnapshotUploadMutation.Data uploadSnapshot(String buildId, boolean capture } byte[] screenshot; - if(clipElement != null) { + if (clipElement != null) { screenshot = SnapshotHelper.getInstance().captureView(clipElement, false); - } else if(scrollView != null) { + } else if (scrollView != null) { screenshot = SnapshotHelper.getInstance().captureView(scrollView, true); } else { screenshot = SnapshotHelper.getInstance().captureScreen(); diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java index b749ea38..d8d73764 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java @@ -66,6 +66,7 @@ public static final class Builder { *
* Use this method in case {@link com.saucelabs.visual.junit.TestMetaInfoRule} * doesn't work for some reason. + * * @param testName The name of your test * @return Builder instance */ @@ -79,6 +80,7 @@ public Builder testName(String testName) { *
* Use this method in case {@link com.saucelabs.visual.junit.TestMetaInfoRule} * doesn't work for some reason. + * * @param suiteName The name of your suite * @return Builder instance */ @@ -89,6 +91,7 @@ public Builder suiteName(String suiteName) { /** * Specify a set of regions to be ignored by Sauce Visual + * * @param regions Regions to be ignored * @return Builder instance */ @@ -101,6 +104,7 @@ public Builder ignore(Region... regions) { /** * Specify a set of {@link Matcher}s that resolve to views to be ignored by Sauce Visual + * * @param viewMatchers View matchers to be ignored * @return Builder instance */ @@ -119,6 +123,7 @@ public final Builder ignore(Matcher... viewMatchers) { /** * Specify a set of {@link View}s to be ignored by Sauce Visual + * * @param views Views to be ignored * @return Builder instance */ @@ -133,6 +138,7 @@ public Builder ignore(View... views) { /** * Specify if the DOM (Android hierarchy) needs to be captured. + * * @param captureDom True to capture the DOM * @return Builder instance */ @@ -143,6 +149,7 @@ public Builder captureDom(boolean captureDom) { /** * Specify a {@link Matcher} that will be resolved to a {@link View} that needs clipping + * * @param viewMatcher View matchers to be clipped * @return Builder instance */ @@ -155,6 +162,7 @@ public Builder clipElement(Matcher viewMatcher) { /** * Specify a {@link View} that needs clipping + * * @param view View to be clipped * @return Builder instance */ @@ -167,6 +175,7 @@ public Builder clipElement(View view) { * Specify the {@link DiffingMethod} to be used *
* Default is BALANCED + * * @param diffingMethod DiffingMethod to be used * @return Builder instance */ @@ -178,6 +187,7 @@ public Builder diffingMethod(DiffingMethod diffingMethod) { /** * Specify a {@link Matcher} that resolves to a {@link NestedScrollView} or {@link ScrollView} * that will be used for full page screenshots + * * @param scrollViewMatcher View matchers to be used for full page screenshot * @return Builder instance */ @@ -190,6 +200,7 @@ public Builder fullPageScreenshot(Matcher scrollViewMatcher) { /** * Specify a {@link NestedScrollView} or {@link ScrollView} that will be used for full page screenshots + * * @param scrollView View to be used for full page screenshot * @return Builder instance */ diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java index dacd7289..1bf1e984 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualClient.java @@ -1,7 +1,6 @@ package com.saucelabs.visual; import android.os.Build; -import android.view.View; import com.saucelabs.visual.VisualBuild.BuildAttributes; import com.saucelabs.visual.graphql.CreateSnapshotMutation; diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java index f95c1c5d..ece607a5 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java @@ -48,7 +48,7 @@ public static RegionIn fromView(View view, DiffingOptionsIn diffingOptions, View int[] loc = new int[2]; view.getLocationOnScreen(loc); int[] parentLoc = new int[2]; - if(parentView != null) { + if (parentView != null) { parentView.getLocationOnScreen(parentLoc); } String resourceName = view.getResources().getResourceEntryName(view.getId()); diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java index 35155ec0..597f115f 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/SnapshotHelper.java @@ -8,9 +8,7 @@ import android.util.Base64; import android.view.View; import android.widget.FrameLayout; -import android.widget.ScrollView; -import androidx.core.widget.NestedScrollView; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; From da4e1a584bf952631ada3643860181c50d68d547 Mon Sep 17 00:00:00 2001 From: Kerem Date: Thu, 7 Nov 2024 10:39:15 +0100 Subject: [PATCH 10/14] fix --- .../src/main/java/com/saucelabs/visual/VisualApi.java | 6 +++--- .../main/java/com/saucelabs/visual/VisualCheckOptions.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java index b64397ca..3095f223 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java @@ -50,10 +50,10 @@ CreateSnapshotUploadMutation.Data uploadSnapshot(String buildId, boolean capture } byte[] screenshot; - if (clipElement != null) { - screenshot = SnapshotHelper.getInstance().captureView(clipElement, false); - } else if (scrollView != null) { + if (scrollView != null) { screenshot = SnapshotHelper.getInstance().captureView(scrollView, true); + } else if (clipElement != null) { + screenshot = SnapshotHelper.getInstance().captureView(clipElement, false); } else { screenshot = SnapshotHelper.getInstance().captureScreen(); } diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java index d8d73764..56784e00 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java @@ -90,7 +90,7 @@ public Builder suiteName(String suiteName) { } /** - * Specify a set of regions to be ignored by Sauce Visual + * Specify a set of {@link Region}s to be ignored by Sauce Visual * * @param regions Regions to be ignored * @return Builder instance @@ -103,7 +103,7 @@ public Builder ignore(Region... regions) { } /** - * Specify a set of {@link Matcher}s that resolve to views to be ignored by Sauce Visual + * Specify a set of {@link Matcher}s that resolve to {@link View}s to be ignored by Sauce Visual * * @param viewMatchers View matchers to be ignored * @return Builder instance From 6f5b001c81927072560a269650b8a6a284837ca8 Mon Sep 17 00:00:00 2001 From: Kerem Date: Thu, 7 Nov 2024 14:31:05 +0100 Subject: [PATCH 11/14] ignore regions --- .../java/com/saucelabs/visual/VisualApi.java | 6 ++-- .../saucelabs/visual/VisualCheckOptions.java | 12 ++++++-- .../visual/utils/RegionInFactory.java | 28 +++++++++++++++---- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java index 3095f223..b64397ca 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java @@ -50,10 +50,10 @@ CreateSnapshotUploadMutation.Data uploadSnapshot(String buildId, boolean capture } byte[] screenshot; - if (scrollView != null) { - screenshot = SnapshotHelper.getInstance().captureView(scrollView, true); - } else if (clipElement != null) { + if (clipElement != null) { screenshot = SnapshotHelper.getInstance().captureView(clipElement, false); + } else if (scrollView != null) { + screenshot = SnapshotHelper.getInstance().captureView(scrollView, true); } else { screenshot = SnapshotHelper.getInstance().captureScreen(); } diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java index 56784e00..cf2ba796 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualCheckOptions.java @@ -96,8 +96,11 @@ public Builder suiteName(String suiteName) { * @return Builder instance */ public Builder ignore(Region... regions) { - for (Region region : regions) { - this.ignoreRegions.add(RegionInFactory.fromRegion(region)); + for (Region r : regions) { + RegionIn region = scrollView != null + ? RegionInFactory.fromRegion(r, scrollView) + : RegionInFactory.fromRegion(r); + this.ignoreRegions.add(region); } return this; } @@ -130,7 +133,10 @@ public final Builder ignore(Matcher... viewMatchers) { public Builder ignore(View... views) { List result = new ArrayList<>(); for (View view : views) { - result.add(RegionInFactory.fromView(view)); + RegionIn region = scrollView != null + ? RegionInFactory.fromView(view, scrollView) + : RegionInFactory.fromView(view); + result.add(region); } this.ignoreRegions.addAll(result); return this; diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java index ece607a5..453f922d 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java @@ -33,17 +33,29 @@ public static RegionIn fromViewMatcher(Matcher viewMatcher, DiffingOptions } public static RegionIn fromView(View view) { - return fromView(view, null); + return fromView(view, null, null); } - public static RegionIn fromRegion(Region region) { - return fromRegion(region, null); + public static RegionIn fromView(View view, View parentView) { + return fromView(view, null, parentView); } public static RegionIn fromView(View view, DiffingOptionsIn diffingOptions) { return fromView(view, diffingOptions, null); } + public static RegionIn fromRegion(Region region) { + return fromRegion(region, null, null); + } + + public static RegionIn fromRegion(Region region, View parentView) { + return fromRegion(region, null, parentView); + } + + public static RegionIn fromRegion(Region region, DiffingOptionsIn diffingOptions) { + return fromRegion(region, diffingOptions, null); + } + public static RegionIn fromView(View view, DiffingOptionsIn diffingOptions, View parentView) { int[] loc = new int[2]; view.getLocationOnScreen(loc); @@ -62,12 +74,16 @@ public static RegionIn fromView(View view, DiffingOptionsIn diffingOptions, View .build(); } - public static RegionIn fromRegion(Region region, DiffingOptionsIn diffingOptions) { + public static RegionIn fromRegion(Region region, DiffingOptionsIn diffingOptions, View parentView) { + int[] parentLoc = new int[2]; + if (parentView != null) { + parentView.getLocationOnScreen(parentLoc); + } return RegionIn.builder() .diffingOptions(diffingOptions) .name(region.getName()) - .x(region.getX()) - .y(region.getY()) + .x(region.getX() - parentLoc[0]) + .y(region.getY() - parentLoc[1]) .width(region.getWidth()) .height(region.getHeight()) .build(); From cbd555f0eddadbdac47bdbd8d1bcacb8ab719431 Mon Sep 17 00:00:00 2001 From: Kerem Date: Thu, 7 Nov 2024 15:09:55 +0100 Subject: [PATCH 12/14] clip & FPS logic --- .../src/main/java/com/saucelabs/visual/VisualApi.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java index b64397ca..aafdeb7c 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java @@ -50,11 +50,11 @@ CreateSnapshotUploadMutation.Data uploadSnapshot(String buildId, boolean capture } byte[] screenshot; - if (clipElement != null) { + if (clipElement != null && scrollView == null) { // Clipping only (without full page) screenshot = SnapshotHelper.getInstance().captureView(clipElement, false); - } else if (scrollView != null) { + } else if (scrollView != null) { // Full page (with or without clipping) screenshot = SnapshotHelper.getInstance().captureView(scrollView, true); - } else { + } else { // No clipping, no full page screenshot = SnapshotHelper.getInstance().captureScreen(); } SnapshotHelper.getInstance().uploadScreenshot(data.result.imageUploadUrl, screenshot); From 2f374aa6370e509516508917209606c0edb0a3eb Mon Sep 17 00:00:00 2001 From: Kerem Date: Fri, 8 Nov 2024 14:29:47 +0100 Subject: [PATCH 13/14] clip & FPS logic --- .../src/main/java/com/saucelabs/visual/VisualApi.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java index aafdeb7c..2587bfc3 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java @@ -50,9 +50,11 @@ CreateSnapshotUploadMutation.Data uploadSnapshot(String buildId, boolean capture } byte[] screenshot; - if (clipElement != null && scrollView == null) { // Clipping only (without full page) + if (clipElement != null && clipElement == scrollView) { // Clip and fps views are same + screenshot = SnapshotHelper.getInstance().captureView(clipElement, true); + } else if (clipElement != null) { // Only clipping screenshot = SnapshotHelper.getInstance().captureView(clipElement, false); - } else if (scrollView != null) { // Full page (with or without clipping) + } else if (scrollView != null) { // Full page, no clipping screenshot = SnapshotHelper.getInstance().captureView(scrollView, true); } else { // No clipping, no full page screenshot = SnapshotHelper.getInstance().captureScreen(); From 25504e3e3615684ebcc6186bdc38ca86d0b67e42 Mon Sep 17 00:00:00 2001 From: Kerem Date: Fri, 8 Nov 2024 15:11:38 +0100 Subject: [PATCH 14/14] non visible ignorable elements for fps --- .../saucelabs/visual/espresso/GetViewAction.java | 14 +++++++++++++- .../saucelabs/visual/utils/RegionInFactory.java | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/espresso/GetViewAction.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/espresso/GetViewAction.java index 13951c33..8148b0ca 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/espresso/GetViewAction.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/espresso/GetViewAction.java @@ -6,15 +6,27 @@ import androidx.test.espresso.UiController; import androidx.test.espresso.ViewAction; +import androidx.test.espresso.matcher.ViewMatchers; import org.hamcrest.Matcher; public class GetViewAction implements ViewAction { private View view; + private View parentView; + + public GetViewAction() { + } + + public GetViewAction(View parentView) { + this.parentView = parentView; + } @Override public Matcher getConstraints() { - return isDisplayed(); + if (parentView == null) { + return isDisplayed(); + } + return ViewMatchers.isAssignableFrom(View.class); } @Override diff --git a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java index 453f922d..a15a0174 100644 --- a/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java +++ b/visual-espresso/visual/src/main/java/com/saucelabs/visual/utils/RegionInFactory.java @@ -27,7 +27,7 @@ public static RegionIn fromViewMatcher(Matcher viewMatcher, DiffingOptions } public static RegionIn fromViewMatcher(Matcher viewMatcher, DiffingOptionsIn diffingOptions, View parentView) { - GetViewAction action = new GetViewAction(); + GetViewAction action = new GetViewAction(parentView); onView(viewMatcher).perform(action); return fromView(action.getView(), diffingOptions, parentView); }