diff --git a/.github/workflows/runUiTests.yml b/.github/workflows/runUiTests.yml
new file mode 100644
index 000000000..68ca34c66
--- /dev/null
+++ b/.github/workflows/runUiTests.yml
@@ -0,0 +1,66 @@
+name: Run UI Tests
+on:
+  workflow_dispatch
+jobs:
+  build-for-ui-test-mac-os:
+    runs-on: macos-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Setup Java
+        uses: actions/setup-java@v2.1.0
+        with:
+          distribution: zulu
+          java-version: 11
+      - name: Build Plugin
+        run: gradle :buildPlugin
+      - name: Run Idea
+        run: |
+          mkdir -p build/reports
+          gradle :runIdeForUiTests > build/reports/idea.log &
+      - name: Wait for Idea started
+        uses: jtalk/url-health-check-action@1.5
+        with:
+          url: http://127.0.0.1:8082
+          max-attempts: 20
+          retry-delay: 10s
+      - name: Tests
+        run: gradle :testUi
+      - name: Save fails report
+        if: ${{ failure() }}
+        uses: actions/upload-artifact@v2
+        with:
+          name: ui-test-fails-report-mac
+          path: |
+            build/reports
+#  build-for-ui-test-linux:
+#    runs-on: ubuntu-latest
+#    steps:
+#      - uses: actions/checkout@v2
+#      - name: Setup Java
+#        uses: actions/setup-java@v2.1.0
+#        with:
+#          distribution: zulu
+#          java-version: 11
+#      - name: Build Plugin
+#        run: gradle :buildPlugin
+#      - name: Run Idea
+#        run: |
+#          export DISPLAY=:99.0
+#          Xvfb -ac :99 -screen 0 1920x1080x16 &
+#          mkdir -p build/reports
+#          gradle :runIdeForUiTests #> build/reports/idea.log
+#      - name: Wait for Idea started
+#        uses: jtalk/url-health-check-action@1.5
+#        with:
+#          url: http://127.0.0.1:8082
+#          max-attempts: 15
+#          retry-delay: 30s
+#      - name: Tests
+#        run: gradle :testUi
+#      - name: Save fails report
+#        if: ${{ failure() }}
+#        uses: actions/upload-artifact@v2
+#        with:
+#          name: ui-test-fails-report-linux
+#          path: |
+#            ui-test-example/build/reports
\ No newline at end of file
diff --git a/AUTHORS.md b/AUTHORS.md
index 28422db59..2a3244bc6 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -360,6 +360,10 @@ Contributors:
   [![icon][github]](https://github.com/MichalPlacek)
    
   Michal Placek
+* [![icon][mail]](mailto:eugene.nizienko@jetbrains.com)
+  [![icon][github]](https://github.com/nizienko)
+   
+  eugene nizienko
                         
 If you are a contributor and your name is not listed here, feel free to
 contact the maintainers.
diff --git a/build.gradle.kts b/build.gradle.kts
index adde649dd..85846e64f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -36,6 +36,7 @@ val kotlinVersion: String by project
 val ideaVersion: String by project
 val downloadIdeaSources: String by project
 val instrumentPluginCode: String by project
+val remoteRobotVersion: String by project
 
 val publishChannels: String by project
 val publishToken: String by project
@@ -55,8 +56,8 @@ dependencies {
     testImplementation("com.ensarsarajcic.neovim.java:neovim-api:0.2.3")
     testImplementation("com.ensarsarajcic.neovim.java:core-rpc:0.2.3")
 
-    testImplementation("com.intellij.remoterobot:remote-robot:0.11.6")
-    testImplementation("com.intellij.remoterobot:remote-fixtures:1.1.18")
+    testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
+    testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
 }
 
 // --- Compilation
@@ -116,7 +117,7 @@ intellij {
 
 tasks {
     downloadRobotServerPlugin {
-        version.set("0.10.0")
+        version.set(remoteRobotVersion)
     }
 
     publishPlugin {
diff --git a/gradle.properties b/gradle.properties
index 91b2dbce9..1c665bbdd 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -5,6 +5,7 @@ downloadIdeaSources=true
 instrumentPluginCode=true
 version=SNAPSHOT
 javaVersion=1.8
+remoteRobotVersion=0.11.6
 
 # Please don't forget to update kotlin version in buildscript section
 kotlinVersion=1.5.0
diff --git a/test/ui/UiTests.kt b/test/ui/UiTests.kt
index db3a179fd..d51a58f1a 100644
--- a/test/ui/UiTests.kt
+++ b/test/ui/UiTests.kt
@@ -23,7 +23,6 @@ import com.intellij.remoterobot.fixtures.ContainerFixture
 import com.intellij.remoterobot.search.locators.byXpath
 import com.intellij.remoterobot.stepsProcessing.step
 import com.intellij.remoterobot.utils.keyboard
-import com.intellij.remoterobot.utils.waitFor
 import org.assertj.swing.core.MouseButton
 import org.junit.Test
 import ui.pages.Editor
@@ -53,7 +52,7 @@ class UiTests {
   }
 
   @Test
-  fun ideaVimTest() = uiTest {
+  fun ideaVimTest() = uiTest("ideaVimTest") {
     val sharedSteps = JavaExampleSteps(this)
 
     welcomeFrame {
@@ -69,12 +68,14 @@ class UiTests {
         button("Finish").click()
       }
     }
-    sharedSteps.closeTipOfTheDay()
+    with(sharedSteps) {
+      closeIdeaVimDialog()
+      closeTipOfTheDay()
+    }
     idea {
       step("Create App file") {
         with(projectViewTree) {
-          findText(projectName).doubleClick()
-          waitFor { hasText("src") }
+          expand(projectName, "src")
           findText("src").click(MouseButton.RIGHT_BUTTON)
         }
         actionMenu("New").click()
diff --git a/test/ui/pages/Editor.kt b/test/ui/pages/Editor.kt
index 82868f8e8..482f40e17 100644
--- a/test/ui/pages/Editor.kt
+++ b/test/ui/pages/Editor.kt
@@ -54,8 +54,19 @@ class Editor(
   val caretOffset: Int
     get() = callJs("component.getEditor().getCaretModel().getOffset()", runInEdt = true)
 
+  val isBlockCursor: Boolean
+    get() = callJs("component.getEditor().getSettings().isBlockCursor()", true)
+
   fun injectText(text: String) {
-    runJs("component.getEditor().getDocument().setText('${text.escape()}')", runInEdt = true)
+    runJs("""
+      const app = com.intellij.openapi.application.ApplicationManager.getApplication()
+
+      app.invokeLaterOnWriteThread(()=>{
+          app['runWriteAction(com.intellij.openapi.util.Computable)'](()=>{
+              component.getEditor().getDocument().setText('${text.escape()}')
+          })
+        })
+""")
   }
 
   @Suppress("unused")
diff --git a/test/ui/pages/IdeaFrame.kt b/test/ui/pages/IdeaFrame.kt
index f1336acfc..5f5d95c9f 100644
--- a/test/ui/pages/IdeaFrame.kt
+++ b/test/ui/pages/IdeaFrame.kt
@@ -24,6 +24,7 @@ import com.intellij.remoterobot.fixtures.CommonContainerFixture
 import com.intellij.remoterobot.fixtures.ContainerFixture
 import com.intellij.remoterobot.fixtures.DefaultXpath
 import com.intellij.remoterobot.fixtures.FixtureName
+import com.intellij.remoterobot.fixtures.JTreeFixture
 import com.intellij.remoterobot.search.locators.byXpath
 import com.intellij.remoterobot.stepsProcessing.step
 import com.intellij.remoterobot.utils.waitFor
@@ -41,7 +42,7 @@ class IdeaFrame(
 ) : CommonContainerFixture(remoteRobot, remoteComponent) {
 
   val projectViewTree
-    get() = find<ContainerFixture>(byXpath("ProjectViewTree", "//div[@class='ProjectViewTree']"))
+    get() = find<JTreeFixture>(byXpath("ProjectViewTree", "//div[@class='ProjectViewTree']"), Duration.ofSeconds(10))
 
   val projectName
     get() = step("Get project name") { return@step callJs<String>("component.getProject().getName()") }
diff --git a/test/ui/utils/JavaExampleSteps.kt b/test/ui/utils/JavaExampleSteps.kt
index 79d36f426..b91fd62f5 100644
--- a/test/ui/utils/JavaExampleSteps.kt
+++ b/test/ui/utils/JavaExampleSteps.kt
@@ -18,28 +18,48 @@
 package ui.utils
 
 import com.intellij.remoterobot.RemoteRobot
+import com.intellij.remoterobot.fixtures.JButtonFixture
+import com.intellij.remoterobot.search.locators.byXpath
 import com.intellij.remoterobot.stepsProcessing.step
 import com.intellij.remoterobot.utils.Keyboard
 import ui.pages.DialogFixture
 import ui.pages.DialogFixture.Companion.byTitle
 import ui.pages.IdeaFrame
+import ui.pages.dialog
+import ui.pages.idea
 
 class JavaExampleSteps(private val remoteRobot: RemoteRobot) {
   @Suppress("unused")
   private val keyboard: Keyboard = Keyboard(remoteRobot)
 
-  fun closeTipOfTheDay() {
-    step(
-      "Close Tip of the Day if it appears",
-      Runnable {
-        val idea: IdeaFrame = remoteRobot.find(IdeaFrame::class.java)
-        idea.dumbAware {
-          try {
-            idea.find(DialogFixture::class.java, byTitle("Tip of the Day")).button("Close").click()
-          } catch (ignore: Throwable) {
-          }
-        }
-      }
-    )
+  fun closeIdeaVimDialog() = optionalStep("Close Idea Vim dialog if it appears") {
+    remoteRobot.idea {
+      dialog("IdeaVim") { button("Yes").click() }
+    }
+  }
+
+
+  fun closeTipOfTheDay() = optionalStep("Close Tip of the Day if it appears") {
+    val idea: IdeaFrame = remoteRobot.find(IdeaFrame::class.java)
+    idea.dumbAware {
+      idea.find(DialogFixture::class.java, byTitle("Tip of the Day")).button("Close").click()
+    }
+    closeAllGotIt()
+  }
+
+
+  fun closeAllGotIt() = step("Close Got It") {
+    remoteRobot.findAll<JButtonFixture>(byXpath("//div[@accessiblename='Got It']")).forEach {
+      it.click()
+    }
+  }
+
+
+  private fun optionalStep(stepName: String, code: () -> Unit) = step(stepName) {
+    try {
+      code()
+    } catch (ignore: Throwable) {
+      println("$stepName ignored")
+    }
   }
 }
diff --git a/test/ui/utils/UiTestWrapper.kt b/test/ui/utils/UiTestWrapper.kt
index 8c2f3c057..df66ec5ea 100644
--- a/test/ui/utils/UiTestWrapper.kt
+++ b/test/ui/utils/UiTestWrapper.kt
@@ -19,7 +19,54 @@
 package ui.utils
 
 import com.intellij.remoterobot.RemoteRobot
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import java.awt.image.BufferedImage
+import java.io.ByteArrayOutputStream
+import java.io.File
+import javax.imageio.ImageIO
 
-fun uiTest(url: String = "http://127.0.0.1:8082", test: RemoteRobot.() -> Unit) {
-  RemoteRobot(url).apply(test)
+fun uiTest(testName: String = "test_${System.currentTimeMillis()}", url: String = "http://127.0.0.1:8082", test: RemoteRobot.() -> Unit) {
+  val remoteRobot = RemoteRobot(url)
+  try {
+    remoteRobot.test()
+  } catch (e: Throwable) {
+    saveScreenshot(testName, remoteRobot)
+    saveHierarchy(testName, url)
+    throw e
+  }
 }
+private val client by lazy { OkHttpClient() }
+private fun BufferedImage.save(name: String) {
+  val bytes = ByteArrayOutputStream().use { b ->
+    ImageIO.write(this, "png", b)
+    b.toByteArray()
+  }
+  File("build/reports").apply { mkdirs() }.resolve("$name.png").writeBytes(bytes)
+}
+
+fun saveScreenshot(testName: String, remoteRobot: RemoteRobot) {
+  fetchScreenShot(remoteRobot).save(testName)
+}
+
+private fun fetchScreenShot(remoteRobot: RemoteRobot): BufferedImage {
+  return remoteRobot.getScreenshot()
+}
+
+private fun saveHierarchy(testName: String, url: String) {
+  val hierarchySnapshot =
+    saveFile(url, "build/reports", "hierarchy-$testName.html")
+  if (File("build/reports/styles.css").exists().not()) {
+    saveFile("$url/styles.css", "build/reports", "styles.css")
+  }
+  println("Hierarchy snapshot: ${hierarchySnapshot.absolutePath}")
+}
+
+private fun saveFile(url: String, folder: String, name: String): File {
+  val response = client.newCall(Request.Builder().url(url).build()).execute()
+  return File(folder).apply {
+    mkdirs()
+  }.resolve(name).apply {
+    writeText(response.body?.string() ?: "")
+  }
+}
\ No newline at end of file
diff --git a/test/ui/utils/Utils.kt b/test/ui/utils/Utils.kt
index 83b897ed1..4779dc8b4 100644
--- a/test/ui/utils/Utils.kt
+++ b/test/ui/utils/Utils.kt
@@ -22,6 +22,7 @@ import com.intellij.remoterobot.fixtures.Fixture
 import com.intellij.remoterobot.fixtures.dataExtractor.RemoteText
 import com.intellij.remoterobot.utils.waitFor
 import org.assertj.swing.core.MouseButton
+import ui.pages.Editor
 import java.awt.Point
 
 fun RemoteText.doubleClickOnRight(shiftX: Int, fixture: Fixture, button: MouseButton = MouseButton.LEFT_BUTTON) {
@@ -38,22 +39,13 @@ fun RemoteText.tripleClickOnRight(shiftX: Int, fixture: Fixture, button: MouseBu
   }
 }
 
-fun RemoteText.moveMouseTo(goal: RemoteText, fixture: Fixture): Boolean {
+fun RemoteText.moveMouseTo(goal: RemoteText, editor: Editor): Boolean {
   this.moveMouse()
-  val goalPoint = goal.point
-
-  val caretDuringDragging = fixture.callJs<Boolean>(
-    """
-    const point = new java.awt.Point(${goalPoint.x}, ${goalPoint.y});
-    let isBlock = true;
-    robot.pressMouseWhileRunning(MouseButton.LEFT_BUTTON, () => {
-      robot.moveMouse(component, point)
-      isBlock = component.getEditor().getSettings().isBlockCursor();
-    })
-    isBlock
-    """
-  )
-  waitFor { fixture.callJs("component.getEditor().getSettings().isBlockCursor()") }
+  editor.runJs("robot.pressMouse(MouseButton.LEFT_BUTTON)")
+  goal.moveMouse()
+  val caretDuringDragging = editor.isBlockCursor
+  editor.runJs("robot.releaseMouse(MouseButton.LEFT_BUTTON)")
+  waitFor { editor.isBlockCursor }
   return caretDuringDragging
 }
 
@@ -71,22 +63,22 @@ fun RemoteText.moveMouseInGutterTo(goal: RemoteText, fixture: Fixture) {
   )
 }
 
-fun RemoteText.moveMouseForthAndBack(middle: RemoteText, fixture: Fixture) {
+fun RemoteText.moveMouseForthAndBack(middle: RemoteText, editor: Editor) {
   this.moveMouse()
   val initialPoint = this.point
   val middlePoint = middle.point
 
-  fixture.runJs(
+  editor.runJs(
     """
-    const initialPoint = new java.awt.Point(${initialPoint.x}, ${initialPoint.y});
-    const point = new java.awt.Point(${middlePoint.x}, ${middlePoint.y});
+    const initialPoint = new Point(${initialPoint.x}, ${initialPoint.y});
+    const point = new Point(${middlePoint.x}, ${middlePoint.y});
     robot.pressMouseWhileRunning(MouseButton.LEFT_BUTTON, () => {
       robot.moveMouse(component, point)
       robot.moveMouse(component, initialPoint)
     })
     """
   )
-  waitFor { fixture.callJs("component.getEditor().getSettings().isBlockCursor()") }
+  waitFor { editor.isBlockCursor }
 }
 
 fun String.escape(): String = this.replace("\n", "\\n")