PY-82390 Implement pycharm nightly jupyter notebook ui tests

GitOrigin-RevId: 48fc82d8c8e0c62672a8ec6e21323f1ca45dddc4
This commit is contained in:
Daniil Stankevych
2025-08-26 12:23:21 +00:00
committed by intellij-monorepo-bot
parent ef6866c012
commit 31141dd417
5 changed files with 102 additions and 24 deletions
@@ -429,11 +429,15 @@ open class UiComponent(private val data: ComponentData) : Finder, WithKeyboard {
}
}
fun tripleClick(point: Point) {
fun tripleClick(point: Point? = null) {
LOG.info("Triple click at $this${point?.let { ": $it" } ?: ""}")
withComponent { robot.click(it, point, RemoteMouseButton.LEFT, 3) }
if (point != null) {
withComponent { robot.click(it, point, RemoteMouseButton.LEFT, 3) }
}
else {
withComponent { robot.tripleClick(it) }
}
}
fun rightClick(point: Point? = null) {
LOG.info("Right click at $this${point?.let { ": $it" } ?: ""}")
if (point != null) {
@@ -25,7 +25,6 @@ class ToolWindowLeftToolbarUi(data: ComponentData) : ToolWindowToolbarUi(data) {
val servicesButton = stripeButton("Services")
val terminalButton = stripeButton("Terminal")
val problemsButton = stripeButton("Problems")
val jupyterButton = stripeButton("Jupyter")
val moreButton = stripeButton("More")
val debugButton = stripeButton("Debug")
val findButton = stripeButton("Find")
@@ -1,19 +1,33 @@
package com.intellij.driver.sdk.ui.components.kotlin
package com.intellij.driver.sdk.ui.components.notebooks
import com.intellij.driver.client.Driver
import com.intellij.driver.client.service
import com.intellij.driver.sdk.*
import com.intellij.driver.sdk.PsiFile
import com.intellij.driver.sdk.PsiManager
import com.intellij.driver.sdk.invokeAction
import com.intellij.driver.sdk.invokeActionWithRetries
import com.intellij.driver.sdk.singleProject
import com.intellij.driver.sdk.step
import com.intellij.driver.sdk.ui.Finder
import com.intellij.driver.sdk.ui.components.ComponentData
import com.intellij.driver.sdk.ui.components.UiComponent
import com.intellij.driver.sdk.ui.components.common.EditorComponentImpl
import com.intellij.driver.sdk.ui.components.common.JEditorUiComponent
import com.intellij.driver.sdk.ui.components.common.editor
import com.intellij.driver.sdk.ui.components.common.ideFrame
import com.intellij.driver.sdk.ui.components.common.toolwindows.ToolWindowRightToolbarUi
import com.intellij.driver.sdk.ui.components.common.toolwindows.projectView
import com.intellij.driver.sdk.ui.components.elements.JLabelUiComponent
import com.intellij.driver.sdk.ui.components.elements.JTextFieldUI
import com.intellij.driver.sdk.ui.components.elements.JcefOffScreenViewComponent
import com.intellij.driver.sdk.ui.components.elements.LetsPlotComponent
import com.intellij.driver.sdk.ui.components.elements.NotebookTableOutputUi
import com.intellij.driver.sdk.ui.components.elements.popup
import com.intellij.driver.sdk.ui.pasteText
import com.intellij.driver.sdk.ui.should
import com.intellij.driver.sdk.ui.ui
import com.intellij.driver.sdk.waitFor
import com.intellij.driver.sdk.waitForCodeAnalysis
import org.intellij.lang.annotations.Language
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
@@ -57,6 +71,20 @@ class NotebookEditorUiComponent(private val data: ComponentData) : JEditorUiComp
get() = x("//div[@myicon='restartKernel.svg']")
private val interruptKernel
get() = x("//div[@myicon='stop.svg']")
val notebookCellOutputs: List<UiComponent>
get() = xx("//div[@class='FullEditorWidthRenderer']//div[@class='EditorComponentImpl']").list()
val jcefOffScreens: List<JcefOffScreenViewComponent>
get() = xx("//div[@class='JcefOffScreenViewComponent']", JcefOffScreenViewComponent::class.java).list()
val notebookCellExecutionInfos: List<JLabelUiComponent>
get() = xx("//div[@accessiblename='ExecutionLabel']", JLabelUiComponent::class.java).list()
val notebookTables: List<NotebookTableOutputUi>
get() = xx("//div[@class='LoadingDecoratorLayeredPaneImpl']/div[@class='JPanel'][descendant::div[@class='TableResultView']][descendant::div[@class='TwoSideComponent']]", NotebookTableOutputUi::class.java).list()
val notebookPlots: List<LetsPlotComponent>
get() = xx("//div[@class='LetsPlotComponent']", LetsPlotComponent::class.java).list()
val toolbar: UiComponent
get() = x("//div[@class='JupyterFileEditorToolbar']")
val imagePanel: List<UiComponent>
get() = xx("//div[@class='FullEditorWidthRenderer']//div[@class='ImagePanel']").list()
override val editorComponent: EditorComponentImpl
get() = when {
@@ -161,24 +189,6 @@ class NotebookEditorUiComponent(private val data: ComponentData) : JEditorUiComp
""".trimIndent()
).list()
val notebookCellOutputs: List<UiComponent>
get() = xx("//div[@class='FullEditorWidthRenderer']//div[@class='EditorComponentImpl']").list()
val notebookMdCellsAsHtml: List<JcefOffScreenViewComponent>
get() = xx("//div[@class='JcefOffScreenViewComponent']", JcefOffScreenViewComponent::class.java).list()
val notebookCellExecutionInfos: List<JLabelUiComponent>
get() = xx("//div[@accessiblename='ExecutionLabel']", JLabelUiComponent::class.java).list()
val notebookTables: List<NotebookTableOutputUi>
get() = xx("//div[@class='LoadingDecoratorLayeredPaneImpl']/div[@class='JPanel'][descendant::div[@class='TableResultView']][descendant::div[@class='TwoSideComponent']]", NotebookTableOutputUi::class.java).list()
val notebookPlots: List<LetsPlotComponent>
get() = xx("//div[@class='LetsPlotComponent']", LetsPlotComponent::class.java).list()
val toolbar: UiComponent
get() = x("//div[@class='JupyterFileEditorToolbar']")
fun JLabelUiComponent.getExecutionTimeInMs(): Long = step("Get cell execution time") {
this.getText().run {
val matchSeconds = Regex("\\d+s").find(this)?.value?.substringBefore("s")?.toLong() ?: 0
@@ -188,3 +198,56 @@ class NotebookEditorUiComponent(private val data: ComponentData) : JEditorUiComp
}
}
}
enum class NotebookType(val typeName: String, val newNotebookActionId: String) {
KOTLIN("Kotlin", "NewKotlinNotebookAction"),
JUPYTER("Jupyter", "NewJupyterNotebookAction");
}
/**
* Creates a new Kotlin or Jupyter notebook in the IDE.
*
* @param name The name for the new notebook. Defaults to "New Notebook".
* @param type The type of notebook to create.
*/
fun Driver.createNewNotebook(name: String = "New Notebook", type: NotebookType) {
ideFrame {
projectView {
projectViewTree.run {
waitFor("wait for project tree to load", 30.seconds) {
getAllTexts().isNotEmpty()
}
getAllTexts().first().click()
}
}
invokeAction(type.newNotebookActionId, false)
popup().run {
x("//div[@accessiblename='Name']", JTextFieldUI::class.java).click()
keyboard {
driver.ui.pasteText(name)
should("expect $name in the popup") {
getAllTexts().any { it.text == name }
}
enter() // submit the popup
}
}
waitFor("the editor is present") {
notebookEditor().present()
}
}
}
fun Driver.closeJupyterVariablesToolwindow() {
ideFrame {
val rightToolbar = xx(ToolWindowRightToolbarUi::class.java) { byClass("ToolWindowRightToolbar") }.list().firstOrNull()
?: return@ideFrame
val varsButton = rightToolbar.stripeButton("Jupyter Variables")
if (varsButton.present()) {
varsButton.close()
}
}
}
@@ -38,6 +38,14 @@ interface Robot {
fun rightClick(component: Component)
fun focus(component: Component)
fun doubleClick(component: Component)
/**
* Performs a left mouse triple-click on the given component.
*
* Triple-click is commonly used to select a whole line or statement.
* @param c the target component to receive the triple-click
*/
fun tripleClick(component: Component)
fun cleanUpWithoutDisposingWindows()
fun isReadyForInput(component: Component): Boolean
fun focusAndWaitForFocusGain(component: Component)
@@ -134,6 +134,10 @@ internal class SmoothRobot @JvmOverloads constructor(
click(c, MouseButton.LEFT_BUTTON, 2)
}
fun tripleClick(c: Component) {
click(c, MouseButton.LEFT_BUTTON, 3)
}
fun pressMouse(mouseButton: RemoteMouseButton) {
pressMouse(mouseButton.toAssertJ())
}