Skip to content

Commit d268bbc

Browse files
committed
PAINTROID-342 Add system integration tests and create test suite
Create WebView tests and add test suites
1 parent 93edf08 commit d268bbc

14 files changed

+457
-9
lines changed

Jenkinsfile

+30-5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pipeline {
2828
string name: 'DEBUG_LABEL', defaultValue: '', description: 'For debugging when entered will be used as label to decide on which slaves the jobs will run.'
2929
booleanParam name: 'BUILD_WITH_CATROID', defaultValue: false, description: 'When checked then the current Paintroid build will be built with the current develop branch of Catroid'
3030
string name: 'CATROID_BRANCH', defaultValue: 'develop', description: 'The branch which to build catroid with, when BUILD_WITH_CATROID is checked.'
31+
booleanParam name: 'MEDIA_GALLERY_TESTS', defaultValue: false, description: 'When checked, MediaGalleryTests will be executed.'
3132
}
3233

3334
agent {
@@ -113,18 +114,42 @@ pipeline {
113114
}
114115
}
115116

117+
stage('MediaGallery Tests') {
118+
when {
119+
expression { params.MEDIA_GALLERY_TESTS || env.BRANCH_NAME == 'develop' }
120+
}
121+
steps {
122+
catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
123+
sh "echo no | avdmanager create avd --force --name android28 --package 'system-images;android-28;default;x86_64'"
124+
sh "/home/user/android/sdk/emulator/emulator -no-window -no-boot-anim -noaudio -avd android28 > /dev/null 2>&1 &"
125+
sh '''./gradlew -PenableCoverage -Pjenkins -Pemulator=android28 -Pci createDebugCoverageReport \
126+
-Pandroid.testInstrumentationRunnerArguments.class=org.catrobat.paintroid.test.testsuites.MediaGalleryTestSuite -i'''
127+
}
128+
}
129+
post {
130+
always {
131+
sh '/home/user/android/sdk/platform-tools/adb logcat -d > logcat_media_gallery.txt'
132+
junitAndCoverage "$reports/coverage/debug/report.xml", 'mediaGallery', javaSrc
133+
archiveArtifacts 'logcat_media_gallery.txt'
134+
}
135+
failure {
136+
notifyChat(['#system-tests'])
137+
}
138+
}
139+
}
140+
116141
stage('Device Tests') {
117142
steps {
118-
sh "echo no | avdmanager create avd --force --name android28 --package 'system-images;android-28;default;x86_64'"
119-
sh "/home/user/android/sdk/emulator/emulator -no-window -no-boot-anim -noaudio -avd android28 > /dev/null 2>&1 &"
120-
sh './gradlew -PenableCoverage -Pjenkins -Pemulator=android28 -Pci createDebugCoverageReport -i'
143+
sh '''./gradlew -PenableCoverage -Pjenkins -Pemulator=android28 -Pci \
144+
createDebugCoverageReport \
145+
-Pandroid.testInstrumentationRunnerArguments.class=org.catrobat.paintroid.test.testsuites.LocalAndroidTests -i'''
121146
}
122147
post {
123148
always {
124-
sh '/home/user/android/sdk/platform-tools/adb logcat -d > logcat.txt'
149+
sh '/home/user/android/sdk/platform-tools/adb logcat -d > logcat_device.txt'
125150
sh './gradlew stopEmulator'
126151
junitAndCoverage "$reports/coverage/debug/report.xml", 'device', javaSrc
127-
archiveArtifacts 'logcat.txt'
152+
archiveArtifacts 'logcat_device.txt'
128153
}
129154
}
130155
}

Paintroid/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ dependencies {
148148
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
149149
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0'
150150
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
151+
androidTestImplementation('androidx.test.espresso:espresso-web:3.4.0')
151152
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0"
152153
testImplementation "androidx.test:core-ktx:1.4.0"
153154
implementation 'com.android.support.test.espresso:espresso-idling-resource:3.1.0'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Paintroid: An image manipulation application for Android.
3+
* Copyright (C) 2010-2023 The Catrobat Team
4+
* (<http://developer.catrobat.org/credits>)
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
package org.catrobat.paintroid.test.espresso
21+
22+
import android.graphics.Color
23+
import android.webkit.WebView
24+
import androidx.test.espresso.Espresso.onView
25+
import androidx.test.espresso.IdlingRegistry
26+
import androidx.test.espresso.IdlingResource
27+
import androidx.test.espresso.assertion.ViewAssertions
28+
import androidx.test.espresso.matcher.ViewMatchers.withId
29+
import androidx.test.espresso.web.sugar.Web.onWebView
30+
import androidx.test.espresso.web.webdriver.DriverAtoms.webClick
31+
import androidx.test.espresso.web.webdriver.DriverAtoms.findElement
32+
import androidx.test.espresso.web.webdriver.Locator
33+
import androidx.test.ext.junit.runners.AndroidJUnit4
34+
import androidx.test.rule.ActivityTestRule
35+
import kotlinx.coroutines.delay
36+
import kotlinx.coroutines.runBlocking
37+
import org.catrobat.paintroid.MainActivity
38+
import org.catrobat.paintroid.R
39+
import org.catrobat.paintroid.test.espresso.util.BitmapLocationProvider
40+
import org.catrobat.paintroid.test.espresso.util.wrappers.DrawingSurfaceInteraction
41+
import org.catrobat.paintroid.test.espresso.util.wrappers.ImportToolOptionsViewInteraction.Companion.onImportToolOptionsViewInteraction
42+
import org.catrobat.paintroid.test.espresso.util.wrappers.ToolBarViewInteraction.Companion.onToolBarView
43+
import org.catrobat.paintroid.test.espresso.util.wrappers.TopBarViewInteraction
44+
import org.catrobat.paintroid.test.testsuites.annotations.MediaGalleryTests
45+
import org.catrobat.paintroid.test.utils.ScreenshotOnFailRule
46+
import org.catrobat.paintroid.tools.ToolType
47+
import org.junit.After
48+
import org.junit.Before
49+
import org.junit.Rule
50+
import org.junit.Test
51+
import org.junit.experimental.categories.Category
52+
import org.junit.runner.RunWith
53+
54+
@RunWith(AndroidJUnit4::class)
55+
@Category(MediaGalleryTests::class)
56+
class MediaGalleryWebViewClientIntegrationTest {
57+
companion object {
58+
private const val TOP_APP_BAR_TITLE = "top-app-bar__title"
59+
private const val TOP_APP_BAR_BUTTON_SIDEBAR_TOGGLE = "top-app-bar__btn-sidebar-toggle"
60+
private const val LOGO = "logo"
61+
private const val MEDIA_FILE = "mediafile-335"
62+
}
63+
64+
@get:Rule
65+
val launchActivityRule = ActivityTestRule(
66+
MainActivity::class.java
67+
)
68+
69+
@get:Rule
70+
var screenshotOnFailRule = ScreenshotOnFailRule()
71+
72+
private lateinit var mainActivity: MainActivity
73+
private lateinit var idlingResource: IdlingResource
74+
75+
@Before
76+
fun setUp() {
77+
mainActivity = launchActivityRule.activity
78+
idlingResource = mainActivity.idlingResource
79+
IdlingRegistry.getInstance().register(idlingResource)
80+
}
81+
82+
@After
83+
fun tearDown() {
84+
IdlingRegistry.getInstance().unregister(idlingResource)
85+
}
86+
87+
@Test
88+
fun testClickingOnCatrobatCommunityClosesWebView() {
89+
onToolBarView().performSelectTool(ToolType.IMPORTPNG)
90+
onImportToolOptionsViewInteraction().performOpenStickers()
91+
clickElementInWebView(120_000, Locator.ID, TOP_APP_BAR_TITLE)
92+
checkIfViewDisappears(120_000, R.id.webview)
93+
}
94+
95+
@Test
96+
fun testClickingOnCatrobatLogoClosesWebView() {
97+
onToolBarView().performSelectTool(ToolType.IMPORTPNG)
98+
onImportToolOptionsViewInteraction().performOpenStickers()
99+
clickElementInWebView(120_000, Locator.ID, TOP_APP_BAR_BUTTON_SIDEBAR_TOGGLE)
100+
clickElementInWebView(120_000, Locator.CLASS_NAME, LOGO)
101+
checkIfViewDisappears(120_000, R.id.webview)
102+
}
103+
104+
@Test
105+
fun testImportSticker() {
106+
runBlocking {
107+
delay(3000)
108+
}
109+
DrawingSurfaceInteraction.onDrawingSurfaceView().checkPixelColor(Color.TRANSPARENT, BitmapLocationProvider.MIDDLE)
110+
onToolBarView().performSelectTool(ToolType.IMPORTPNG)
111+
onImportToolOptionsViewInteraction().performOpenStickers()
112+
clickElementInWebView(120_000, Locator.ID, MEDIA_FILE)
113+
checkIfViewDisappears(120_000, R.id.webview)
114+
runBlocking {
115+
delay(5000)
116+
}
117+
TopBarViewInteraction.onTopBarView().performClickCheckmark()
118+
DrawingSurfaceInteraction.onDrawingSurfaceView().checkPixelColorIsNotTransparent(BitmapLocationProvider.MIDDLE)
119+
}
120+
121+
@SuppressWarnings("SwallowedException")
122+
private fun clickElementInWebView(maxWaitingTimeMs: Int, locator: Locator, name: String) {
123+
val endTime = System.currentTimeMillis() + maxWaitingTimeMs
124+
do {
125+
try {
126+
onWebView().withElement(findElement(locator, name)).perform(webClick())
127+
return
128+
} catch (e: java.lang.RuntimeException) {
129+
runBlocking {
130+
delay(1000)
131+
}
132+
continue
133+
}
134+
} while (System.currentTimeMillis() <= endTime)
135+
onWebView().withElement(findElement(locator, name)).perform(webClick())
136+
}
137+
138+
private fun checkIfViewDisappears(maxWaitingTimeMs: Int, viewId: Int) {
139+
val endTime = System.currentTimeMillis() + maxWaitingTimeMs
140+
do {
141+
val view = mainActivity.findViewById<WebView>(viewId)
142+
} while (view != null && System.currentTimeMillis() <= endTime)
143+
onView(withId(viewId)).check(ViewAssertions.doesNotExist())
144+
}
145+
}

Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/screenshots/CreateMarketingScreenshots.kt Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/screenshots/CreateMarketingScreenshotsTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import tools.fastlane.screengrab.Screengrab
5353
import tools.fastlane.screengrab.locale.LocaleTestRule
5454

5555
@RunWith(AndroidJUnit4::class)
56-
class CreateMarketingScreenshots {
56+
class CreateMarketingScreenshotsTest {
5757

5858
@Rule
5959
@JvmField

Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/wrappers/DrawingSurfaceInteraction.kt

+19
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.catrobat.paintroid.test.espresso.util.wrappers
2020

21+
import android.graphics.Color
2122
import android.view.View
2223
import androidx.annotation.ColorInt
2324
import androidx.annotation.ColorRes
@@ -62,6 +63,24 @@ class DrawingSurfaceInteraction private constructor() :
6263
return this
6364
}
6465

66+
fun checkPixelColorIsNotTransparent(coordinateProvider: CoordinatesProvider): DrawingSurfaceInteraction {
67+
check(ViewAssertions.matches(object : TypeSafeMatcher<View?>() {
68+
override fun describeTo(description: Description) {
69+
description.appendText("Color at coordinates is not transparent ")
70+
}
71+
72+
override fun matchesSafely(view: View?): Boolean {
73+
val activity = MainActivityHelper.getMainActivityFromView(view!!)
74+
val currentBitmap = activity.layerModel.getBitmapOfAllLayers()
75+
val coordinates = coordinateProvider.calculateCoordinates(view)
76+
val actualColor =
77+
currentBitmap!!.getPixel(coordinates[0].toInt(), coordinates[1].toInt())
78+
return Color.TRANSPARENT != actualColor
79+
}
80+
}))
81+
return this
82+
}
83+
6584
fun checkPixelColorOnLayer(
6685
@ColorInt expectedColor: Int,
6786
coordinateProvider: CoordinatesProvider
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Paintroid: An image manipulation application for Android.
3+
* Copyright (C) 2010-2023 The Catrobat Team
4+
* (<http://developer.catrobat.org/credits>)
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
package org.catrobat.paintroid.test.espresso.util.wrappers
21+
22+
import androidx.test.espresso.Espresso.onView
23+
import androidx.test.espresso.ViewInteraction
24+
import androidx.test.espresso.action.ViewActions.click
25+
import androidx.test.espresso.matcher.ViewMatchers.withId
26+
import org.catrobat.paintroid.R
27+
28+
class ImportToolOptionsViewInteraction(viewInteraction: ViewInteraction?) : CustomViewInteraction(
29+
viewInteraction) {
30+
31+
companion object {
32+
fun onImportToolOptionsViewInteraction(): ImportToolOptionsViewInteraction =
33+
ImportToolOptionsViewInteraction(onView(withId(R.id.pocketpaint_layout_tool_specific_options)))
34+
}
35+
36+
fun performOpenStickers() {
37+
onView(withId(R.id.pocketpaint_dialog_import_stickers)).perform(click())
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Paintroid: An image manipulation application for Android.
3+
* Copyright (C) 2010-2023 The Catrobat Team
4+
* (<http://developer.catrobat.org/credits>)
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
package org.catrobat.paintroid.test.runner
21+
22+
import android.util.Log
23+
import androidx.test.platform.app.InstrumentationRegistry
24+
import dalvik.system.DexFile
25+
import org.junit.runner.Description
26+
import org.junit.runner.Runner
27+
import org.junit.runner.notification.RunNotifier
28+
import org.junit.runners.ParentRunner
29+
import org.junit.runners.model.InitializationError
30+
import org.junit.runners.model.RunnerBuilder
31+
import java.lang.annotation.Inherited
32+
import java.lang.annotation.Retention
33+
import java.lang.annotation.RetentionPolicy
34+
import java.util.Collections
35+
import kotlin.collections.ArrayList
36+
37+
class AndroidPackageRunner(klass: Class<*>, builder: RunnerBuilder) : ParentRunner<Runner>(klass) {
38+
@Retention(RetentionPolicy.RUNTIME)
39+
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
40+
@Inherited
41+
annotation class PackagePath(val value: String)
42+
43+
private val runners: List<Runner>
44+
45+
init {
46+
val suiteClasses = getAllClassesInAnnotatedPath(klass)
47+
val runners = builder.runners(klass, suiteClasses)
48+
this.runners = Collections.unmodifiableList(runners)
49+
}
50+
51+
override fun getChildren(): List<Runner> = runners
52+
53+
override fun describeChild(child: Runner): Description = child.description
54+
55+
override fun runChild(runner: Runner, notifier: RunNotifier) = runner.run(notifier)
56+
57+
companion object {
58+
private val TAG = AndroidPackageRunner::class.java.simpleName
59+
@Throws(InitializationError::class)
60+
private fun getAllClassesInAnnotatedPath(klass: Class<*>): Array<Class<*>> {
61+
val annotation = klass.getAnnotation(PackagePath::class.java)
62+
?: throw InitializationError(String.format("class '%s' must have a PackagePath annotation", klass.name))
63+
val classes = ArrayList<Class<*>>()
64+
try {
65+
val packageCodePath = InstrumentationRegistry.getInstrumentation().context.packageCodePath
66+
val dexFile = DexFile(packageCodePath)
67+
val iter = dexFile.entries()
68+
while (iter.hasMoreElements()) {
69+
val className = iter.nextElement()
70+
if (className.contains(annotation.value) && className.endsWith("Test")) {
71+
classes.add(Class.forName(className))
72+
}
73+
}
74+
} catch (e: Exception) {
75+
Log.e(TAG, e.message, e)
76+
throw InitializationError("Exception during loading Test classes from Dex")
77+
}
78+
return classes.toTypedArray()
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)