Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Espresso fps #160

Merged
merged 14 commits into from
Nov 14, 2024
44 changes: 14 additions & 30 deletions visual-espresso/visual/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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}\""
}

Expand All @@ -32,6 +32,8 @@ android {
}
publishing {
singleVariant("release") {
withSourcesJar()
withJavadocJar()
}
}
buildFeatures {
Expand All @@ -40,12 +42,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 {
Expand All @@ -72,8 +79,6 @@ publishing {
version = android.defaultConfig.versionName
afterEvaluate {
from components.release
artifact sourcesJar
artifact javadocJar
}
pom {
name = "${artifactName}"
Expand Down Expand Up @@ -106,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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,29 @@ 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 && 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, no clipping
screenshot = SnapshotHelper.getInstance().captureView(scrollView, true);
} else { // No clipping, no full page
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
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;
import com.saucelabs.visual.model.Region;
Expand All @@ -24,20 +29,26 @@ public class VisualCheckOptions {
private final Boolean captureDom;
private final DiffingOptionsIn diffingOptions;
private final View clipElement;
private final DiffingMethod diffingMethod;
private final View scrollView;

private VisualCheckOptions(
String testName,
String suiteName,
List<RegionIn> ignoreRegions,
Boolean captureDom,
DiffingOptionsIn diffingOptions,
View clipElement) {
View clipElement,
DiffingMethod diffingMethod,
View scrollView) {
this.testName = testName;
this.suiteName = suiteName;
this.ignoreRegions = ignoreRegions;
this.captureDom = captureDom;
this.diffingOptions = diffingOptions;
this.clipElement = clipElement;
this.diffingMethod = diffingMethod;
this.scrollView = scrollView;
}

public static final class Builder {
Expand All @@ -47,68 +58,173 @@ public static final class Builder {
private Boolean captureDom;
private DiffingOptionsIn diffingOptions;
private View clipElement;
private DiffingMethod diffingMethod;
private View scrollView;

/**
* Specify the test name for your tests
* <br>
* 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
* <br>
* 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 {@link Region}s 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));
for (Region r : regions) {
RegionIn region = scrollView != null
? RegionInFactory.fromRegion(r, scrollView)
: RegionInFactory.fromRegion(r);
this.ignoreRegions.add(region);
}
return this;
}

/**
* 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
*/
@SafeVarargs
public final Builder ignore(Matcher<View>... viewMatchers) {
List<RegionIn> result = new ArrayList<>();
for (Matcher<View> 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;
}

/**
* 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<RegionIn> 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;
}

/**
* 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<View> viewMatcher) {
GetViewAction action = new GetViewAction();
onView(viewMatcher).perform(action);
this.clipElement = action.getView();
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
* <br/>
* Default is BALANCED
*
* @param diffingMethod DiffingMethod to be used
* @return Builder instance
*/
public Builder diffingMethod(DiffingMethod diffingMethod) {
this.diffingMethod = diffingMethod;
return this;
}

/**
* 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<View> scrollViewMatcher) {
GetViewAction action = new GetViewAction();
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;
}

public VisualCheckOptions build() {
return new VisualCheckOptions(
testName,
suiteName,
ignoreRegions,
captureDom,
diffingOptions,
clipElement);
clipElement,
diffingMethod,
scrollView);
}

}
Expand Down Expand Up @@ -138,7 +254,7 @@ public String resolveSuiteName() {
}

public List<RegionIn> getIgnoreRegions() {
if (clipElement == null) {
if (clipElement == null && scrollView == null) {
ignoreRegions.add(BarRegionHelper.getStatusBarRegion());
ignoreRegions.add(BarRegionHelper.getNavigationBarRegion());
}
Expand All @@ -157,6 +273,17 @@ public View getClipElement() {
return clipElement;
}

public DiffingMethod getDiffingMethod() {
return diffingMethod == null ? DiffingMethod.BALANCED : diffingMethod;
}

public View getScrollView() {
if (scrollView != null && !(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();
}
Expand Down
Loading