This is an automated email from the ASF dual-hosted git repository.
ruanwenjun pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler.git
The following commit(s) were added to refs/heads/dev by this push:
new 9f68254ffa [Improvement-18712][E2E] Fix flaky e2e test (#18715)
9f68254ffa is described below
commit 9f68254ffa7446a4bd52969dc5fdd6471b0d6850
Author: xiangzihao <[email protected]>
AuthorDate: Fri Oct 16 18:49:15 2026 +0800
[Improvement-18712][E2E] Fix flaky e2e test (#18715)
---
.../e2e/cases/tasks/PythonTaskE2ETest.java | 2 +-
.../e2e/cases/tasks/ShellTaskE2ETest.java | 6 +-
.../e2e/cases/workflow/BaseWorkflowE2ETest.java | 20 ++-
.../e2e/pages/common/CodeEditor.java | 138 +++++++++------------
.../e2e/pages/datasource/DataSourcePage.java | 18 +--
.../e2e/pages/project/workflow/WorkflowForm.java | 21 +++-
.../pages/project/workflow/WorkflowRunDialog.java | 24 +---
.../pages/project/workflow/task/ShellTaskForm.java | 4 -
.../pages/project/workflow/task/TaskNodeForm.java | 47 ++++---
.../e2e/pages/resource/FileManagePage.java | 23 ++--
.../e2e/core/DolphinSchedulerExtension.java | 69 ++++++++++-
.../dolphinscheduler/e2e/core/UiInteraction.java | 63 ++++++++++
.../e2e/core/WebDriverWaitFactory.java | 2 +-
dolphinscheduler-ui/src/components/modal/index.tsx | 18 ++-
.../src/components/monaco-editor/index.tsx | 12 +-
.../workflow/definition/components/start-modal.tsx | 1 +
.../resource/components/resource/folder/index.tsx | 1 +
17 files changed, 316 insertions(+), 153 deletions(-)
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/PythonTaskE2ETest.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/PythonTaskE2ETest.java
index cb18e58848..09a3e7b4e0 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/PythonTaskE2ETest.java
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/PythonTaskE2ETest.java
@@ -279,7 +279,7 @@ public class PythonTaskE2ETest extends BaseWorkflowE2ETest {
.<PythonTaskForm>addTask(WorkflowForm.TaskType.PYTHON)
.script(pythonScripts)
.name(taskName)
- .selectResource(testFileName)
+ .selectResource(testFileName + ".sh")
.addParam("file_name", String.format("%s.sh", testFileName))
.submit()
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java
index 7a547201c9..be2f60e4f8 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java
@@ -35,6 +35,8 @@ import
org.apache.dolphinscheduler.e2e.pages.security.SecurityPage;
import org.apache.dolphinscheduler.e2e.pages.security.TenantPage;
import org.apache.dolphinscheduler.e2e.pages.security.UserPage;
+import java.util.UUID;
+
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
@@ -212,7 +214,7 @@ public class ShellTaskE2ETest extends BaseWorkflowE2ETest {
@Test
void testRunShellTasks_UsingResourceFile() {
- String testFileName = "echo";
+ String testFileName = "echo_" + UUID.randomUUID();
new ResourcePage(browser)
.goToNav(ResourcePage.class)
.goToTab(FileManagePage.class)
@@ -231,7 +233,7 @@ public class ShellTaskE2ETest extends BaseWorkflowE2ETest {
.<ShellTaskForm>addTask(WorkflowForm.TaskType.SHELL)
.script("cat " + testFileName + ".sh")
.name(taskName)
- .selectResource(testFileName)
+ .selectResource(testFileName + ".sh")
.submit()
.submit()
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/workflow/BaseWorkflowE2ETest.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/workflow/BaseWorkflowE2ETest.java
index 550f83d719..fd3d610079 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/workflow/BaseWorkflowE2ETest.java
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/workflow/BaseWorkflowE2ETest.java
@@ -91,17 +91,25 @@ public abstract class BaseWorkflowE2ETest {
final ProjectDetailPage projectPage = new ProjectPage(browser)
.goToNav(ProjectPage.class)
.goTo(projectName);
- return await()
+ log.info("Waiting for workflow instance success: project={},
workflow={}", projectName, workflowName);
+ return await("Workflow instance should succeed: " + workflowName)
.until(() -> {
browser.navigate().refresh();
- return projectPage
+ List<WorkflowInstanceTab.Row> instances = projectPage
.goToTab(WorkflowInstanceTab.class)
.instances()
.stream()
- .filter(it ->
it.workflowInstanceName().startsWith(workflowName))
- .filter(WorkflowInstanceTab.Row::isSuccess)
- .findFirst()
- .orElse(null);
+ .filter(it ->
it.workflowInstanceName().startsWith(workflowName + "-"))
+ .collect(Collectors.toList());
+ for (WorkflowInstanceTab.Row instance : instances) {
+ if (instance.isFailed()) {
+ throw new IllegalStateException(
+ "Workflow instance failed while waiting
for success: project="
+ + projectName + ", instance=" +
instance.workflowInstanceName()
+ + ", url=" +
browser.getCurrentUrl());
+ }
+ }
+ return
instances.stream().filter(WorkflowInstanceTab.Row::isSuccess).findFirst().orElse(null);
}, Objects::nonNull);
}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/CodeEditor.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/CodeEditor.java
index 60f1be2ee9..09bdeb268d 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/CodeEditor.java
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/CodeEditor.java
@@ -17,112 +17,94 @@
package org.apache.dolphinscheduler.e2e.pages.common;
-import org.apache.dolphinscheduler.e2e.core.Constants;
+import org.apache.dolphinscheduler.e2e.core.UiInteraction;
import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
-import java.util.List;
+import java.util.Map;
+import java.util.UUID;
-import lombok.Getter;
-import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
-import org.junit.platform.commons.util.StringUtils;
+import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
-import org.openqa.selenium.support.FindBy;
-import org.openqa.selenium.support.FindBys;
-import org.openqa.selenium.support.PageFactory;
-import org.openqa.selenium.support.ui.ExpectedConditions;
-@Getter
@Slf4j
public final class CodeEditor {
- @FindBys({
- @FindBy(className = "monaco-editor"),
- @FindBy(className = "view-line"),
- })
- private List<WebElement> editor;
-
- @FindBy(className = "pre-tasks-model")
- private WebElement scrollBar;
-
- private WebDriver driver;
+ private final WebDriver driver;
public CodeEditor(WebDriver driver) {
- PageFactory.initElements(driver, this);
this.driver = driver;
}
- @SneakyThrows
public CodeEditor content(String content) {
-
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(editor.get(0)));
-
- Actions actions = new Actions(this.driver);
-
- List<String> contentList =
List.of(content.split(Constants.LINE_SEPARATOR));
-
- try {
- ((JavascriptExecutor)
driver).executeScript("arguments[0].scrollIntoView();", scrollBar);
- } catch (org.openqa.selenium.NoSuchElementException ignored) {
- log.warn("scroll bar not found, skipping...");
+ UiInteraction.click(driver, ".monaco-editor .overflow-guard");
+ WebElement focusedInput = driver.switchTo().activeElement();
+ if (!"textarea".equals(focusedInput.getTagName())
+ || !focusedInput.getDomAttribute("class").contains("inputarea")
+ || focusedInput.getDomAttribute("readonly") != null) {
+ throw new IllegalStateException("Expected focus in an editable
Monaco input: " + focusedInput.getTagName());
}
-
- for (int i = 0; i < contentList.size(); i++) {
- String editorLineText;
- String inputContent = contentList.get(i);
- if (i == 0) {
- actions.moveToElement(editor.get(i))
- .click()
- .sendKeys(inputContent)
- .sendKeys(Constants.LINE_SEPARATOR)
- .perform();
- continue;
+ WebElement editor =
focusedInput.findElement(By.xpath("ancestor::*[@data-editor-model-version][1]"));
+ String initialVersion =
editor.getDomAttribute("data-editor-model-version");
+ int initialLength =
Integer.parseInt(editor.getDomAttribute("data-editor-content-length"));
+ String normalized = content.replace("\r\n", "\n");
+ log.info("Replacing Monaco content: characters={}, initialVersion={},
initialLength={}",
+ normalized.length(), initialVersion, initialLength);
+ if (!normalized.isEmpty() || initialLength != 0) {
+ Actions input = new
Actions(driver).keyDown(Keys.CONTROL).sendKeys("a").keyUp(Keys.CONTROL);
+ if (normalized.isEmpty()) {
+ input.sendKeys(Keys.BACK_SPACE).perform();
} else {
- editorLineText = editor.get(i).getText();
+ clipboard(normalized);
+
input.keyDown(Keys.CONTROL).sendKeys("v").keyUp(Keys.CONTROL).perform();
}
+ WebDriverWaitFactory.createWebDriverWait(driver)
+ .withMessage("Monaco paste did not update the model to " +
normalized.length() + " characters")
+ .until(ignored ->
!initialVersion.equals(editor.getDomAttribute("data-editor-model-version"))
+ &&
Integer.parseInt(editor.getDomAttribute("data-editor-content-length")) ==
normalized
+ .length());
+ }
- if (StringUtils.isNotBlank(inputContent)) {
- if (editorLineText.isEmpty()) {
- actions.moveToElement(editor.get(i))
- .click()
- .sendKeys(inputContent)
- .sendKeys(Constants.LINE_SEPARATOR)
- .perform();
- Thread.sleep(Constants.DEFAULT_SLEEP_MILLISECONDS);
- } else {
- for (int p = 0; p < editorLineText.strip().length(); p++) {
- clearLine(actions, editor.get(i));
- }
- if (!editorLineText.isEmpty()) {
- clearLine(actions, editor.get(i));
- }
- actions.moveToElement(editor.get(i))
- .click()
- .sendKeys(inputContent)
- .sendKeys(Constants.LINE_SEPARATOR)
- .perform();
- Thread.sleep(Constants.DEFAULT_SLEEP_MILLISECONDS);
- }
- } else {
- actions.moveToElement(editor.get(i))
- .click()
- .sendKeys(Constants.LINE_SEPARATOR)
- .perform();
- Thread.sleep(Constants.DEFAULT_SLEEP_MILLISECONDS);
+ if (!normalized.isEmpty()) {
+ // Clear the clipboard so an unsuccessful copy cannot validate the
paste payload.
+ String sentinel = UUID.randomUUID().toString();
+ clipboard(sentinel);
+ new Actions(driver)
+ .keyDown(Keys.CONTROL).sendKeys("a").keyUp(Keys.CONTROL)
+ .keyDown(Keys.CONTROL).sendKeys("c").keyUp(Keys.CONTROL)
+ .perform();
+ String actual = WebDriverWaitFactory.createWebDriverWait(driver)
+ .withMessage("Monaco copy did not populate the clipboard")
+ .until(ignored -> {
+ String copied = clipboard(null);
+ return sentinel.equals(copied) ? null : copied;
+ }).replace("\r\n", "\n");
+ if (!normalized.equals(actual)) {
+ throw new IllegalStateException("Monaco content mismatch:
expected characters=" + normalized.length()
+ + ", copied characters=" + actual.length());
}
}
-
+ new Actions(driver).sendKeys(Keys.ARROW_RIGHT).perform();
+ log.info("Monaco content verified: characters={}",
normalized.length());
return this;
}
- private void clearLine(Actions actions, WebElement element) {
- actions.moveToElement(element)
- .click()
- .sendKeys(Keys.BACK_SPACE)
- .perform();
+ private String clipboard(String value) {
+ Map<?, ?> result = (Map<?, ?>) ((JavascriptExecutor)
driver).executeAsyncScript(
+ "const value = arguments[0], done = arguments[arguments.length
- 1];"
+ + "if (!navigator.clipboard) { done({error: 'Clipboard
API unavailable; check secure origin configuration'}); return; }"
+ + "const operation = value === null ?
navigator.clipboard.readText() : navigator.clipboard.writeText(value);"
+ + "operation.then(text => done({text: value === null ?
text : value}),"
+ + "error => done({error: error.name + ': ' +
error.message}));",
+ value);
+ if (result.containsKey("error")) {
+ throw new IllegalStateException("Monaco clipboard operation
failed: " + result.get("error"));
+ }
+ return (String) result.get("text");
}
}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/datasource/DataSourcePage.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/datasource/DataSourcePage.java
index aa91da1fd9..2b356a347f 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/datasource/DataSourcePage.java
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/datasource/DataSourcePage.java
@@ -17,10 +17,12 @@
package org.apache.dolphinscheduler.e2e.pages.datasource;
+import org.apache.dolphinscheduler.e2e.core.UiInteraction;
import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
import java.util.List;
+import java.util.Locale;
import lombok.Getter;
@@ -63,16 +65,16 @@ public class DataSourcePage extends NavBarPage implements
NavBarPage.NavBarItem
public DataSourcePage createDataSource(String dataSourceType, String
dataSourceName, String dataSourceDescription,
String ip, String port, String
userName, String password, String database,
String jdbcParams) {
- buttonCreateDataSource().click();
+ UiInteraction.click(driver, ".btn-create-data-source");
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.visibilityOf(dataSourceModal));
- By dataSourceTypeBox = By.className(dataSourceType.toUpperCase() +
"-box");
- WebElement dataSourceTypeButton =
WebDriverWaitFactory.createWebDriverWait(driver)
-
.until(ExpectedConditions.elementToBeClickable(dataSourceTypeBox));
- dataSourceTypeButton.click();
+ String typeLabel = dataSourceType.toUpperCase(Locale.ROOT);
+ // Labels such as HIVE/IMPALA are class tokens, but not valid
unescaped CSS identifiers.
+ String typeClass = typeLabel.replace("\\", "\\\\").replace("'", "\\'")
+ "-box";
+ UiInteraction.click(driver, ".dialog-source-modal [class~='" +
typeClass + "']");
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.textToBePresentInElementLocated(
- By.className("dialog-create-data-source"),
dataSourceType.toUpperCase()));
+ By.className("dialog-create-data-source"), typeLabel));
createDataSourceForm().inputDataSourceName().sendKeys(dataSourceName);
createDataSourceForm().inputDataSourceDescription().sendKeys(dataSourceDescription);
@@ -90,7 +92,9 @@ public class DataSourcePage extends NavBarPage implements
NavBarPage.NavBarItem
createDataSourceForm().inputJdbcParams().sendKeys(jdbcParams);
}
- createDataSourceForm().buttonSubmit().click();
+ UiInteraction.click(driver, ".dialog-create-data-source .btn-submit");
+
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.invisibilityOfElementLocated(
+ By.cssSelector(".dialog-create-data-source")));
return this;
}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java
index 7c47eb36a1..ada4ddc3f4 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java
@@ -30,6 +30,7 @@ import java.util.List;
import lombok.Getter;
import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
@@ -44,6 +45,7 @@ import com.google.common.io.Resources;
@SuppressWarnings("UnstableApiUsage")
@Getter
+@Slf4j
public final class WorkflowForm {
private WebDriver driver;
@@ -67,15 +69,28 @@ public final class WorkflowForm {
@SneakyThrows
@SuppressWarnings("unchecked")
public <T> T addTask(TaskType type) {
- final WebElement task = driver.findElement(By.className("task-item-" +
type.name()));
+ log.info("Waiting for DAG canvas and task menu: type={}, url={}",
type, driver.getCurrentUrl());
+ WebDriverWaitFactory.createWebDriverWait(driver)
+ .withMessage("DAG graph has not mounted before adding task " +
type)
+
.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".dag-container
.x6-graph-svg")));
+ // Task entries are populated by the asynchronous DAG menu request
after navigation.
+ final WebElement task =
WebDriverWaitFactory.createWebDriverWait(driver)
+ .withMessage("DAG task menu has not rendered a draggable entry
for " + type)
+ .until(ExpectedConditions.visibilityOfElementLocated(
+ By.cssSelector(".task-item-" + type.name() +
"[draggable='true']")));
final WebElement canvas =
driver.findElement(By.className("dag-container"));
+ log.info("Dragging DAG task: type={}, sourceLocation={},
sourceSize={}, canvasLocation={}, canvasSize={}",
+ type, task.getLocation(), task.getSize(),
canvas.getLocation(), canvas.getSize());
final JavascriptExecutor js = (JavascriptExecutor) driver;
final String dragAndDrop = String.join("\n",
Resources.readLines(Resources.getResource("dragAndDrop.js"),
StandardCharsets.UTF_8));
js.executeScript(dragAndDrop, task, canvas);
-
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions
- .visibilityOfElementLocated(By.xpath("//*[contains(text(),
'Current node settings')]")));
+ WebDriverWaitFactory.createWebDriverWait(driver)
+ .withMessage("Task settings did not open after dragging " +
type)
+ .until(ExpectedConditions
+
.visibilityOfElementLocated(By.xpath("//*[contains(text(), 'Current node
settings')]")));
+ log.info("DAG task settings opened: type={}", type);
switch (type) {
case SHELL:
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowRunDialog.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowRunDialog.java
index 8bfa738e2f..4602fea0e0 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowRunDialog.java
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowRunDialog.java
@@ -17,15 +17,12 @@
package org.apache.dolphinscheduler.e2e.pages.project.workflow;
+import org.apache.dolphinscheduler.e2e.core.UiInteraction;
import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
import lombok.Getter;
import org.openqa.selenium.By;
-import org.openqa.selenium.WebElement;
-import org.openqa.selenium.support.FindBy;
-import org.openqa.selenium.support.FindBys;
-import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;
@Getter
@@ -33,29 +30,14 @@ public final class WorkflowRunDialog {
private final WorkflowDefinitionTab parent;
- @FindBys({
- @FindBy(xpath = "//div[contains(text(), 'Please set the parameters
before starting')]/../.."),
- @FindBy(className = "btn-submit")
- })
- private WebElement buttonSubmit;
-
public WorkflowRunDialog(WorkflowDefinitionTab parent) {
this.parent = parent;
-
- PageFactory.initElements(parent().driver(), this);
}
public WorkflowDefinitionTab submit() {
- By runDialogTitleXpath =
- By.xpath(String.format("//*[contains(text(), '%s')]", "Please
set the parameters before starting"));
- WebDriverWaitFactory.createWebDriverWait(parent.driver())
-
.until(ExpectedConditions.visibilityOfElementLocated(runDialogTitleXpath));
- WebDriverWaitFactory.createWebDriverWait(parent.driver())
-
.until(ExpectedConditions.elementToBeClickable(buttonSubmit()));
-
- buttonSubmit().click();
+ UiInteraction.click(parent.driver(), ".dialog-start-workflow
.btn-submit");
WebDriverWaitFactory.createWebDriverWait(parent.driver())
-
.until(ExpectedConditions.invisibilityOfElementLocated(runDialogTitleXpath));
+
.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(".dialog-start-workflow")));
return parent();
}
}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/ShellTaskForm.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/ShellTaskForm.java
index d84708f290..fd15e384e9 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/ShellTaskForm.java
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/ShellTaskForm.java
@@ -17,12 +17,10 @@
package org.apache.dolphinscheduler.e2e.pages.project.workflow.task;
-import org.apache.dolphinscheduler.e2e.core.Constants;
import org.apache.dolphinscheduler.e2e.pages.common.CodeEditor;
import org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowForm;
import lombok.Getter;
-import lombok.SneakyThrows;
import org.openqa.selenium.WebDriver;
@@ -41,9 +39,7 @@ public final class ShellTaskForm extends TaskNodeForm {
this.driver = parent.driver();
}
- @SneakyThrows
public ShellTaskForm script(String script) {
- Thread.sleep(Constants.DEFAULT_SLEEP_MILLISECONDS);
codeEditor.content(script);
return this;
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java
index bec099385d..acd71ca770 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java
@@ -17,12 +17,16 @@
package org.apache.dolphinscheduler.e2e.pages.project.workflow.task;
+import org.apache.dolphinscheduler.e2e.core.UiInteraction;
import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
import org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowForm;
import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
@@ -35,6 +39,7 @@ import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;
@Getter
+@Slf4j
public abstract class TaskNodeForm {
@FindBys({
@@ -162,22 +167,34 @@ public abstract class TaskNodeForm {
return this;
}
- public TaskNodeForm selectResource(String resourceName) {
- ((JavascriptExecutor)
parent().driver()).executeScript("arguments[0].click();", selectResource);
-
+ public TaskNodeForm selectResource(String resourceFileName) {
+ UiInteraction.click(parent.driver(), ".resource-select
.n-base-selection");
+ WebElement filter =
WebDriverWaitFactory.createWebDriverWait(parent.driver())
+
.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".resource-select
input")));
+ filter.clear();
+ filter.sendKeys(resourceFileName);
final By optionsLocator = By.className("n-tree-node-content__text");
-
- WebDriverWaitFactory.createWebDriverWait(parent().driver())
-
.until(ExpectedConditions.visibilityOfElementLocated(optionsLocator));
-
- parent().driver()
- .findElements(optionsLocator)
- .stream()
- .filter(it -> it.getText().startsWith(resourceName))
- .findFirst()
- .orElseThrow(() -> new RuntimeException("No such resource: " +
resourceName))
- .click();
-
+ AtomicReference<List<String>> visibleNames = new AtomicReference<>();
+ WebElement resource =
WebDriverWaitFactory.createWebDriverWait(parent.driver())
+ .withMessage(() -> "Resource not found by exact file name: " +
resourceFileName
+ + "; visible resources=" + visibleNames.get())
+ .until(ignored -> {
+ List<WebElement> options =
parent.driver().findElements(optionsLocator).stream()
+ .filter(WebElement::isDisplayed)
+ .collect(Collectors.toList());
+
visibleNames.set(options.stream().map(WebElement::getText).collect(Collectors.toList()));
+ List<WebElement> matches = options.stream()
+ .filter(it ->
resourceFileName.equals(it.getText().trim()))
+ .collect(Collectors.toList());
+ if (matches.size() > 1) {
+ throw new IllegalStateException("Ambiguous resource
file name: " + resourceFileName
+ + "; visible resources=" + visibleNames.get());
+ }
+ return matches.isEmpty() ? null : matches.get(0);
+ });
+ log.info("Selecting resource by exact file name: requested={},
selected={}", resourceFileName,
+ resource.getText());
+ resource.click();
parent.driver().switchTo().activeElement().sendKeys(Keys.ESCAPE);
return this;
}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/FileManagePage.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/FileManagePage.java
index 10ba4e2cba..65f5a170f8 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/FileManagePage.java
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/FileManagePage.java
@@ -20,6 +20,7 @@ package org.apache.dolphinscheduler.e2e.pages.resource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
+import org.apache.dolphinscheduler.e2e.core.UiInteraction;
import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
import org.apache.dolphinscheduler.e2e.pages.common.CodeEditor;
import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
@@ -89,21 +90,23 @@ public class FileManagePage extends NavBarPage implements
ResourcePage.Tab {
public FileManagePage createDirectory(String name) {
waitForPageLoading();
- buttonCreateDirectory().click();
+ UiInteraction.click(driver, ".btn-create-directory");
createDirectoryBox().inputDirectoryName().sendKeys(name);
- WebDriverWaitFactory.createWebDriverWait(driver)
-
.until(ExpectedConditions.elementToBeClickable(createDirectoryBox().buttonSubmit()));
- createDirectoryBox().buttonSubmit().click();
+ UiInteraction.click(driver, ".dialog-create-directory .btn-submit");
+
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.invisibilityOfElementLocated(
+ By.cssSelector(".dialog-create-directory")));
return this;
}
public FileManagePage cancelCreateDirectory(String name) {
waitForPageLoading();
- buttonCreateDirectory().click();
+ UiInteraction.click(driver, ".btn-create-directory");
createDirectoryBox().inputDirectoryName().sendKeys(name);
- createDirectoryBox().buttonCancel().click();
+ UiInteraction.click(driver, ".dialog-create-directory .btn-cancel");
+
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.invisibilityOfElementLocated(
+ By.cssSelector(".dialog-create-directory")));
return this;
}
@@ -136,12 +139,12 @@ public class FileManagePage extends NavBarPage implements
ResourcePage.Tab {
.orElseThrow(() -> new RuntimeException(String.format("No %s
in file manage list", directoryName)))
.click();
- buttonCreateDirectory().click();
+ UiInteraction.click(driver, ".btn-create-directory");
createDirectoryBox().inputDirectoryName().sendKeys(subDirectoryName);
- WebDriverWaitFactory.createWebDriverWait(driver)
-
.until(ExpectedConditions.elementToBeClickable(createDirectoryBox().buttonSubmit()));
- createDirectoryBox().buttonSubmit().click();
+ UiInteraction.click(driver, ".dialog-create-directory .btn-submit");
+
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.invisibilityOfElementLocated(
+ By.cssSelector(".dialog-create-directory")));
return this;
}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/DolphinSchedulerExtension.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/DolphinSchedulerExtension.java
index 351363154e..5dc3ba6a20 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/DolphinSchedulerExtension.java
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/DolphinSchedulerExtension.java
@@ -25,13 +25,16 @@ import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -41,8 +44,14 @@ import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.TestWatcher;
+import org.openqa.selenium.By;
+import org.openqa.selenium.OutputType;
import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeOptions;
+import org.openqa.selenium.logging.LogType;
+import org.openqa.selenium.logging.LoggingPreferences;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testcontainers.Testcontainers;
import org.testcontainers.containers.BindMode;
@@ -56,7 +65,7 @@ import com.google.common.base.Strings;
import com.google.common.net.HostAndPort;
@Slf4j
-final class DolphinSchedulerExtension implements BeforeAllCallback,
AfterAllCallback, BeforeEachCallback {
+final class DolphinSchedulerExtension implements BeforeAllCallback,
AfterAllCallback, BeforeEachCallback, TestWatcher {
private final boolean LOCAL_MODE =
Objects.equals(System.getProperty("local"), "true");
@@ -73,6 +82,7 @@ final class DolphinSchedulerExtension implements
BeforeAllCallback, AfterAllCall
private String rootPath;
private Path record;
+ private Throwable firstFailure;
private final String serviceName = "dolphinscheduler";
@@ -98,11 +108,18 @@ final class DolphinSchedulerExtension implements
BeforeAllCallback, AfterAllCall
}
browser.start();
ChromeOptions chromeOptions = new ChromeOptions();
+ chromeOptions.setExperimentalOption("prefs",
Map.of("profile.default_content_setting_values.clipboard", 1));
+ LoggingPreferences logging = new LoggingPreferences();
+ logging.enable(LogType.BROWSER, Level.ALL);
+ chromeOptions.setCapability("goog:loggingPrefs", logging);
chromeOptions.addArguments("--allow-running-insecure-content");
chromeOptions.addArguments(String.format("--unsafely-treat-insecure-origin-as-secure=http://%s:%s",
address.getHost(), address.getPort()));
driver = new RemoteWebDriver(browser.getSeleniumAddress(),
chromeOptions);
+ log.info("E2E environment: local={}, m1={}, address={}, browser={},
recording={}",
+ LOCAL_MODE, M1_CHIP_FLAG, address, driver.getCapabilities(),
record);
+
driver.manage().timeouts()
.implicitlyWait(Duration.ofSeconds(1))
.pageLoadTimeout(Duration.ofSeconds(5));
@@ -185,7 +202,7 @@ final class DolphinSchedulerExtension implements
BeforeAllCallback, AfterAllCall
@Override
public void afterAll(ExtensionContext context) {
- browser.afterTest(new TestDescription(context), Optional.empty());
+ browser.afterTest(new TestDescription(context),
Optional.ofNullable(firstFailure));
browser.stop();
if (compose != null) {
compose.stop();
@@ -194,12 +211,60 @@ final class DolphinSchedulerExtension implements
BeforeAllCallback, AfterAllCall
@Override
public void beforeEach(ExtensionContext context) {
+ log.info("Starting E2E test: {}, url={}", context.getUniqueId(),
driver.getCurrentUrl());
final Object instance = context.getRequiredTestInstance();
Stream.of(instance.getClass().getDeclaredFields())
.filter(f -> WebDriver.class.isAssignableFrom(f.getType()))
.forEach(it -> setDriver(instance, it));
}
+ @Override
+ public void testFailed(ExtensionContext context, Throwable cause) {
+ if (firstFailure == null) {
+ firstFailure = cause;
+ }
+ log.error("E2E test failed: {}", context.getUniqueId(), cause);
+ String name = context.getRequiredTestClass().getSimpleName() + "-"
+ + context.getRequiredTestMethod().getName();
+ try {
+ Files.write(record.resolve(name + ".png"),
driver.getScreenshotAs(OutputType.BYTES));
+ Files.write(record.resolve(name + ".html"),
driver.getPageSource().getBytes(StandardCharsets.UTF_8));
+ log.error("E2E failure screenshot and DOM saved: {}",
record.resolve(name));
+ WebElement focused = driver.switchTo().activeElement();
+ log.error("E2E browser state: url={}, window={}, focusTag={},
focusClass={}",
+ driver.getCurrentUrl(), driver.manage().window().getSize(),
+ focused.getTagName(), focused.getDomAttribute("class"));
+ for (WebElement modal :
driver.findElements(By.cssSelector("[data-modal-ready]"))) {
+ if (modal.isDisplayed()) {
+ log.error("E2E visible modal: class={}, ready={},
location={}, size={}",
+ modal.getDomAttribute("class"),
modal.getDomAttribute("data-modal-ready"),
+ modal.getLocation(), modal.getSize());
+ }
+ }
+ for (WebElement feedback :
driver.findElements(By.cssSelector(".n-form-item-feedback-wrapper"))) {
+ if (feedback.isDisplayed() && !feedback.getText().isEmpty()) {
+ log.error("E2E form feedback: {}", feedback.getText());
+ }
+ }
+ for (WebElement editor :
driver.findElements(By.cssSelector("[data-editor-model-version]"))) {
+ log.error("E2E editor: version={}, characters={}",
+ editor.getDomAttribute("data-editor-model-version"),
+ editor.getDomAttribute("data-editor-content-length"));
+ }
+ } catch (Exception diagnosticFailure) {
+ cause.addSuppressed(diagnosticFailure);
+ log.error("Failed to capture E2E browser state for {}",
context.getUniqueId(), diagnosticFailure);
+ }
+ try {
+ String browserLogs =
driver.manage().logs().get(LogType.BROWSER).getAll().toString();
+ log.error("E2E browser console: {}", browserLogs);
+ Files.write(record.resolve(name + "-browser.log"),
browserLogs.getBytes(StandardCharsets.UTF_8));
+ } catch (Exception diagnosticFailure) {
+ cause.addSuppressed(diagnosticFailure);
+ log.error("Failed to capture E2E browser console for {}",
context.getUniqueId(), diagnosticFailure);
+ }
+ }
+
private void setDriver(Object object, Field field) {
try {
field.setAccessible(true);
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/UiInteraction.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/UiInteraction.java
new file mode 100644
index 0000000000..9b1175f023
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/UiInteraction.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dolphinscheduler.e2e.core;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+@Slf4j
+public final class UiInteraction {
+
+ private UiInteraction() {
+ }
+
+ /**
+ * Wait for the modal's enter transition before performing exactly one
native click.
+ */
+ public static void click(WebDriver driver, String selector) {
+ AtomicReference<String> lastState = new AtomicReference<>();
+ WebElement element = WebDriverWaitFactory.createWebDriverWait(driver)
+ .withMessage(() -> "UI target not ready: " + selector + ";
state=" + lastState.get())
+ .until(ignored -> {
+ List<WebElement> targets =
driver.findElements(By.cssSelector(selector)).stream()
+ .filter(WebElement::isDisplayed)
+ .collect(Collectors.toList());
+ if (targets.size() != 1) {
+ lastState.set("visible matches=" + targets.size());
+ return null;
+ }
+ WebElement target = targets.get(0);
+ List<WebElement> modals =
+
target.findElements(By.xpath("ancestor-or-self::*[@data-modal-ready][1]"));
+ String modalReady =
+ modals.isEmpty() ? "outside modal" :
modals.get(0).getDomAttribute("data-modal-ready");
+ lastState.set("enabled=" + target.isEnabled() + ",
modalReady=" + modalReady);
+ return target.isEnabled() && (modals.isEmpty() ||
"true".equals(modalReady)) ? target : null;
+ });
+ log.info("Clicking UI target: selector={}, url={}, location={},
size={}, state={}",
+ selector, driver.getCurrentUrl(), element.getLocation(),
element.getSize(), lastState.get());
+ element.click();
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/WebDriverWaitFactory.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/WebDriverWaitFactory.java
index 2d9f47eb23..299637b5c8 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/WebDriverWaitFactory.java
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/WebDriverWaitFactory.java
@@ -29,7 +29,7 @@ public class WebDriverWaitFactory {
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(10);
/**
- * Create a WebDriverWait instance with default timeout 60s and interval
100ms.
+ * Create a WebDriverWait instance with default timeout 10s and interval
500ms.
*/
public static WebDriverWait createWebDriverWait(WebDriver driver) {
return createWebDriverWait(driver, DEFAULT_TIMEOUT);
diff --git a/dolphinscheduler-ui/src/components/modal/index.tsx
b/dolphinscheduler-ui/src/components/modal/index.tsx
index db39caba20..73be5fc695 100644
--- a/dolphinscheduler-ui/src/components/modal/index.tsx
+++ b/dolphinscheduler-ui/src/components/modal/index.tsx
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import { defineComponent, PropType, renderSlot, Ref } from 'vue'
+import { defineComponent, PropType, renderSlot, Ref, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { NModal, NCard, NButton, NSpace } from 'naive-ui'
import ButtonLink from '@/components/button-link'
@@ -77,6 +77,18 @@ const Modal = defineComponent({
emits: ['cancel', 'confirm', 'jumpLink', 'maskClick'],
setup(props, ctx) {
const { t } = useI18n()
+ // NModal does not animate an initially visible modal on its first mount.
+ const entered = ref(props.show)
+ watch(
+ () => props.show,
+ () => {
+ entered.value = false
+ },
+ { flush: 'sync' }
+ )
+ const onAfterEnter = () => {
+ entered.value = props.show
+ }
const onCancel = () => {
ctx.emit('cancel')
@@ -90,7 +102,7 @@ const Modal = defineComponent({
ctx.emit('maskClick')
}
- return { t, onCancel, onConfirm, onMaskClick }
+ return { t, entered, onAfterEnter, onCancel, onConfirm, onMaskClick }
},
render() {
const {
@@ -110,10 +122,12 @@ const Modal = defineComponent({
mask-closable={false}
auto-focus={this.autoFocus}
onMaskClick={onMaskClick}
+ onAfterEnter={this.onAfterEnter}
>
<NCard
title={this.title}
class={styles['modal-card']}
+ data-modal-ready={this.entered}
contentStyle={{ overflowY: 'auto' }}
>
{{
diff --git a/dolphinscheduler-ui/src/components/monaco-editor/index.tsx
b/dolphinscheduler-ui/src/components/monaco-editor/index.tsx
index b565d9ce8b..eb821f5b9a 100644
--- a/dolphinscheduler-ui/src/components/monaco-editor/index.tsx
+++ b/dolphinscheduler-ui/src/components/monaco-editor/index.tsx
@@ -90,6 +90,9 @@ export default defineComponent({
const themeStore = useThemeStore()
const monacoEditorThemeRef = ref(themeStore.darkTheme ? 'vs-dark' : 'vs')
const editorRef = ref()
+ // Expose model progress without depending on Monaco's virtualized line
DOM.
+ const contentLength = ref(0)
+ const modelVersion = ref(0)
const getValue = () => editor?.getValue()
const formItem = useFormItem({})
@@ -106,7 +109,12 @@ export default defineComponent({
alwaysConsumeMouseWheel: false
}
})
+ const model = editor.getModel()!
+ contentLength.value = model.getValue().length
+ modelVersion.value = model.getVersionId()
editor.onDidChangeModelContent(() => {
+ contentLength.value = model.getValue().length
+ modelVersion.value = model.getVersionId()
const { onUpdateValue, 'onUpdate:value': _onUpdateValue } = props
const value = editor?.getValue() || ''
@@ -161,12 +169,14 @@ export default defineComponent({
ctx.expose({ getValue })
- return { editorRef }
+ return { editorRef, contentLength, modelVersion }
},
render() {
return (
<div
ref='editorRef'
+ data-editor-content-length={this.contentLength}
+ data-editor-model-version={this.modelVersion}
style={{
height: this.height,
minHeight: '150px',
diff --git
a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx
b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx
index eea89ad0d4..b5254055b6 100644
---
a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx
+++
b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx
@@ -319,6 +319,7 @@ export default defineComponent({
const { t } = this
return (
<Modal
+ class='dialog-start-workflow'
show={this.show}
title={t('project.workflow.set_parameters_before_starting')}
onCancel={this.hideModal}
diff --git
a/dolphinscheduler-ui/src/views/resource/components/resource/folder/index.tsx
b/dolphinscheduler-ui/src/views/resource/components/resource/folder/index.tsx
index 6367971cd5..c095dae9d0 100644
---
a/dolphinscheduler-ui/src/views/resource/components/resource/folder/index.tsx
+++
b/dolphinscheduler-ui/src/views/resource/components/resource/folder/index.tsx
@@ -65,6 +65,7 @@ export default defineComponent({
const { t } = useI18n()
return (
<Modal
+ class='dialog-create-directory'
show={this.$props.show}
title={t('resource.file.create_folder')}
onCancel={this.hideModal}