-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[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
Changes from 1 commit
0463dd1
586836b
c198b3d
053a765
052ee6a
fc3545d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,3 +75,8 @@ | |
-keep class org.acra.** { *; } | ||
-keepattributes SourceFile,LineNumberTable | ||
-keepattributes *Annotation* | ||
|
||
# --- /recycler view --- | ||
-keep class androidx.recyclerview.widget.RecyclerView { | ||
public androidx.recyclerview.widget.RecyclerView$ViewHolder findViewHolderForPosition(int); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ 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 | ||
|
@@ -24,6 +25,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 | ||
|
@@ -65,19 +68,175 @@ class UploadTest { | |
} | ||
UITestHelper.skipWelcome() | ||
UITestHelper.loginUser() | ||
saveToInternalStorage() | ||
} | ||
|
||
@After | ||
fun teardown() { | ||
Intents.release() | ||
} | ||
|
||
private fun saveToInternalStorage() { | ||
@Test | ||
fun testUploadWithoutDescription() { | ||
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/*"))) | ||
|
||
// 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) | ||
dismissWarningDialog() | ||
|
||
onView(allOf<View>(withId(R.id.description_item_edit_text), withParent(withParent(withId(R.id.image_title_container))))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For code readability it can be a good idea to split long lines. many code repos have a particular length limit, 80 or 100 characters are common. |
||
.perform(replaceText(commonsFileName)) | ||
|
||
onView(withId(R.id.bottom_card_next)) | ||
.perform(click()) | ||
|
||
UITestHelper.sleep(1000) | ||
|
||
dismissWarningDialog() | ||
dismissWarningDialog() | ||
|
||
onView(withId(R.id.bottom_card_next)) | ||
.perform(click()) | ||
|
||
UITestHelper.sleep(1000) | ||
|
||
chooseCategoryAndLicense() | ||
|
||
val fileUrl = "https://commons.wikimedia.beta.wmflabs.org/wiki/File:" + | ||
commonsFileName.replace(' ', '_') + ".jpg" | ||
Timber.i("File should be uploaded to $fileUrl") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
} | ||
|
||
@Test | ||
fun testUploadWithDescription() { | ||
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/*"))) | ||
|
||
// 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) | ||
dismissWarningDialog() | ||
|
||
onView(allOf<View>(withId(R.id.description_item_edit_text), withParent(withParent(withId(R.id.image_title_container))))) | ||
.perform(replaceText(commonsFileName)) | ||
|
||
onView(withId(R.id.rv_descriptions)).perform( | ||
RecyclerViewActions | ||
.actionOnItemAtPosition<DescriptionsAdapter.ViewHolder>(1, | ||
MyViewAction.typeTextInChildViewWithId(R.id.description_item_edit_text, "Test description"))) | ||
|
||
onView(withId(R.id.bottom_card_next)) | ||
.perform(click()) | ||
|
||
UITestHelper.sleep(1000) | ||
|
||
dismissWarningDialog() | ||
dismissWarningDialog() | ||
|
||
onView(withId(R.id.bottom_card_next)) | ||
.perform(click()) | ||
|
||
UITestHelper.sleep(1000) | ||
|
||
chooseCategoryAndLicense() | ||
|
||
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/*"))) | ||
|
||
// 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) | ||
dismissWarningDialog() | ||
|
||
onView(allOf<View>(withId(R.id.description_item_edit_text), withParent(withParent(withId(R.id.image_title_container))))) | ||
.perform(replaceText(commonsFileName)) | ||
|
||
onView(withId(R.id.rv_descriptions)).perform( | ||
RecyclerViewActions | ||
.actionOnItemAtPosition<DescriptionsAdapter.ViewHolder>(1, | ||
MyViewAction.typeTextInChildViewWithId(R.id.description_item_edit_text, "Test description"))) | ||
|
||
onView(withId(R.id.bottom_card_add_desc)) | ||
.perform(click()) | ||
|
||
onView(withId(R.id.rv_descriptions)).perform( | ||
RecyclerViewActions | ||
.actionOnItemAtPosition<DescriptionsAdapter.ViewHolder>(2, | ||
MyViewAction.selectSpinnerItemInChildViewWithId(R.id.spinner_description_languages, 2))) | ||
|
||
onView(withId(R.id.rv_descriptions)).perform( | ||
RecyclerViewActions | ||
.actionOnItemAtPosition<DescriptionsAdapter.ViewHolder>(2, | ||
MyViewAction.typeTextInChildViewWithId(R.id.description_item_edit_text, "Description"))) | ||
|
||
onView(withId(R.id.bottom_card_next)) | ||
.perform(click()) | ||
|
||
UITestHelper.sleep(1000) | ||
|
||
dismissWarningDialog() | ||
dismissWarningDialog() | ||
|
||
onView(withId(R.id.bottom_card_next)) | ||
.perform(click()) | ||
|
||
UITestHelper.sleep(1000) | ||
|
||
chooseCategoryAndLicense() | ||
|
||
val fileUrl = "https://commons.wikimedia.beta.wmflabs.org/wiki/File:" + | ||
commonsFileName.replace(' ', '_') + ".jpg" | ||
Timber.i("File should be uploaded to $fileUrl") | ||
} | ||
|
||
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(), "image.jpg") | ||
val mypath = File(Environment.getExternalStorageDirectory(), imageName) | ||
|
||
Timber.d("Filepath: %s", mypath.path) | ||
|
||
|
@@ -100,15 +259,10 @@ class UploadTest { | |
} | ||
} | ||
|
||
@Test | ||
fun uploadTest() { | ||
if (!ConfigUtils.isBetaFlavour()) { | ||
throw Error("This test should only be run in Beta!") | ||
} | ||
|
||
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/image.jpg") | ||
val imageUri = Uri.parse("file://mnt/sdcard/$imageName") | ||
|
||
// Build a result to return from the Camera app | ||
val intent = Intent() | ||
|
@@ -118,37 +272,18 @@ class UploadTest { | |
// 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) | ||
} | ||
|
||
// 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()) | ||
|
||
// 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) | ||
private fun dismissWarningDialog() { | ||
try { | ||
onView(withText("Yes")) | ||
.check(matches(isDisplayed())) | ||
.perform(click()) | ||
} catch (ignored: NoMatchingViewException) {} | ||
|
||
onView(allOf<View>(withId(R.id.description_item_edit_text), withParent(withParent(withId(R.id.image_title_container))))) | ||
.perform(replaceText(commonsFileName)) | ||
|
||
onView(withId(R.id.bottom_card_next)) | ||
.perform(click()) | ||
|
||
UITestHelper.sleep(1000) | ||
} catch (ignored: NoMatchingViewException) { | ||
} | ||
} | ||
|
||
private fun chooseCategoryAndLicense() { | ||
onView(withId(R.id.category_search)) | ||
.perform(replaceText("Uploaded with Mobile/Android Tests")) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are all these Espresso is supposed to already make the steps synchronous and not require it for successions of UI actions: https://developer.android.com/training/testing/espresso/index.html#sync Additionally, sleep makes tests last a looooong time, so you don't run them very often, and might cause them to timeout when you do. There are utilities to wait on non-ui stuff to finish, if necessary maybe refactor to use this?https://developer.android.com/training/testing/espresso/idling-resource The no-sleep changes might be out of scope of this patch, but in that case please file a new issue to change this, it doesn't look like a good idea to have all these around to me. |
||
|
@@ -166,9 +301,15 @@ class UploadTest { | |
.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") | ||
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()) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package fr.free.nrw.commons.util | ||
|
||
import android.view.View | ||
import android.widget.EditText | ||
import androidx.appcompat.widget.AppCompatSpinner | ||
import androidx.test.espresso.UiController | ||
import androidx.test.espresso.ViewAction | ||
import org.hamcrest.Matcher | ||
|
||
class MyViewAction { | ||
companion object { | ||
fun typeTextInChildViewWithId(id: Int, textToBeTyped: String): ViewAction { | ||
return object : ViewAction { | ||
override fun getConstraints(): Matcher<View>? { | ||
return null | ||
} | ||
|
||
override fun getDescription(): String { | ||
return "Click on a child view with specified id." | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it could be useful for debugging to print the actual id here |
||
} | ||
|
||
override fun perform(uiController: UiController, view: View) { | ||
val v = view.findViewById<View>(id) as EditText | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. findViewById can take care of casting the returned view to the desired type, so give it EditText as generic type instead of casting after the fact |
||
v.setText(textToBeTyped) | ||
} | ||
} | ||
} | ||
|
||
fun selectSpinnerItemInChildViewWithId(id: Int, position: Int): ViewAction { | ||
return object : ViewAction { | ||
override fun getConstraints(): Matcher<View>? { | ||
return null | ||
} | ||
|
||
override fun getDescription(): String { | ||
return "Click on a child view with specified id." | ||
} | ||
|
||
override fun perform(uiController: UiController, view: View) { | ||
val v = view.findViewById<View>(id) as AppCompatSpinner | ||
v.setSelection(position) | ||
} | ||
} | ||
} | ||
|
||
} | ||
} |
There was a problem hiding this comment.
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