diff --git a/visual-espresso/visual/build.gradle b/visual-espresso/visual/build.gradle index 040b6bd7..f3878a2f 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}\"" } @@ -32,6 +32,8 @@ android { } publishing { singleVariant("release") { + withSourcesJar() + withJavadocJar() } } buildFeatures { @@ -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 { @@ -72,8 +79,6 @@ publishing { version = android.defaultConfig.versionName afterEvaluate { from components.release - artifact sourcesJar - artifact javadocJar } pom { name = "${artifactName}" @@ -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") 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/VisualApi.java b/visual-espresso/visual/src/main/java/com/saucelabs/visual/VisualApi.java index b5b51277..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 @@ -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) { 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..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 @@ -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; @@ -24,6 +29,8 @@ 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, @@ -31,13 +38,17 @@ private VisualCheckOptions( List 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 { @@ -47,48 +58,107 @@ 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 + *
+ * 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 {@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... 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; } + /** + * 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) { - 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 viewMatcher) { GetViewAction action = new GetViewAction(); onView(viewMatcher).perform(action); @@ -96,11 +166,55 @@ 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; + } + + /** + * 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(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, @@ -108,7 +222,9 @@ public VisualCheckOptions build() { ignoreRegions, captureDom, diffingOptions, - clipElement); + clipElement, + diffingMethod, + scrollView); } } @@ -138,7 +254,7 @@ public String resolveSuiteName() { } public List getIgnoreRegions() { - if (clipElement == null) { + if (clipElement == null && scrollView == null) { ignoreRegions.add(BarRegionHelper.getStatusBarRegion()); ignoreRegions.add(BarRegionHelper.getNavigationBarRegion()); } @@ -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(); } 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..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()) @@ -166,9 +167,10 @@ 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()) .build(); return visualApi.createSnapshot(input); } 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/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/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; 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..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 @@ -15,43 +15,75 @@ public class RegionInFactory { public static RegionIn fromViewMatcher(Matcher viewMatcher) { - return fromViewMatcher(viewMatcher, null); + return fromViewMatcher(viewMatcher, null, null); } - public static RegionIn fromView(View view) { - return fromView(view, null); + public static RegionIn fromViewMatcher(Matcher viewMatcher, View parentView) { + return fromViewMatcher(viewMatcher, null, parentView); } - public static RegionIn fromRegion(Region region) { - return fromRegion(region, null); + public static RegionIn fromViewMatcher(Matcher viewMatcher, DiffingOptionsIn diffingOptions) { + return fromViewMatcher(viewMatcher, diffingOptions, null); } - public static RegionIn fromViewMatcher(Matcher viewMatcher, DiffingOptionsIn diffingOptions) { - GetViewAction action = new GetViewAction(); + public static RegionIn fromViewMatcher(Matcher viewMatcher, DiffingOptionsIn diffingOptions, View parentView) { + GetViewAction action = new GetViewAction(parentView); onView(viewMatcher).perform(action); - return fromView(action.getView(), diffingOptions); + return fromView(action.getView(), diffingOptions, parentView); + } + + public static RegionIn fromView(View view) { + return fromView(view, null, 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); // 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(); } - 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(); 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..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 @@ -7,6 +7,7 @@ import android.graphics.Canvas; import android.util.Base64; import android.view.View; +import android.widget.FrameLayout; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; @@ -48,11 +49,16 @@ public static SnapshotHelper getInstance() { return instance; } - public byte[] getScreenshot(View view) { + public byte[] captureView(View view, boolean isFullPage) { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { - // Get the width and height of the view + int width = view.getWidth(); - int height = view.getHeight(); + int height; + if (isFullPage && view instanceof FrameLayout) { + 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 +76,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 +87,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 +150,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 +170,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);