Skip to content

Commit e65b724

Browse files
committed
chore: refactor NativeScreenshot class
1 parent fbc3767 commit e65b724

File tree

6 files changed

+133
-125
lines changed

6 files changed

+133
-125
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@
3030
example:
3131
curl -u "< username >:< password >" \
3232
-X POST "https://api-cloud.browserstack.com/app-automate/espresso/v2/build" \
33-
-d '{"app": "bs://<app_id>", "testSuite": "bs://<test_suite_id>", "devices": ["Google Pixel-8.0"], "coverage": true, "enableSpoonFramework": true }' \
33+
-d '{"app": "bs://<app_id>", "testSuite": "bs://<test_suite_id>", "devices": ["Google Pixel-8.0"], "coverage": true, "debugscreenshots": true }' \
3434
-H "Content-Type: application/json"

app/build.gradle

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
apply plugin: 'com.android.application'
2-
apply plugin: 'spoon'
32
apply plugin: 'jacoco'
43
jacoco {
54
toolVersion='0.7.5.201505241946'
@@ -8,10 +7,6 @@ buildscript {
87
repositories {
98
mavenCentral()
109
}
11-
12-
dependencies {
13-
classpath 'com.stanfy.spoon:spoon-gradle-plugin:1.2.2'
14-
}
1510
}
1611

1712
android {
@@ -46,13 +41,7 @@ dependencies {
4641
implementation 'androidx.appcompat:appcompat:1.0.0'
4742
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
4843
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
49-
androidTestImplementation 'com.squareup.spoon:spoon-client:1.7.1'
5044
androidTestImplementation 'androidx.test:rules:1.1.1'
5145
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
5246
testImplementation 'junit:junit:4.12'
53-
}
54-
55-
spoon {
56-
debug = true
57-
adbTimeout = 30
58-
}
47+
}

app/src/androidTest/java/com/sample/browserstack/samplecalculator/EnsureInputTests.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,18 @@ public void setUp() {
3737

3838
@Test
3939
public void ensureSingleInputIsHandled() {
40-
ScreenshotUtils screenshotUtils = new ScreenshotUtils();
41-
screenshotUtils.captureScreenshot("initial_state");
42-
40+
NativeScreenshot.capture("initial_state");
4341
onView(withId(R.id.buttonOne)).perform(click());
4442
onView(withId(R.id.editText)).check(matches(withText("1")));
45-
46-
screenshotUtils.captureScreenshot("post_single_btn_click");
43+
NativeScreenshot.capture("post_single_btn_click");
4744
}
4845

4946
@Test
5047
public void ensureMultipleInputIsHandled() {
51-
ScreenshotUtils screenshotUtils = new ScreenshotUtils();
52-
screenshotUtils.captureScreenshot("initial_state");
53-
48+
NativeScreenshot.capture("initial_state");
5449
onView(withId(R.id.buttonOne)).perform(click());
5550
onView(withId(R.id.buttonTwo)).perform(click());
5651
onView(withId(R.id.editText)).check(matches(withText("12")));
57-
58-
screenshotUtils.captureScreenshot("post_multiple_btn_click");
52+
NativeScreenshot.capture("post_multiple_btn_click");
5953
}
6054
}

app/src/androidTest/java/com/sample/browserstack/samplecalculator/EnsureOperationTests.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ public void setUp() {
3636
}
3737

3838
void childScreenshotMethod(String screenshotName) {
39-
ScreenshotUtils screenshotUtils = new ScreenshotUtils();
40-
screenshotUtils.captureScreenshot(screenshotName);
39+
NativeScreenshot.capture(screenshotName);
4140
}
4241

4342
void parentScreenshotMethod(String screenshotName) {
@@ -53,25 +52,20 @@ public void ensureAdditionWorks() {
5352
onView(withId(R.id.buttonOne)).perform(click());
5453
onView(withId(R.id.buttonEqual)).perform(click());
5554
onView(withId(R.id.editText)).check(matches(withText("33")));
56-
57-
ScreenshotUtils screenshotUtils = new ScreenshotUtils();
58-
screenshotUtils.captureScreenshot("post_addition");
55+
NativeScreenshot.capture("post_addition");
5956
}
6057

6158
@Test
6259
public void ensureSubtractionWorks() {
63-
ScreenshotUtils screenshotUtils = new ScreenshotUtils();
64-
screenshotUtils.captureScreenshot("pre_subtraction");
65-
60+
NativeScreenshot.capture("pre_subtraction");
6661
onView(withId(R.id.buttonTwo)).perform(click());
6762
onView(withId(R.id.buttonTwo)).perform(click());
6863
onView(withId(R.id.buttonSubtract)).perform(click());
6964
onView(withId(R.id.buttonOne)).perform(click());
7065
onView(withId(R.id.buttonOne)).perform(click());
7166
onView(withId(R.id.buttonEqual)).perform(click());
7267
onView(withId(R.id.editText)).check(matches(withText("11")));
73-
74-
screenshotUtils.captureScreenshot("post_subtraction");
68+
NativeScreenshot.capture("post_subtraction");
7569
}
7670

7771
@Test
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package com.sample.browserstack.samplecalculator;
2+
3+
import android.graphics.Bitmap;
4+
import android.os.Build;
5+
import android.os.Environment;
6+
7+
import androidx.test.runner.screenshot.BasicScreenCaptureProcessor;
8+
import androidx.test.runner.screenshot.ScreenCapture;
9+
import androidx.test.runner.screenshot.Screenshot;
10+
11+
import java.io.File;
12+
import java.io.IOException;
13+
import java.util.regex.Pattern;
14+
15+
public final class NativeScreenshot {
16+
17+
private static String methodName;
18+
private static String className;
19+
private static final Pattern SCREENSHOT_NAME_VALIDATION = Pattern.compile("[a-zA-Z0-9_-]+");
20+
21+
private NativeScreenshot() {}
22+
23+
/**
24+
* Captures screenshot using Android Screenshot library and stores in the filesystem.
25+
* Special Cases:
26+
* If the screenshotName contains spaces or does not pass validation, the corresponding
27+
* screenshot is not visible on BrowserStack's Dashboard.
28+
* If there is any runtime exception while capturing screenshot, the method throws
29+
* Exception and the test might fail if exception is not handled properly.
30+
* @param screenshotName a screenshot identifier
31+
* @return path to the screenshot file
32+
*/
33+
public static String capture(String screenshotName) {
34+
StackTraceElement testClass = findTestClassTraceElement(Thread.currentThread().getStackTrace());
35+
className = testClass.getClassName().replaceAll("[^A-Za-z0-9._-]", "_");
36+
methodName = testClass.getMethodName();
37+
EspressoScreenCaptureProcessor screenCaptureProcessor = new EspressoScreenCaptureProcessor();
38+
39+
if (!SCREENSHOT_NAME_VALIDATION.matcher(screenshotName).matches()) {
40+
throw new IllegalArgumentException("ScreenshotName must match " + SCREENSHOT_NAME_VALIDATION.pattern() + ".");
41+
} else {
42+
ScreenCapture capture = Screenshot.capture();
43+
capture.setFormat(Bitmap.CompressFormat.PNG);
44+
capture.setName(screenshotName);
45+
46+
try {
47+
return screenCaptureProcessor.process(capture);
48+
} catch (IOException e) {
49+
throw new RuntimeException("Unable to capture screenshot.", e);
50+
}
51+
}
52+
}
53+
54+
/**
55+
* Extracts the currently executing test's trace element based on the test runner
56+
* or any framework being used.
57+
* @param trace stacktrace of the currently running test
58+
* @return StackTrace Element corresponding to the current test being executed.
59+
*/
60+
private static StackTraceElement findTestClassTraceElement(StackTraceElement[] trace) {
61+
for(int i = trace.length - 1; i >= 0; --i) {
62+
StackTraceElement element = trace[i];
63+
if ("android.test.InstrumentationTestCase".equals(element.getClassName()) && "runMethod".equals(element.getMethodName())) {
64+
return extractStackElement(trace, i);
65+
}
66+
67+
if ("org.junit.runners.model.FrameworkMethod$1".equals(element.getClassName()) && "runReflectiveCall".equals(element.getMethodName())) {
68+
return extractStackElement(trace, i);
69+
}
70+
71+
if ("cucumber.runtime.model.CucumberFeature".equals(element.getClassName()) && "run".equals(element.getMethodName())) {
72+
return extractStackElement(trace, i);
73+
}
74+
}
75+
76+
throw new IllegalArgumentException("Could not find test class!");
77+
}
78+
79+
/**
80+
* Based on the test runner or framework being used, extracts the exact traceElement.
81+
* @param trace stacktrace of the currently running test
82+
* @param i a reference index
83+
* @return trace element based on the index passed
84+
*/
85+
private static StackTraceElement extractStackElement(StackTraceElement[] trace, int i) {
86+
int testClassTraceIndex = Build.VERSION.SDK_INT >= 23 ? i - 2 : i - 3;
87+
return trace[testClassTraceIndex];
88+
}
89+
90+
private static class EspressoScreenCaptureProcessor extends BasicScreenCaptureProcessor {
91+
private static final String SCREENSHOT = "screenshot";
92+
93+
/**
94+
* There are 2 kinds of directories where screenshots can be stored:
95+
* Option 1:
96+
* Path: /storage/emulated/0/Android/data/<bundleId>/files/screenshots/<className>
97+
* Code snippet:
98+
* File screenshotDir = new File(String.valueOf(ApplicationProvider.getApplicationContext().getExternalFilesDir(null)), SCREENSHOT);
99+
*
100+
* Option 2:
101+
* Path: /storage/emulated/0/Download/screenshots
102+
* Code snippet:
103+
* File screenshotDir = new File(String.valueOf(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)), SCREENSHOT);
104+
*/
105+
EspressoScreenCaptureProcessor() {
106+
File screenshotDir = new File(String.valueOf(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)), SCREENSHOT);
107+
File classDir = new File(screenshotDir, className);
108+
mDefaultScreenshotPath = new File(classDir, methodName);
109+
}
110+
111+
/**
112+
* Converts the filename to a standard path to be stored on device.
113+
* Example: "post_addition" converts to "1648038895211_post_addition"
114+
* which is later suffixed by the file extension i.e. png.
115+
* @param filename a screenshot identifier
116+
* @return custom filename format
117+
*/
118+
@Override
119+
protected String getFilename(String filename) {
120+
return System.currentTimeMillis() + "_" + filename;
121+
}
122+
}
123+
}

app/src/androidTest/java/com/sample/browserstack/samplecalculator/ScreenshotUtils.java

Lines changed: 0 additions & 92 deletions
This file was deleted.

0 commit comments

Comments
 (0)