I am trying to test my lazy column in JetpackCompose and I keep getting this error:
[Compose-Espresso link] to become idle timed out
I tried using composeTestRule.waitforIdle() but it doesn't work. What am I missing here?
#HiltAndroidTest
class MainTest {
#get:Rule(order = 1)
var hiltTestRule = HiltAndroidRule(this)
#get:Rule(order = 2)
var composeTestRule = createAndroidComposeRule<MainActivity>()
private val context = InstrumentationRegistry.getInstrumentation().context
#Before
fun setup() {
hiltTestRule.inject()
composeTestRule.setContent {
HomePage(
context = context,
viewModel = composeTestRule.activity.viewModels<MarvelViewModel>().value,
onClick = {}
)
}
composeTestRule.onRoot().printToLog("currentLabelExists")
}
#Test
fun isResultDisplayedOnLazyColumn() {
composeTestRule.waitForIdle()
composeTestRule.onNode(hasImeAction(ImeAction.Done)).performTextInput("iron man")
composeTestRule.onNode(hasImeAction(ImeAction.Done)).performImeAction()
composeTestRule.onNodeWithTag(TAG_LAZY_COLUMN, useUnmergedTree = true).assertIsDisplayed()
I've got this issue too. The root cause was having my composeview GONE. It seems ComposeTestRule IdlingResource don't like then you call setContent on a Gone ComposeView
After several hours of trials and experimentation, l found a way to fix this issue in a recent project. I would advise to try the following:
Update compose version to 1.2.0-Alpha06 as Skav mentioned. For me that implies bumping my kotlin version to 1.7.0 which my team is not ready for.
So I did some digging around the IdlingRegistry and found that unregistering Espresso-Compose Idling resource at the right spot in the test fixed the issue and my UI test passed afterwards.
IdlingRegistry.getInstance().resources.forEach {
Timber.e("resource ${it.name} idleNow: ${it.isIdleNow}")
if (it.name == "Compose-Espresso link") {
IdlingRegistry.getInstance().unregister(it)
}
}
It is important to note that unregistering Espresso-Compose too early can make your compose ui to fail to display. In my case, l ran this code after the UI is ready before testing for specific UI functionalities.
Building on top of the answer from #leeCoder, I've discovered that upgrading to Compose 1.2.0 (alpha no longer necessary) fixed my problem and there was no need to unregister anything (which was the part that made me the most nervous about his answer).
Related
I am using Espresso with Kotlin for the UI test automation. I am trying to find a proper way to restart the app during the test and start it again, so the test scenario is the following:
start the app, go to login page
force close the app and open it again (basically restart it)
check some stuff etc
The way our UI tests are organized:
there is a test class where I have rules
val intent = Intent(ApplicationProvider.getApplicationContext(), MainActivity::class.java)
.putExtra(UI_TEST_INTENT, true)
#get:Rule
val rule = ActivityScenarioRule<MainActivity>(intent)
there Before/After functions and tests functions in this class
What I want is to have generic restartApp function in separated class, let's say TestUtils and to be able to call it at any point of time, when is needed.
So far I didn't find a solution. There are some similar questions on stackoverflow, but I am not sure I understand how to work with the answers I found, like this:
with(activityRule) {
finishActivity()
launchActivity(null)
}
Since ActivityTestRule is deprecated and documentation asking to use ActivityScenarioRule, I tried this:
#get:Rule
val rule = ActivityScenarioRule<MainActivity>(intent)
private fun restart() {
rule.scenario.close()
rule.scenario.recreate()
}
but it gets java.lang.NullPointerException
another option is
private fun restart() {
pressBackUnconditionally()
Intents.release()
ActivityScenario.launch<MainActivity>(intent)
}
it works, app restarts but I can not interact with the app anymore, because for some reason there are two intents running now
Would be great to get an answer I can work with (I am quite new to Espresso)
Cheers
The solution is found:
private fun restart() {
Intents.release()
rule.scenario.close()
Intents.init()
ActivityScenario.launch<MainActivity>(intent)
}
Seems like the author's answer has some excess code. The following is enough
activityScenarioRule.scenario.close()
ActivityScenario.launch(YourActivity::class.java, null)
I have a few tests that only seem to fail the first time i run them, here is an example of one of them;
class OptionDialogTest {
#get:Rule
val activityScenarioRule = activityScenarioRule<MainActivity>()
#Test
fun optionDialogTest() {
val selected = R.string.manage_categories
var dialog: OptionDialog? = null
activityScenarioRule.scenario.onActivity { activity: MainActivity? ->
dialog = OptionDialog("test", "test", arrayOf(R.string.drawer_categories,selected), activity as Context)
dialog!!.setConfirm { _, _ -> }
dialog!!.show()
}
onView(withId(R.id.custom)).perform(click()) //this line sometimes doesn't execute causing the test to fail, presumably it doesn't execute in time.
onView(withText(R.string.manage_categories)).inRoot(isPlatformPopup()).perform(click())
onView(withText(R.string.Yes)).inRoot(isDialog()).perform(click())
assertEquals(dialog!!.getSelected(), selected)
}
}
the error android studio gives is; Waited for the root of the view hierarchy to have window focus and not request layout for 10 seconds. If you specified a non default root matcher, it may be picking a root that never takes focus.
my only guess is that some of the code isn't executing in an completely asyncronous fashion. I don't know if other developers experiance random test failures, or how i would go about fixing this.
any help or ideas are appreciated.
When I run my instant app I'm experiencing a crash due to the shortcut feature, please see the image with the stack trace, this is the only thing I can provide as I'm not able to attach the debugger, another problem :(
**GroupApplication.class**
override fun onCreate() {
...
ShortcutsHelper.init(this)
ShortcutsHelper.addSearchShortcut()
}
object ShortcutsHelper {
lateinit var appContext: Context
lateinit var shortcutManager: ShortcutManager
fun init(context: Context) {
this.appContext = context.applicationContext
this.shortcutManager = context.getSystemService(ShortcutManager::class.java)
}
fun addSearchShortcut() {
val shortcut = ShortcutInfo.Builder(appContext, SEARCH_SHORTCUT_ID)
.setShortLabel(appContext.getString(R.string.search_shortcut_short_label))
.setLongLabel(appContext.getString(R.string.search_shortcut_long_label))
.setIcon(Icon.createWithResource(appContext, R.drawable.ic_search_black))
.setIntent(GroupHomeActivity.getStartIntent(appContext, NavigationScreen.SEARCH))
.build()
shortcutManager.addDynamicShortcuts(listOf(shortcut))
}
I understand that a shortcut does not make any sense in an Instant App, is there a better solution than having all the code related to shortcuts commented when creating an IA?
I understand that a shortcut does not make any sense in an Instant App, is there a better solution than having all the code related to
shortcuts commented when creating an IA?
So yes, Instant Apps is restricted from running some APIs for security purposes. There used to be a FAQ page for it that indicated this, but I can't find it anymore.
But anyways, you don't have to comment it out. You can use:
/instantapps/PackageManagerCompat.html#isInstantApp()
I got to a project which uses Airbnb DeepLinkDispatch library. This part works fine, I'm able to run activities via URI + query params which parse fine too.
But when I tried using Espresso I got this issue - Intent does not contain Extra with URI and params.
The test I wrote:
class Test {
companion object {
#ClassRule
#JvmField
val rule = ActivityTestRule<MyActivity>(MyActivity::class.java, true, false)
}
#Before
fun setUp() {
val intent: Intent = MyActivity.createIntent(false)
rule.launchActivity(intent)
}
#Test
fun firstTest() {
onView(withId(R.id.switch))
.check(matches(isDisplayed()))
.perform(click())
Screengrab.screenshot("testtest")
}
}
I found out that onCreate in DeepLinkActivity is not called (the class which is annotated with #DeepLinkHandler(AppModule::class).
One way how to fix this could be adding missing EXTRA to the custom intent in setUp(), but this is something I don't want to do. It's a fragile solution and prone to future issues.
Any other ideas how to get espresso + deeplink running together?
I'm trying to make an http request in Android, using Kotlin, and I've come across two ways of doing so.
One is the traditional way, using AsyncTask (not really pretty) which I got to work with the following code (just the doInBackground, as the rest of the class seemed unnecessary):
override fun doInBackground(vararg params: Void?): String? {
val url = URL("myUrl")
val httpClient = url.openConnection() as HttpURLConnection
if(httpClient.getResponseCode() == HttpURLConnection.HTTP_OK){
try {
val stream = BufferedInputStream(httpClient.getInputStream())
val data: String = readStream(inputStream = stream)
return data;
} catch (e : Exception) {
e.printStackTrace()
} finally {
httpClient.disconnect()
}
}else{
println("ERROR ${httpClient.getResponseCode()}")
}
return null
}
Now, I've come across a library called Anko, which many here know, and I tried to use its DSL for asynchronous tasks. The thing is, I haven't found a lot of info here about Anko for asynchronous tasks, so I thought I would open a new topic to see if someone could walk me through what I'm doing wrong, or what they think I should do to make it work.
The code I wanted to use is the following:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
async() {
val result = URL("myUrl").readText()
textView1.setText(result)
}
}
I tried to keep it as slim as possible so to minimize any potential mistakes, but I must be doing something wrong here because the code inside the async block is not doing anything, yet the app is not crashing and I'm not getting any exceptions. I've tried debugging it using Intellij IDEA, but after the first line inside the async block it stops the debugging while saying "The application is running". My best guess is that it got hung up somewhere in that first line due to the failed connection, but I don't know.
I've also tried to use the regular URL("myUrl").openConnection() inside the async block, but that hasn't worked either.
Anyway, any help would be deeply appreciated.
The problem textView1 contents not getting updated is caused by calling setText outside of main thread.
The documentation shows a nice example how to properly use async. Take a look at the following adapted version of your code:
async() {
val result = URL("http://stackoverflow.com/").readText()
uiThread {
textView1.text = result
}
}
PS. While not directly related to the question consider using a nicer http client i.e. Retrofit, OkHttp
The problem turned out to be a lot more basic than what I was thinking. It was a problem of compatibility apparently from having an older version of Android Studio running with the new version 1.0.2 of the Kotlin plugin and, again, apparently the function readText was not working properly and therefore I wasn't getting anything from it.
Anyway, I updated Android Studio, with the latest version of Kotlin and it started working fine, although I'm going to see if I can find out what it was that was causing the problem inside readText.