Android Espresso针对Chrome定制标签的测试

Android Espresso针对Chrome定制标签的测试,android,oauth-2.0,openid,android-espresso,ui-testing,Android,Oauth 2.0,Openid,Android Espresso,Ui Testing,通过以下代码片段,我一直在尝试使用Espresso(UiAutomator自动填充用户输入字段)用于测试登录验证流,使用where通过自定义Chrome选项卡外部登录,成功登录后通过启动活动的onActivityResult()回调将用户带回应用程序,然后运行一些逻辑(在这种情况下,通过验证下一个屏幕的视图是否正在显示来断言屏幕确实发生了更改)。但结果表明,该应用程序在登录后没有正确恢复,随后会抛出NoActivityResumedException 是的,我尝试过使用意式浓缩咖啡,但不知道如何

通过以下代码片段,我一直在尝试使用Espresso(
UiAutomator
自动填充用户输入字段)用于测试登录验证流,使用where通过自定义Chrome选项卡外部登录,成功登录后通过启动活动的
onActivityResult()
回调将用户带回应用程序,然后运行一些逻辑(在这种情况下,通过验证下一个屏幕的视图是否正在显示来断言屏幕确实发生了更改)。但结果表明,该应用程序在登录后没有正确恢复,随后会抛出
NoActivityResumedException

是的,我尝试过使用
意式浓缩咖啡
,但不知道如何将其与此场景联系起来,因为我要在登录屏幕中测试整个登录流,就像
活动测试规则
,特别是触发它自己的意图(来自库的身份验证请求)在按下登录按钮后。我感觉到目前为止我在正确的轨道上,所以任何帮助都将为我指明正确的方向

登录屏幕:

class LoginActivity : AppCompatActivity() {

    companion object {
        const val RC_AUTH_LOGIN = 100
    }

    private lateinit var authService: AuthorizationService

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)

        initAuthService()
        initViews()
    }

    override fun onDestroy() {
        authService.dispose()
        super.onDestroy()
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (resultCode == Activity.RESULT_OK) {
            when (requestCode) {
                RC_AUTH_LOGIN -> initViewModelAndObserve(data)
                else -> // Display error message
            }
        }
    }

    private fun initAuthService() {
        authService = AuthorizationService(this)
    }

    private fun initViews() {
        start_auth_button?.setOnClickListener {
            startAuthorization()
        }
    }

    private fun initViewModelAndObserve(data: Intent?) {
        // [authState] can either be retrieved from cache or [AuthState()]
        AuthUtils.handleAuthorizationResponse(authService, data, authState) { success ->
            if (success) {
                // Run necessary API async calls and such within the ViewModel 
                // layer to observe.
                loginViewModel.loginLiveData.observe(this, Observer<Boolean> { loginSuccessful ->
                    if (loginSuccessful) {
                        // Transition to the next screen
                    } else {
                        // Display error message
                    }
                })
            } else {
                // Display error message
            }
        }
    }

    private fun startAuthorization() {
        val req = AuthUtils.getAuthRequest()
        val intent = authService.getAuthorizationRequestIntent(req)
        startActivityForResult(intent, RC_AUTH_LOGIN)
    }

}
浓缩咖啡用户界面测试:

@LargeTest
@RunWith(AndroidJUnit4::class)
class LoginAuthInstrumentedTest { 

    private val context = InstrumentationRegistry.getInstrumentation().targetContext

    @Rule
    @JvmField
    var activityTestRule = ActivityTestRule(LoginActivity::class.java)

    @Test
    fun loginAuthFlow_isCorrect() {
        // Performs a click action in the login screen to fire off
        // the auth service intent for an activity result.
        onView(withId(R.id.start_auth_button)).perform(click())

        // Automatically logs the user in with dummy creds within a
        // custom Chrome tab intent (via the OpenID auth library).
        val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
        val selector = UiSelector()
        val usernameInputObject = device.findObject(selector.resourceId("username"))
        usernameInputObject.click()
        usernameInputObject.text = "testuser@testapp.com"
        val passwordInputObject = device.findObject(selector.resourceId("password"))
        passwordInputObject.click()
        passwordInputObject.text = "testpassword"
        val loginBtnObject = device.findObject(selector.resourceId("cmdLogin"))
        loginBtnObject.click()

        // Upon a successful login from the auth service, the following
        // asserts that the following views are shown on the next
        // transitioned screen.
        onView(withId(R.id.main_screen_header)).check(matches(withText(context.getString(R.string.main_screen_header_text))))
        onView(withId(R.id.main_screen_subheader)).check(matches(withText(context.getString(R.string.main_screen_subheader_text))))
        onView(withId(R.id.main_screen_description)).check(matches(withText(context.getString(R.string.main_screen_description_text))))
    }

}
…但是,
LoginActivity
不会如日志中所示恢复(在
NoActivityResumedException
之前):

D/LifecycleMonitor:生命周期状态更改:com.testapp.view.login。LoginActivity@de1a309在:停止
正在运行回调:androidx.test.rule.ActivityTestRule$LifecycleCallback@ff0a037
回调完成:androidx.test.rule.ActivityTestRule$LifecycleCallback@ff0a037
D/LifecycleMonitor:生命周期状态更改:net.openid.appauth。AuthorizationManagementActivity@76192e1在:停止
正在运行回调:androidx.test.rule.ActivityTestRule$LifecycleCallback@ff0a037
回调完成:androidx.test.rule.ActivityTestRule$LifecycleCallback@ff0a037
I/QueryController:匹配的选择器:UiSelector[RESOURCE\u ID=username][android.view.accessibility。AccessibilityNodeInfo@a3d2f;boundsInParent:Rect(0,28-382,81);boundsInScreen:Rect(39782-1042921);packageName:com.android.chrome;类名:android.widget.EditText;文本:;错误:null;maxTextLength:-1;内容描述:null;tooltipText:null;viewIdResName:username;可检查:false;已检查:false;可聚焦:false;已选择:false;可点击:true;可长点击:false;可上下文点击:false;已启用:true;password:false;可滚动:false;可访问性的重要性:false;可见:true;操作:[AccessibilityAction:ACTION\u NEXT\u HTML\u ELEMENT-null,AccessibilityAction:ACTION\u NEXT\u AT\u MOVEMENT\u GRANULARITY-null,AccessibilityAction:ACTION\u PREVIOUS\u AT\u MOVEMENT\u GRANULARITY-null,AccessibilityAction:ACTION\u在屏幕上显示\u-null,AccessibilityAction:ACTION\u CONTEXT\u CLICK-null,AAccessibilityAction:ACTION\u SET\u TEXT-null,AccessibilityAction:ACTION\u PASTE-null,AccessibilityAction:ACTION\u FOCUS-null,AccessibilityAction:ACTION\u ACCESSIBILITY\u FOCUS-null,AccessibilityAction:ACTION\u CLICK-null]]
交互控制器:点击并同步(540851)
I/QueryController:匹配的选择器:UiSelector[RESOURCE\u ID=username][android.view.accessibility。AccessibilityNodeInfo@a3d2f;boundsInParent:Rect(0,28-382,81);boundsInScreen:Rect(39782-1042921);packageName:com.android.chrome;类名:android.widget.EditText;文本:;错误:null;maxTextLength:-1;内容描述:null;tooltipText:null;viewIdResName:username;可检查:false;已检查:false;可聚焦:true;已选择:false;可单击:true;可长单击:false;可上下文单击:false;已启用:true;password:false;可滚动:false;importantForAccessibility:false;visible:true;操作:[AccessibilityAction:ACTION\u NEXT\u HTML\u ELEMENT-null,AccessibilityAction:ACTION\u NEXT\u AT\u MOVEMENT\u GRANULARITY-null,AccessibilityAction:ACTION\u PREVIOUS\u AT\u MOVEMENT\u GRANULARITY-null,AccessibilityAction:ACTION\u在屏幕上显示\u-null,AccessibilityAction:ACTION\u CONTEXT\u CLICK-null,AAccessibilityAction:ACTION\u SET\u TEXT-null,AccessibilityAction:ACTION\u PASTE-null,AccessibilityAction:ACTION\u CLEAR\u FOCUS-null,AccessibilityAction:ACTION\u CLEAR\u ACCESSIBILITY\u FOCUS-null,AccessibilityAction:ACTION\u CLICK-null]]
I/QueryController:匹配的选择器:UiSelector[RESOURCE\u ID=password][android.view.accessibility。AccessibilityNodeInfo@a3f1f;boundsInParent:Rect(0,0-317,8);boundsInScreen:Rect(391034-8711055);packageName:com.android.chrome;类名:android.widget.EditText;文本:;错误:null;maxTextLength:-1;内容描述:null;tooltipText:null;viewIdResName:password;可检查:false;已检查:false;可聚焦:true;已选择:false;可单击:true;可长单击:false;可上下文单击:false;已启用:true;password:true;可滚动:false;importantForAccessibility:false;visible:true;操作:[AccessibilityAction:ACTION\u NEXT\u HTML\u ELEMENT-null,AccessibilityAction:ACTION\u NEXT\u AT\u MOVEMENT\u GRANULARITY-null,AccessibilityAction:ACTION\u PREVIOUS\u AT\u MOVEMENT\u GRANULARITY-null,AccessibilityAction:ACTION\u在屏幕上显示\u-null,AccessibilityAction:ACTION\u CONTEXT\u CLICK-null,AAccessibilityAction:ACTION\u SET\u TEXT-null,AccessibilityAction:ACTION\u PASTE-null,AccessibilityAction:ACTION\u FOCUS-null,AccessibilityAction:ACTION\u ACCESSIBILITY\u FOCUS-null,AccessibilityAction:ACTION\u CLICK-null]]
交互控制器:点击并同步(4551044)
I/QueryController:匹配的选择器:UiSelector[RESOURCE\u ID=password][android.view.accessibility。AccessibilityNodeInfo@a3f1f;boundsInParent:Rect(0,0-317,8);boundsInScreen:Rect(391034-8711055);packageName:com.android.chrome;类名:android.widget.EditText;文本:;错误:
@LargeTest
@RunWith(AndroidJUnit4::class)
class LoginAuthInstrumentedTest { 

    private val context = InstrumentationRegistry.getInstrumentation().targetContext

    @Rule
    @JvmField
    var activityTestRule = ActivityTestRule(LoginActivity::class.java)

    @Test
    fun loginAuthFlow_isCorrect() {
        // Performs a click action in the login screen to fire off
        // the auth service intent for an activity result.
        onView(withId(R.id.start_auth_button)).perform(click())

        // Automatically logs the user in with dummy creds within a
        // custom Chrome tab intent (via the OpenID auth library).
        val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
        val selector = UiSelector()
        val usernameInputObject = device.findObject(selector.resourceId("username"))
        usernameInputObject.click()
        usernameInputObject.text = "testuser@testapp.com"
        val passwordInputObject = device.findObject(selector.resourceId("password"))
        passwordInputObject.click()
        passwordInputObject.text = "testpassword"
        val loginBtnObject = device.findObject(selector.resourceId("cmdLogin"))
        loginBtnObject.click()

        // Upon a successful login from the auth service, the following
        // asserts that the following views are shown on the next
        // transitioned screen.
        onView(withId(R.id.main_screen_header)).check(matches(withText(context.getString(R.string.main_screen_header_text))))
        onView(withId(R.id.main_screen_subheader)).check(matches(withText(context.getString(R.string.main_screen_subheader_text))))
        onView(withId(R.id.main_screen_description)).check(matches(withText(context.getString(R.string.main_screen_description_text))))
    }

}
D/LifecycleMonitor: Lifecycle status change: com.testapp.view.login.LoginActivity@de1a309 in: STOPPED
    running callback: androidx.test.rule.ActivityTestRule$LifecycleCallback@ff0a037
    callback completes: androidx.test.rule.ActivityTestRule$LifecycleCallback@ff0a037
D/LifecycleMonitor: Lifecycle status change: net.openid.appauth.AuthorizationManagementActivity@76192e1 in: STOPPED
    running callback: androidx.test.rule.ActivityTestRule$LifecycleCallback@ff0a037
    callback completes: androidx.test.rule.ActivityTestRule$LifecycleCallback@ff0a037
I/QueryController: Matched selector: UiSelector[RESOURCE_ID=username] <<==>> [android.view.accessibility.AccessibilityNodeInfo@a3d2f; boundsInParent: Rect(0, 28 - 382, 81); boundsInScreen: Rect(39, 782 - 1042, 921); packageName: com.android.chrome; className: android.widget.EditText; text: ; error: null; maxTextLength: -1; contentDescription: null; tooltipText: null; viewIdResName: username; checkable: false; checked: false; focusable: true; focused: false; selected: false; clickable: true; longClickable: false; contextClickable: false; enabled: true; password: false; scrollable: false; importantForAccessibility: false; visible: true; actions: [AccessibilityAction: ACTION_NEXT_HTML_ELEMENT - null, AccessibilityAction: ACTION_PREVIOUS_HTML_ELEMENT - null, AccessibilityAction: ACTION_NEXT_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_SHOW_ON_SCREEN - null, AccessibilityAction: ACTION_CONTEXT_CLICK - null, AccessibilityAction: ACTION_SET_TEXT - null, AccessibilityAction: ACTION_PASTE - null, AccessibilityAction: ACTION_FOCUS - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_CLICK - null]]
D/InteractionController: clickAndSync(540, 851)
I/QueryController: Matched selector: UiSelector[RESOURCE_ID=username] <<==>> [android.view.accessibility.AccessibilityNodeInfo@a3d2f; boundsInParent: Rect(0, 28 - 382, 81); boundsInScreen: Rect(39, 782 - 1042, 921); packageName: com.android.chrome; className: android.widget.EditText; text: ; error: null; maxTextLength: -1; contentDescription: null; tooltipText: null; viewIdResName: username; checkable: false; checked: false; focusable: true; focused: true; selected: false; clickable: true; longClickable: false; contextClickable: false; enabled: true; password: false; scrollable: false; importantForAccessibility: false; visible: true; actions: [AccessibilityAction: ACTION_NEXT_HTML_ELEMENT - null, AccessibilityAction: ACTION_PREVIOUS_HTML_ELEMENT - null, AccessibilityAction: ACTION_NEXT_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_SHOW_ON_SCREEN - null, AccessibilityAction: ACTION_CONTEXT_CLICK - null, AccessibilityAction: ACTION_SET_TEXT - null, AccessibilityAction: ACTION_PASTE - null, AccessibilityAction: ACTION_CLEAR_FOCUS - null, AccessibilityAction: ACTION_CLEAR_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_CLICK - null]]
I/QueryController: Matched selector: UiSelector[RESOURCE_ID=password] <<==>> [android.view.accessibility.AccessibilityNodeInfo@a3f1f; boundsInParent: Rect(0, 0 - 317, 8); boundsInScreen: Rect(39, 1034 - 871, 1055); packageName: com.android.chrome; className: android.widget.EditText; text: ; error: null; maxTextLength: -1; contentDescription: null; tooltipText: null; viewIdResName: password; checkable: false; checked: false; focusable: true; focused: false; selected: false; clickable: true; longClickable: false; contextClickable: false; enabled: true; password: true; scrollable: false; importantForAccessibility: false; visible: true; actions: [AccessibilityAction: ACTION_NEXT_HTML_ELEMENT - null, AccessibilityAction: ACTION_PREVIOUS_HTML_ELEMENT - null, AccessibilityAction: ACTION_NEXT_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_SHOW_ON_SCREEN - null, AccessibilityAction: ACTION_CONTEXT_CLICK - null, AccessibilityAction: ACTION_SET_TEXT - null, AccessibilityAction: ACTION_PASTE - null, AccessibilityAction: ACTION_FOCUS - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_CLICK - null]]
D/InteractionController: clickAndSync(455, 1044)
I/QueryController: Matched selector: UiSelector[RESOURCE_ID=password] <<==>> [android.view.accessibility.AccessibilityNodeInfo@a3f1f; boundsInParent: Rect(0, 0 - 317, 8); boundsInScreen: Rect(39, 1034 - 871, 1055); packageName: com.android.chrome; className: android.widget.EditText; text: ; error: null; maxTextLength: -1; contentDescription: null; tooltipText: null; viewIdResName: password; checkable: false; checked: false; focusable: true; focused: false; selected: false; clickable: true; longClickable: false; contextClickable: false; enabled: true; password: true; scrollable: false; importantForAccessibility: false; visible: true; actions: [AccessibilityAction: ACTION_NEXT_HTML_ELEMENT - null, AccessibilityAction: ACTION_PREVIOUS_HTML_ELEMENT - null, AccessibilityAction: ACTION_NEXT_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_SHOW_ON_SCREEN - null, AccessibilityAction: ACTION_CONTEXT_CLICK - null, AccessibilityAction: ACTION_SET_TEXT - null, AccessibilityAction: ACTION_PASTE - null, AccessibilityAction: ACTION_FOCUS - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_CLICK - null]]
I/QueryController: Matched selector: UiSelector[RESOURCE_ID=cmdSubmit] <<==>> [android.view.accessibility.AccessibilityNodeInfo@a3dab; boundsInParent: Rect(0, 131 - 382, 132); boundsInScreen: Rect(39, 1052 - 1042, 1055); packageName: com.android.chrome; className: android.widget.Button; text: Sign In; error: null; maxTextLength: -1; contentDescription: null; tooltipText: null; viewIdResName: cmdSubmit; checkable: false; checked: false; focusable: false; focused: false; selected: false; clickable: false; longClickable: false; contextClickable: false; enabled: false; password: false; scrollable: false; importantForAccessibility: false; visible: true; actions: [AccessibilityAction: ACTION_NEXT_HTML_ELEMENT - null, AccessibilityAction: ACTION_PREVIOUS_HTML_ELEMENT - null, AccessibilityAction: ACTION_NEXT_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_SHOW_ON_SCREEN - null, AccessibilityAction: ACTION_CONTEXT_CLICK - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null]]
D/InteractionController: clickAndSync(540, 1053)
V/FA: Inactivity, disconnecting from the service
W/RootViewPicker: No activity currently resumed - waiting: 10ms for one to appear.
W/RootViewPicker: No activity currently resumed - waiting: 50ms for one to appear.
W/RootViewPicker: No activity currently resumed - waiting: 100ms for one to appear.
W/RootViewPicker: No activity currently resumed - waiting: 500ms for one to appear.
W/RootViewPicker: No activity currently resumed - waiting: 2000ms for one to appear.
W/RootViewPicker: No activity currently resumed - waiting: 30000ms for one to appear.