From 302ca65ed76d567540669441ef8b04b87b53667e Mon Sep 17 00:00:00 2001 From: Anastasia Katsman Date: Mon, 16 Sep 2024 22:33:50 +0200 Subject: [PATCH] [rdct-tests] fix the situation when the focus is globally gone, and we are waiting for it: use robot GitOrigin-RevId: 667cf7fe735b315eba200772cba149ce50c040cc --- .../DistributedTestModel.Generated.kt | 21 +++-- .../modelSources/DistributedTestModel.kt | 3 +- .../tests/impl/DistributedTestHost.kt | 90 ++++++++++--------- 3 files changed, 63 insertions(+), 51 deletions(-) diff --git a/platform/remoteDev-util/modelGenerated/DistributedTestModel.Generated.kt b/platform/remoteDev-util/modelGenerated/DistributedTestModel.Generated.kt index fdc75e792f26..15f7336a46b0 100644 --- a/platform/remoteDev-util/modelGenerated/DistributedTestModel.Generated.kt +++ b/platform/remoteDev-util/modelGenerated/DistributedTestModel.Generated.kt @@ -57,7 +57,7 @@ class DistributedTestModel private constructor( private val __RdTestSessionNullableSerializer = RdTestSession.nullable() - const val serializationHash = 5182389909649567871L + const val serializationHash = -2934857283786697801L } override val serializersOwner: ISerializersOwner get() = DistributedTestModel @@ -377,7 +377,8 @@ class RdTestSession private constructor( private val _closeProjectIfOpened: RdCall, private val _runNextAction: RdCall, private val _runNextActionGetComponentData: RdCall, - private val _requestFocus: RdCall, + private val _requestFocus: RdCall, + private val _isFocused: RdCall, private val _visibleFrameNames: RdCall>, private val _projectsNames: RdCall>, private val _makeScreenshot: RdCall, @@ -407,13 +408,14 @@ class RdTestSession private constructor( val _closeProjectIfOpened = RdCall.read(ctx, buffer, FrameworkMarshallers.Void, FrameworkMarshallers.Bool) val _runNextAction = RdCall.read(ctx, buffer, RdTestActionParameters, __StringNullableSerializer) val _runNextActionGetComponentData = RdCall.read(ctx, buffer, RdTestActionParameters, RdTestComponentData) - val _requestFocus = RdCall.read(ctx, buffer, FrameworkMarshallers.String, FrameworkMarshallers.Bool) + val _requestFocus = RdCall.read(ctx, buffer, FrameworkMarshallers.Bool, FrameworkMarshallers.Bool) + val _isFocused = RdCall.read(ctx, buffer, FrameworkMarshallers.Void, FrameworkMarshallers.Bool) val _visibleFrameNames = RdCall.read(ctx, buffer, FrameworkMarshallers.Void, __StringListSerializer) val _projectsNames = RdCall.read(ctx, buffer, FrameworkMarshallers.Void, __StringListSerializer) val _makeScreenshot = RdCall.read(ctx, buffer, FrameworkMarshallers.String, FrameworkMarshallers.Bool) val _isResponding = RdCall.read(ctx, buffer, FrameworkMarshallers.Void, FrameworkMarshallers.Bool) val _projectsAreInitialised = RdCall.read(ctx, buffer, FrameworkMarshallers.Void, FrameworkMarshallers.Bool) - return RdTestSession(agentInfo, testClassName, testMethodName, traceCategories, debugCategories, _ready, _sendException, _exitApp, _showNotification, _closeProject, _forceLeaveAllModals, _closeProjectIfOpened, _runNextAction, _runNextActionGetComponentData, _requestFocus, _visibleFrameNames, _projectsNames, _makeScreenshot, _isResponding, _projectsAreInitialised).withId(_id) + return RdTestSession(agentInfo, testClassName, testMethodName, traceCategories, debugCategories, _ready, _sendException, _exitApp, _showNotification, _closeProject, _forceLeaveAllModals, _closeProjectIfOpened, _runNextAction, _runNextActionGetComponentData, _requestFocus, _isFocused, _visibleFrameNames, _projectsNames, _makeScreenshot, _isResponding, _projectsAreInitialised).withId(_id) } override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: RdTestSession) { @@ -433,6 +435,7 @@ class RdTestSession private constructor( RdCall.write(ctx, buffer, value._runNextAction) RdCall.write(ctx, buffer, value._runNextActionGetComponentData) RdCall.write(ctx, buffer, value._requestFocus) + RdCall.write(ctx, buffer, value._isFocused) RdCall.write(ctx, buffer, value._visibleFrameNames) RdCall.write(ctx, buffer, value._projectsNames) RdCall.write(ctx, buffer, value._makeScreenshot) @@ -455,7 +458,8 @@ class RdTestSession private constructor( val closeProjectIfOpened: RdCall get() = _closeProjectIfOpened val runNextAction: RdCall get() = _runNextAction val runNextActionGetComponentData: RdCall get() = _runNextActionGetComponentData - val requestFocus: RdCall get() = _requestFocus + val requestFocus: RdCall get() = _requestFocus + val isFocused: RdCall get() = _isFocused val visibleFrameNames: RdCall> get() = _visibleFrameNames val projectsNames: RdCall> get() = _projectsNames val makeScreenshot: RdCall get() = _makeScreenshot @@ -476,6 +480,7 @@ class RdTestSession private constructor( _runNextAction.async = true _runNextActionGetComponentData.async = true _requestFocus.async = true + _isFocused.async = true _visibleFrameNames.async = true _projectsNames.async = true _makeScreenshot.async = true @@ -494,6 +499,7 @@ class RdTestSession private constructor( bindableChildren.add("runNextAction" to _runNextAction) bindableChildren.add("runNextActionGetComponentData" to _runNextActionGetComponentData) bindableChildren.add("requestFocus" to _requestFocus) + bindableChildren.add("isFocused" to _isFocused) bindableChildren.add("visibleFrameNames" to _visibleFrameNames) bindableChildren.add("projectsNames" to _projectsNames) bindableChildren.add("makeScreenshot" to _makeScreenshot) @@ -523,7 +529,8 @@ class RdTestSession private constructor( RdCall(FrameworkMarshallers.Void, FrameworkMarshallers.Bool), RdCall(RdTestActionParameters, __StringNullableSerializer), RdCall(RdTestActionParameters, RdTestComponentData), - RdCall(FrameworkMarshallers.String, FrameworkMarshallers.Bool), + RdCall(FrameworkMarshallers.Bool, FrameworkMarshallers.Bool), + RdCall(FrameworkMarshallers.Void, FrameworkMarshallers.Bool), RdCall>(FrameworkMarshallers.Void, __StringListSerializer), RdCall>(FrameworkMarshallers.Void, __StringListSerializer), RdCall(FrameworkMarshallers.String, FrameworkMarshallers.Bool), @@ -552,6 +559,7 @@ class RdTestSession private constructor( print("runNextAction = "); _runNextAction.print(printer); println() print("runNextActionGetComponentData = "); _runNextActionGetComponentData.print(printer); println() print("requestFocus = "); _requestFocus.print(printer); println() + print("isFocused = "); _isFocused.print(printer); println() print("visibleFrameNames = "); _visibleFrameNames.print(printer); println() print("projectsNames = "); _projectsNames.print(printer); println() print("makeScreenshot = "); _makeScreenshot.print(printer); println() @@ -578,6 +586,7 @@ class RdTestSession private constructor( _runNextAction.deepClonePolymorphic(), _runNextActionGetComponentData.deepClonePolymorphic(), _requestFocus.deepClonePolymorphic(), + _isFocused.deepClonePolymorphic(), _visibleFrameNames.deepClonePolymorphic(), _projectsNames.deepClonePolymorphic(), _makeScreenshot.deepClonePolymorphic(), diff --git a/platform/remoteDev-util/modelSources/DistributedTestModel.kt b/platform/remoteDev-util/modelSources/DistributedTestModel.kt index 7cba592cdcfa..025afa8ec6cf 100644 --- a/platform/remoteDev-util/modelSources/DistributedTestModel.kt +++ b/platform/remoteDev-util/modelSources/DistributedTestModel.kt @@ -76,7 +76,8 @@ object DistributedTestModel : Ext(TestRoot) { call("closeProjectIfOpened", void, bool).async call("runNextAction", RdTestActionParameters, string.nullable).async call("runNextActionGetComponentData", RdTestActionParameters, RdTestComponentData).async - call("requestFocus", string, bool).async + call("requestFocus", bool, bool).async + call("isFocused", void, bool).async call("visibleFrameNames", void, immutableList(string)).async call("projectsNames", void, immutableList(string)).async call("makeScreenshot", string, bool).async diff --git a/platform/remoteDev-util/src/com/intellij/remoteDev/tests/impl/DistributedTestHost.kt b/platform/remoteDev-util/src/com/intellij/remoteDev/tests/impl/DistributedTestHost.kt index 1acf1ed3b199..564b30c3a03c 100644 --- a/platform/remoteDev-util/src/com/intellij/remoteDev/tests/impl/DistributedTestHost.kt +++ b/platform/remoteDev-util/src/com/intellij/remoteDev/tests/impl/DistributedTestHost.kt @@ -22,6 +22,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.project.ex.ProjectManagerEx import com.intellij.openapi.rd.util.adviseSuspendPreserveClientId import com.intellij.openapi.rd.util.setSuspendPreserveClientId +import com.intellij.openapi.ui.isFocusAncestor import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.SystemInfoRt import com.intellij.openapi.wm.WindowManager @@ -49,6 +50,7 @@ import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.TestOnly import java.awt.Component import java.awt.Frame +import java.awt.KeyboardFocusManager import java.awt.Window import java.awt.image.BufferedImage import java.io.File @@ -197,7 +199,7 @@ open class DistributedTestHost(coroutineScope: CoroutineScope) { return withContext(providedContext + clientId.asContextElement()) { assert(ClientId.current == clientId) { "ClientId '${ClientId.current}' should equal $clientId one when test method starts" } if (!app.isHeadlessEnvironment && isNotRdHost && (requestFocusBeforeStart ?: isCurrentThreadEdt())) { - requestFocus(actionTitle) + requestFocus(silent = false) } assert(ClientId.current == clientId) { "ClientId '${ClientId.current}' should equal $clientId one when after request focus" } @@ -323,9 +325,9 @@ open class DistributedTestHost(coroutineScope: CoroutineScope) { // actually doesn't really preserve clientId, not really important here // https://youtrack.jetbrains.com/issue/RDCT-653/setSuspendPreserveClientId-with-custom-dispatcher-doesnt-preserve-ClientId - session.requestFocus.setSuspendPreserveClientId(handlerScheduler = Dispatchers.Default.asRdScheduler) { _, actionTitle -> + session.requestFocus.setSuspendPreserveClientId(handlerScheduler = Dispatchers.Default.asRdScheduler) { _, silent -> withContext(Dispatchers.EDT + ModalityState.any().asContextElement()) { - requestFocus(actionTitle) + requestFocus(silent) } } @@ -361,81 +363,81 @@ open class DistributedTestHost(coroutineScope: CoroutineScope) { } - private suspend fun requestFocus(actionTitle: String): Boolean { - LOG.info("$actionTitle: Requesting focus") + private suspend fun requestFocus(silent: Boolean): Boolean { + LOG.info("Requesting focus") val projects = ProjectManagerEx.getOpenProjects() if (projects.size > 1) { - LOG.info("'$actionTitle': Can't choose a project to focus. All projects: ${projects.joinToString(", ")}") + LOG.info("Can't choose a project to focus. All projects: ${projects.joinToString(", ")}") return false } val currentProject = projects.singleOrNull() return if (currentProject == null) { - requestFocusNoProject(actionTitle) + requestFocusNoProject(silent) } else { - requestFocusWithProject(currentProject, actionTitle) + requestFocusWithProjectIfNeeded(currentProject, silent) } } - private suspend fun requestFocusWithProject(project: Project, actionTitle: String): Boolean { + private suspend fun requestFocusWithProjectIfNeeded(project: Project, silent: Boolean): Boolean { val projectIdeFrame = WindowManager.getInstance().getFrame(project) if (projectIdeFrame == null) { - LOG.info("$actionTitle: No frame yet, nothing to focus") + LOG.info("No frame yet, nothing to focus") return false } else { - val windowString = "window '${projectIdeFrame.name}'" - if (SystemInfo.isWindows) { - return requestFocusWithProjectOnWindows(projectIdeFrame, project, windowString) - } + val frameName = "frame '${projectIdeFrame.name}'" - if (projectIdeFrame.isFocused) { - LOG.info("$actionTitle: Window '$windowString' is already focused") - return true + return if ((projectIdeFrame.isFocusAncestor() || projectIdeFrame.isFocused) && !SystemInfo.isWindows) { + LOG.info("Frame '$frameName' is already focused") + true } else { - LOG.info("$actionTitle: Requesting project focus for '$windowString'") - ProjectUtil.focusProjectWindow(project, true) - val waitResult = waitFor(timeout = 5.seconds.toJavaDuration()) { - projectIdeFrame.isFocused - } - if (!waitResult) { - LOG.error("Couldn't wait for focus in project '$windowString'") - } - return waitResult + requestFocusWithProject(projectIdeFrame, project, frameName, silent) } } } - private suspend fun requestFocusWithProjectOnWindows(projectIdeFrame: JFrame, project: Project, windowString: String): Boolean { + private suspend fun requestFocusWithProject(projectIdeFrame: JFrame, project: Project, frameName: String, silent: Boolean): Boolean { + LOG.info("Requesting project focus for '$frameName'") + AppIcon.getInstance().requestFocus(projectIdeFrame) - ProjectUtil.focusProjectWindow(project) - val waitResult = waitFor(timeout = 5.seconds.toJavaDuration()) { projectIdeFrame.isFocused } - if (!waitResult) { - LOG.error("Couldn't wait for focus in project '$windowString'") + ProjectUtil.focusProjectWindow(project, stealFocusIfAppInactive = true) + + return waitFor(timeout = 5.seconds.toJavaDuration()) { + projectIdeFrame.isFocusAncestor() || projectIdeFrame.isFocused + }.also { + if (!it && !silent) { + val keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager() + LOG.error("Couldn't wait for focus in project '$frameName'," + + "component isFocused=" + projectIdeFrame.isFocused + " isFocusAncestor=" + projectIdeFrame.isFocusAncestor() + + "\nActual focused component: " + + "\nfocusedWindow is " + keyboardFocusManager.focusedWindow + + "\nfocusOwner is " + keyboardFocusManager.focusOwner + + "\nactiveWindow is " + keyboardFocusManager.activeWindow + + "\npermanentFocusOwner is " + keyboardFocusManager.permanentFocusOwner) + } } - return waitResult } - private suspend fun requestFocusNoProject(actionTitle: String): Boolean { + private suspend fun requestFocusNoProject(silent: Boolean): Boolean { val visibleWindows = Window.getWindows().filter { it.isShowing } - if (visibleWindows.size != 1) { - LOG.info("$actionTitle: There are multiple windows, will focus them all. All windows: ${visibleWindows.joinToString(", ")}") + if (visibleWindows.size > 1) { + LOG.info("There are multiple windows, will focus them all. All windows: ${visibleWindows.joinToString(", ")}") } - return visibleWindows.map { - LOG.info("$actionTitle: Focusing window '$it'") + visibleWindows.forEach { AppIcon.getInstance().requestFocus(it) - val waitResult = waitFor(timeout = 5.seconds.toJavaDuration()) { - it.isFocused + } + return waitFor(timeout = 5.seconds.toJavaDuration()) { + KeyboardFocusManager.getCurrentKeyboardFocusManager().focusOwner != null + }.also { + if (!it && !silent) { + LOG.error("Couldn't wait for focus in case there is no project") } - if (!waitResult) { - LOG.error("Couldn't wait for focus in project '$it'") - } - waitResult - }.all { it } + } } private fun screenshotFile(actionName: String, suffix: String, timeStamp: LocalTime): File {