Skip to content

[WIP] Implemented Espresso tests for upload with multilingual descriptions #2830

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

Merged
merged 6 commits into from
Mar 10, 2020
Merged
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
6 changes: 5 additions & 1 deletion app/proguard-rules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@
-keepattributes SourceFile,LineNumberTable
-keepattributes *Annotation*

# --- /recycler view ---
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be the opening comment, without /, and this one would go at the end of the recycler view - related block

-keep class androidx.recyclerview.widget.RecyclerView {
public androidx.recyclerview.widget.RecyclerView$ViewHolder findViewHolderForPosition(int);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you actually verify this proguard config? I think the inner class you mention in the declaration that it's a class and the method should have a return type.

Examples: https://www.guardsquare.com/en/products/proguard/manual/examples

}
# --- Parcelable ---
-keepclassmembers class * implements android.os.Parcelable {
static ** CREATOR;
}
}
23 changes: 22 additions & 1 deletion app/src/androidTest/java/fr/free/nrw/commons/UITestHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.rule.ActivityTestRule
import org.apache.commons.lang3.StringUtils
import org.hamcrest.BaseMatcher
import org.hamcrest.Description
import org.hamcrest.Matcher
import timber.log.Timber


class UITestHelper {
companion object {
fun skipWelcome() {
Expand All @@ -34,7 +38,7 @@ class UITestHelper {
closeSoftKeyboard()
onView(ViewMatchers.withId(R.id.login_button))
.perform(ViewActions.click())
sleep(5000)
sleep(10000)
} catch (ignored: NoMatchingViewException) {
}

Expand Down Expand Up @@ -68,5 +72,22 @@ class UITestHelper {
activityRule.activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
assert(activityRule.activity.requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
}

fun <T> first(matcher: Matcher<T>): Matcher<T>? {
return object : BaseMatcher<T>() {
var isFirst = true
override fun matches(item: Any): Boolean {
if (isFirst && matcher.matches(item)) {
isFirst = false
return true
}
return false
}

override fun describeTo(description: Description) {
description.appendText("should return first matching item")
}
}
}
}
}
243 changes: 202 additions & 41 deletions app/src/androidTest/java/fr/free/nrw/commons/UploadTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import android.graphics.Bitmap
import android.net.Uri
import android.os.Environment
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.Intents.intending
Expand All @@ -24,6 +26,8 @@ import androidx.test.rule.ActivityTestRule
import androidx.test.rule.GrantPermissionRule
import androidx.test.runner.AndroidJUnit4
import fr.free.nrw.commons.auth.LoginActivity
import fr.free.nrw.commons.upload.DescriptionsAdapter
import fr.free.nrw.commons.util.MyViewAction
import fr.free.nrw.commons.utils.ConfigUtils
import org.hamcrest.core.AllOf.allOf
import org.junit.After
Expand Down Expand Up @@ -65,68 +69,154 @@ class UploadTest {
}
UITestHelper.skipWelcome()
UITestHelper.loginUser()
saveToInternalStorage()
}

@After
fun teardown() {
Intents.release()
}

private fun saveToInternalStorage() {
val bitmapImage = randomBitmap
@Test
fun testUploadWithDescription() {
if (!ConfigUtils.isBetaFlavour()) {
throw Error("This test should only be run in Beta!")
}

// path to /data/data/yourapp/app_data/imageDir
val mypath = File(Environment.getExternalStorageDirectory(), "image.jpg")
setupSingleUpload("image.jpg")

Timber.d("Filepath: %s", mypath.path)
openGallery()

Timber.d("Absolute Filepath: %s", mypath.absolutePath)
// Validate that an intent to get an image is sent
intended(allOf(hasAction(Intent.ACTION_GET_CONTENT), hasType("image/*")))

// Create filename with the current time (to prevent overwrites)
val dateFormat = SimpleDateFormat("yyMMdd-hhmmss")
val commonsFileName = "MobileTest " + dateFormat.format(Date())

// Try to dismiss the error, if there is one (probably about duplicate files on Commons)
dismissWarning("Yes")

onView(allOf<View>(isDisplayed(), withId(R.id.et_title)))
.perform(replaceText(commonsFileName))

onView(allOf<View>(isDisplayed(), withId(R.id.description_item_edit_text)))
.perform(replaceText(commonsFileName))


onView(allOf(isDisplayed(), withId(R.id.btn_next)))
.perform(click())

UITestHelper.sleep(5000)
dismissWarning("Yes")

UITestHelper.sleep(3000)

onView(allOf(isDisplayed(), withId(R.id.et_search)))
.perform(replaceText("Uploaded with Mobile/Android Tests"))

UITestHelper.sleep(3000)

var fos: FileOutputStream? = null
try {
fos = FileOutputStream(mypath)
// Use the compress method on the BitMap object to write image to the OutputStream
bitmapImage.compress(Bitmap.CompressFormat.JPEG, 100, fos)
} catch (e: Exception) {
e.printStackTrace()
} finally {
try {
fos?.close()
} catch (e: IOException) {
e.printStackTrace()
}
onView(allOf(isDisplayed(), UITestHelper.first(withParent(withId(R.id.rv_categories)))))
.perform(click())
} catch (ignored: NoMatchingViewException) {
}

onView(allOf(isDisplayed(), withId(R.id.btn_next)))
.perform(click())

dismissWarning("Yes, Submit")

UITestHelper.sleep(500)

onView(allOf(isDisplayed(), withId(R.id.btn_submit)))
.perform(click())

UITestHelper.sleep(10000)

val fileUrl = "https://commons.wikimedia.beta.wmflabs.org/wiki/File:" +
commonsFileName.replace(' ', '_') + ".jpg"
Timber.i("File should be uploaded to $fileUrl")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to verify that the file upload request is triggered with the right arguments? Without making sure that the inputs are properly read, I'm not sure what exactly this is testing, besides that buttons can be clcked without crashing

}

private fun dismissWarning(warningText: String) {
try {
onView(withText(warningText))
.check(matches(isDisplayed()))
.perform(click())
} catch (ignored: NoMatchingViewException) {
}
}

@Test
fun uploadTest() {
fun testUploadWithoutDescription() {
if (!ConfigUtils.isBetaFlavour()) {
throw Error("This test should only be run in Beta!")
}

// Uri to return by our mock gallery selector
// Requires file 'image.jpg' to be placed at root of file structure
val imageUri = Uri.parse("file://mnt/sdcard/image.jpg")
setupSingleUpload("image.jpg")

// Build a result to return from the Camera app
val intent = Intent()
intent.data = imageUri
val result = ActivityResult(Activity.RESULT_OK, intent)
openGallery()

// Stub out the File picker. When an intent is sent to the File picker, this tells
// Espresso to respond with the ActivityResult we just created
intending(allOf(hasAction(Intent.ACTION_GET_CONTENT), hasType("image/*"))).respondWith(result)
// Validate that an intent to get an image is sent
intended(allOf(hasAction(Intent.ACTION_GET_CONTENT), hasType("image/*")))

// Open FAB
onView(allOf<View>(withId(R.id.fab_plus), isDisplayed()))
// Create filename with the current time (to prevent overwrites)
val dateFormat = SimpleDateFormat("yyMMdd-hhmmss")
val commonsFileName = "MobileTest " + dateFormat.format(Date())

// Try to dismiss the error, if there is one (probably about duplicate files on Commons)
dismissWarning("Yes")

onView(allOf<View>(isDisplayed(), withId(R.id.et_title)))
.perform(replaceText(commonsFileName))

onView(allOf(isDisplayed(), withId(R.id.btn_next)))
.perform(click())

// Click gallery
onView(allOf<View>(withId(R.id.fab_gallery), isDisplayed()))
UITestHelper.sleep(10000)
dismissWarning("Yes")

UITestHelper.sleep(3000)

onView(allOf(isDisplayed(), withId(R.id.et_search)))
.perform(replaceText("Test"))

UITestHelper.sleep(3000)

try {
onView(allOf(isDisplayed(), UITestHelper.first(withParent(withId(R.id.rv_categories)))))
.perform(click())
} catch (ignored: NoMatchingViewException) {
}

onView(allOf(isDisplayed(), withId(R.id.btn_next)))
.perform(click())

dismissWarning("Yes, Submit")

UITestHelper.sleep(500)

onView(allOf(isDisplayed(), withId(R.id.btn_submit)))
.perform(click())

UITestHelper.sleep(10000)

val fileUrl = "https://commons.wikimedia.beta.wmflabs.org/wiki/File:" +
commonsFileName.replace(' ', '_') + ".jpg"
Timber.i("File should be uploaded to $fileUrl")
}

@Test
fun testUploadWithMultilingualDescription() {
if (!ConfigUtils.isBetaFlavour()) {
throw Error("This test should only be run in Beta!")
}

setupSingleUpload("image.jpg")

openGallery()

// Validate that an intent to get an image is sent
intended(allOf(hasAction(Intent.ACTION_GET_CONTENT), hasType("image/*")))

Expand All @@ -135,14 +225,28 @@ class UploadTest {
val commonsFileName = "MobileTest " + dateFormat.format(Date())

// Try to dismiss the error, if there is one (probably about duplicate files on Commons)
dismissWarning("Yes")
dismissWarningDialog()

onView(allOf<View>(isDisplayed(), withId(R.id.et_title)))
.perform(replaceText(commonsFileName))

onView(allOf<View>(isDisplayed(), withId(R.id.description_item_edit_text)))
.perform(replaceText(commonsFileName))
onView(withId(R.id.rv_descriptions)).perform(
RecyclerViewActions
.actionOnItemAtPosition<DescriptionsAdapter.ViewHolder>(0,
MyViewAction.typeTextInChildViewWithId(R.id.description_item_edit_text, "Test description")))

onView(withId(R.id.btn_add_description))
.perform(click())

onView(withId(R.id.rv_descriptions)).perform(
RecyclerViewActions
.actionOnItemAtPosition<DescriptionsAdapter.ViewHolder>(1,
MyViewAction.selectSpinnerItemInChildViewWithId(R.id.spinner_description_languages, 2)))

onView(withId(R.id.rv_descriptions)).perform(
RecyclerViewActions
.actionOnItemAtPosition<DescriptionsAdapter.ViewHolder>(1,
MyViewAction.typeTextInChildViewWithId(R.id.description_item_edit_text, "Description")))

onView(allOf(isDisplayed(), withId(R.id.btn_next)))
.perform(click())
Expand All @@ -153,12 +257,12 @@ class UploadTest {
UITestHelper.sleep(3000)

onView(allOf(isDisplayed(), withId(R.id.et_search)))
.perform(replaceText("Uploaded with Mobile/Android Tests"))
.perform(replaceText("Test"))

UITestHelper.sleep(3000)

try {
onView(allOf(isDisplayed(), withParent(withId(R.id.rv_categories))))
onView(allOf(isDisplayed(), UITestHelper.first(withParent(withId(R.id.rv_categories)))))
.perform(click())
} catch (ignored: NoMatchingViewException) {
}
Expand All @@ -180,12 +284,69 @@ class UploadTest {
Timber.i("File should be uploaded to $fileUrl")
}

private fun dismissWarning(warningText: String) {
private fun setupSingleUpload(imageName: String) {
saveToInternalStorage(imageName)
singleImageIntent(imageName)
}

private fun saveToInternalStorage(imageName: String) {
val bitmapImage = randomBitmap

// path to /data/data/yourapp/app_data/imageDir
val mypath = File(Environment.getExternalStorageDirectory(), imageName)

Timber.d("Filepath: %s", mypath.path)

Timber.d("Absolute Filepath: %s", mypath.absolutePath)

var fos: FileOutputStream? = null
try {
onView(withText(warningText))
fos = FileOutputStream(mypath)
// Use the compress method on the BitMap object to write image to the OutputStream
bitmapImage.compress(Bitmap.CompressFormat.JPEG, 100, fos)
} catch (e: Exception) {
e.printStackTrace()
} finally {
try {
fos?.close()
} catch (e: IOException) {
e.printStackTrace()
}

}
}

private fun singleImageIntent(imageName: String) {
// Uri to return by our mock gallery selector
// Requires file 'image.jpg' to be placed at root of file structure
val imageUri = Uri.parse("file://mnt/sdcard/$imageName")

// Build a result to return from the Camera app
val intent = Intent()
intent.data = imageUri
val result = ActivityResult(Activity.RESULT_OK, intent)

// Stub out the File picker. When an intent is sent to the File picker, this tells
// Espresso to respond with the ActivityResult we just created
intending(allOf(hasAction(Intent.ACTION_GET_CONTENT), hasType("image/*"))).respondWith(result)
}

private fun dismissWarningDialog() {
try {
onView(withText("Yes"))
.check(matches(isDisplayed()))
.perform(click())
} catch (ignored: NoMatchingViewException) {
}
}

private fun openGallery() {
// Open FAB
onView(allOf<View>(withId(R.id.fab_plus), isDisplayed()))
.perform(click())

// Click gallery
onView(allOf<View>(withId(R.id.fab_gallery), isDisplayed()))
.perform(click())
}
}
Loading