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

PAINTROID-342 Add system integration tests and create test suite #1298

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pipeline {
string name: 'DEBUG_LABEL', defaultValue: '', description: 'For debugging when entered will be used as label to decide on which slaves the jobs will run.'
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'
string name: 'CATROID_BRANCH', defaultValue: 'develop', description: 'The branch which to build catroid with, when BUILD_WITH_CATROID is checked.'
booleanParam name: 'MEDIA_GALLERY_TESTS', defaultValue: false, description: 'When checked, MediaGalleryTests will be executed.'
}

agent {
Expand Down Expand Up @@ -113,18 +114,42 @@ pipeline {
}
}

stage('MediaGallery Tests') {
when {
expression { params.MEDIA_GALLERY_TESTS || env.BRANCH_NAME == 'develop' }
}
steps {
catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
sh "echo no | avdmanager create avd --force --name android28 --package 'system-images;android-28;default;x86_64'"
sh "/home/user/android/sdk/emulator/emulator -no-window -no-boot-anim -noaudio -avd android28 > /dev/null 2>&1 &"
sh '''./gradlew -PenableCoverage -Pjenkins -Pemulator=android28 -Pci createDebugCoverageReport \
-Pandroid.testInstrumentationRunnerArguments.class=org.catrobat.paintroid.test.testsuites.MediaGalleryTestSuite -i'''
}
}
post {
always {
sh '/home/user/android/sdk/platform-tools/adb logcat -d > logcat_media_gallery.txt'
junitAndCoverage "$reports/coverage/debug/report.xml", 'mediaGallery', javaSrc
archiveArtifacts 'logcat_media_gallery.txt'
}
failure {
notifyChat(['#system-tests'])
}
}
}

stage('Device Tests') {
steps {
sh "echo no | avdmanager create avd --force --name android28 --package 'system-images;android-28;default;x86_64'"
sh "/home/user/android/sdk/emulator/emulator -no-window -no-boot-anim -noaudio -avd android28 > /dev/null 2>&1 &"
sh './gradlew -PenableCoverage -Pjenkins -Pemulator=android28 -Pci createDebugCoverageReport -i'
sh '''./gradlew -PenableCoverage -Pjenkins -Pemulator=android28 -Pci \
createDebugCoverageReport \
-Pandroid.testInstrumentationRunnerArguments.class=org.catrobat.paintroid.test.testsuites.LocalAndroidTests -i'''
}
post {
always {
sh '/home/user/android/sdk/platform-tools/adb logcat -d > logcat.txt'
sh '/home/user/android/sdk/platform-tools/adb logcat -d > logcat_device.txt'
sh './gradlew stopEmulator'
junitAndCoverage "$reports/coverage/debug/report.xml", 'device', javaSrc
archiveArtifacts 'logcat.txt'
archiveArtifacts 'logcat_device.txt'
}
}
}
Expand Down
1 change: 1 addition & 0 deletions Paintroid/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
androidTestImplementation('androidx.test.espresso:espresso-web:3.4.0')
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0"
testImplementation "androidx.test:core-ktx:1.4.0"
implementation 'com.android.support.test.espresso:espresso-idling-resource:3.1.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Paintroid: An image manipulation application for Android.
* Copyright (C) 2010-2023 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.catrobat.paintroid.test.espresso

import android.graphics.Color
import android.webkit.WebView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.IdlingResource
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.web.sugar.Web.onWebView
import androidx.test.espresso.web.webdriver.DriverAtoms.webClick
import androidx.test.espresso.web.webdriver.DriverAtoms.findElement
import androidx.test.espresso.web.webdriver.Locator
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.ActivityTestRule
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.catrobat.paintroid.MainActivity
import org.catrobat.paintroid.R
import org.catrobat.paintroid.test.espresso.util.BitmapLocationProvider
import org.catrobat.paintroid.test.espresso.util.wrappers.DrawingSurfaceInteraction
import org.catrobat.paintroid.test.espresso.util.wrappers.ImportToolOptionsViewInteraction.Companion.onImportToolOptionsViewInteraction
import org.catrobat.paintroid.test.espresso.util.wrappers.ToolBarViewInteraction.Companion.onToolBarView
import org.catrobat.paintroid.test.espresso.util.wrappers.TopBarViewInteraction
import org.catrobat.paintroid.test.testsuites.annotations.MediaGalleryTests
import org.catrobat.paintroid.test.utils.ScreenshotOnFailRule
import org.catrobat.paintroid.tools.ToolType
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.experimental.categories.Category
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
@Category(MediaGalleryTests::class)
class MediaGalleryWebViewClientIntegrationTest {
companion object {
private const val TOP_APP_BAR_TITLE = "top-app-bar__title"
private const val TOP_APP_BAR_BUTTON_SIDEBAR_TOGGLE = "top-app-bar__btn-sidebar-toggle"
private const val LOGO = "logo"
private const val MEDIA_FILE = "mediafile-335"
}

@get:Rule
val launchActivityRule = ActivityTestRule(
MainActivity::class.java
)

@get:Rule
var screenshotOnFailRule = ScreenshotOnFailRule()

private lateinit var mainActivity: MainActivity
private lateinit var idlingResource: IdlingResource

@Before
fun setUp() {
mainActivity = launchActivityRule.activity
idlingResource = mainActivity.idlingResource
IdlingRegistry.getInstance().register(idlingResource)
}

@After
fun tearDown() {
IdlingRegistry.getInstance().unregister(idlingResource)
}

@Test
fun testClickingOnCatrobatCommunityClosesWebView() {
onToolBarView().performSelectTool(ToolType.IMPORTPNG)
onImportToolOptionsViewInteraction().performOpenStickers()
clickElementInWebView(120_000, Locator.ID, TOP_APP_BAR_TITLE)
checkIfViewDisappears(120_000, R.id.webview)
}

@Test
fun testClickingOnCatrobatLogoClosesWebView() {
onToolBarView().performSelectTool(ToolType.IMPORTPNG)
onImportToolOptionsViewInteraction().performOpenStickers()
clickElementInWebView(120_000, Locator.ID, TOP_APP_BAR_BUTTON_SIDEBAR_TOGGLE)
clickElementInWebView(120_000, Locator.CLASS_NAME, LOGO)
checkIfViewDisappears(120_000, R.id.webview)
}

@Test
fun testImportSticker() {
runBlocking {
delay(3000)
}
DrawingSurfaceInteraction.onDrawingSurfaceView().checkPixelColor(Color.TRANSPARENT, BitmapLocationProvider.MIDDLE)
onToolBarView().performSelectTool(ToolType.IMPORTPNG)
onImportToolOptionsViewInteraction().performOpenStickers()
clickElementInWebView(120_000, Locator.ID, MEDIA_FILE)
checkIfViewDisappears(120_000, R.id.webview)
runBlocking {
delay(5000)
}
TopBarViewInteraction.onTopBarView().performClickCheckmark()
DrawingSurfaceInteraction.onDrawingSurfaceView().checkPixelColorIsNotTransparent(BitmapLocationProvider.MIDDLE)
}

@SuppressWarnings("SwallowedException")
private fun clickElementInWebView(maxWaitingTimeMs: Int, locator: Locator, name: String) {
val endTime = System.currentTimeMillis() + maxWaitingTimeMs
do {
try {
onWebView().withElement(findElement(locator, name)).perform(webClick())
return
} catch (e: java.lang.RuntimeException) {
runBlocking {
delay(1000)
}
continue
}
} while (System.currentTimeMillis() <= endTime)
onWebView().withElement(findElement(locator, name)).perform(webClick())
}

private fun checkIfViewDisappears(maxWaitingTimeMs: Int, viewId: Int) {
val endTime = System.currentTimeMillis() + maxWaitingTimeMs
do {
val view = mainActivity.findViewById<WebView>(viewId)
} while (view != null && System.currentTimeMillis() <= endTime)
onView(withId(viewId)).check(ViewAssertions.doesNotExist())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import tools.fastlane.screengrab.Screengrab
import tools.fastlane.screengrab.locale.LocaleTestRule

@RunWith(AndroidJUnit4::class)
class CreateMarketingScreenshots {
class CreateMarketingScreenshotsTest {

@Rule
@JvmField
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.catrobat.paintroid.test.espresso.util.wrappers

import android.graphics.Color
import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
Expand Down Expand Up @@ -62,6 +63,24 @@ class DrawingSurfaceInteraction private constructor() :
return this
}

fun checkPixelColorIsNotTransparent(coordinateProvider: CoordinatesProvider): DrawingSurfaceInteraction {
check(ViewAssertions.matches(object : TypeSafeMatcher<View?>() {
override fun describeTo(description: Description) {
description.appendText("Color at coordinates is not transparent ")
}

override fun matchesSafely(view: View?): Boolean {
val activity = MainActivityHelper.getMainActivityFromView(view!!)
val currentBitmap = activity.layerModel.getBitmapOfAllLayers()
val coordinates = coordinateProvider.calculateCoordinates(view)
val actualColor =
currentBitmap!!.getPixel(coordinates[0].toInt(), coordinates[1].toInt())
return Color.TRANSPARENT != actualColor
}
}))
return this
}

fun checkPixelColorOnLayer(
@ColorInt expectedColor: Int,
coordinateProvider: CoordinatesProvider
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Paintroid: An image manipulation application for Android.
* Copyright (C) 2010-2023 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.catrobat.paintroid.test.espresso.util.wrappers

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers.withId
import org.catrobat.paintroid.R

class ImportToolOptionsViewInteraction(viewInteraction: ViewInteraction?) : CustomViewInteraction(
viewInteraction) {

companion object {
fun onImportToolOptionsViewInteraction(): ImportToolOptionsViewInteraction =
ImportToolOptionsViewInteraction(onView(withId(R.id.pocketpaint_layout_tool_specific_options)))
}

fun performOpenStickers() {
onView(withId(R.id.pocketpaint_dialog_import_stickers)).perform(click())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Paintroid: An image manipulation application for Android.
* Copyright (C) 2010-2023 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.catrobat.paintroid.test.runner

import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import dalvik.system.DexFile
import org.junit.runner.Description
import org.junit.runner.Runner
import org.junit.runner.notification.RunNotifier
import org.junit.runners.ParentRunner
import org.junit.runners.model.InitializationError
import org.junit.runners.model.RunnerBuilder
import java.lang.annotation.Inherited
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.util.Collections
import kotlin.collections.ArrayList

class AndroidPackageRunner(klass: Class<*>, builder: RunnerBuilder) : ParentRunner<Runner>(klass) {
@Retention(RetentionPolicy.RUNTIME)
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
@Inherited
annotation class PackagePath(val value: String)

private val runners: List<Runner>

init {
val suiteClasses = getAllClassesInAnnotatedPath(klass)
val runners = builder.runners(klass, suiteClasses)
this.runners = Collections.unmodifiableList(runners)
}

override fun getChildren(): List<Runner> = runners

override fun describeChild(child: Runner): Description = child.description

override fun runChild(runner: Runner, notifier: RunNotifier) = runner.run(notifier)

companion object {
private val TAG = AndroidPackageRunner::class.java.simpleName
@Throws(InitializationError::class)
private fun getAllClassesInAnnotatedPath(klass: Class<*>): Array<Class<*>> {
val annotation = klass.getAnnotation(PackagePath::class.java)
?: throw InitializationError(String.format("class '%s' must have a PackagePath annotation", klass.name))
val classes = ArrayList<Class<*>>()
try {
val packageCodePath = InstrumentationRegistry.getInstrumentation().context.packageCodePath
val dexFile = DexFile(packageCodePath)
val iter = dexFile.entries()
while (iter.hasMoreElements()) {
val className = iter.nextElement()
if (className.contains(annotation.value) && className.endsWith("Test")) {
classes.add(Class.forName(className))
}
}
} catch (e: Exception) {
Log.e(TAG, e.message, e)
throw InitializationError("Exception during loading Test classes from Dex")
}
return classes.toTypedArray()
}
}
}
Loading