This is an automated email from the ASF dual-hosted git repository.

caishunfeng 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 46eff34e69 [Fix-16224] Add Shell using file E2E case (#16220)
46eff34e69 is described below

commit 46eff34e69d1b3e2a73d568d06e78365a0c3b07b
Author: Wenjun Ruan <[email protected]>
AuthorDate: Thu Jun 27 16:04:03 2024 +0800

    [Fix-16224] Add Shell using file E2E case (#16220)
    
    * Add Shell E2E case
    
    * Add shell using resource file e2e case
    
    * Upgrade checkout from v2 to v4
    
    * Change interval to 500ms
    
    * Upgrade actions/upload-artifact
    
    ---------
    
    Co-authored-by: xiangzihao <[email protected]>
---
 .github/workflows/api-test.yml                     |  12 +-
 .github/workflows/backend.yml                      |  12 +-
 .github/workflows/docs.yml                         |  10 +-
 .github/workflows/e2e-k8s.yml                      |   4 +-
 .github/workflows/e2e.yml                          |  20 +-
 .github/workflows/frontend.yml                     |   6 +-
 .github/workflows/issue-robot.yml                  |   2 +-
 .github/workflows/owasp-dependency-check.yaml      |   4 +-
 .github/workflows/publish-docker.yaml              |   2 +-
 .github/workflows/publish-helm-chart.yaml          |   2 +-
 .github/workflows/unit-test.yml                    |   4 +-
 .../api/controller/ResourcesController.java        |  10 +
 .../e2e/cases/ClickhouseDataSourceE2ETest.java     |   3 +-
 .../e2e/cases/FileManageE2ETest.java               |   5 +-
 .../e2e/cases/HiveDataSourceE2ETest.java           |   3 +-
 .../e2e/cases/MysqlDataSourceE2ETest.java          |   3 +-
 .../e2e/cases/PostgresDataSourceE2ETest.java       |   3 +-
 .../e2e/cases/SqlServerDataSourceE2ETest.java      |   3 +-
 .../dolphinscheduler/e2e/cases/UserE2ETest.java    |   3 +-
 .../e2e/cases/WorkerGroupE2ETest.java              |   3 +-
 .../e2e/cases/WorkflowE2ETest.java                 |   3 +-
 .../e2e/cases/WorkflowHttpTaskE2ETest.java         |   3 +-
 .../e2e/cases/WorkflowJavaTaskE2ETest.java         |   3 +-
 .../e2e/cases/WorkflowSwitchE2ETest.java           |   4 +-
 .../e2e/cases/tasks/ShellTaskE2ETest.java          | 260 +++++++++++++++++++++
 .../e2e/cases/workflow/BaseWorkflowE2ETest.java    | 219 +++++++++++++++++
 .../e2e/models/tenant/BootstrapTenant.java         |  38 +--
 .../e2e/models/tenant/DefaultTenant.java           |  39 +---
 .../e2e/models/tenant/ITenant.java                 |  35 +--
 .../e2e/models/users/AdminUser.java                |  62 +++++
 .../dolphinscheduler/e2e/models/users/IUser.java   |  46 ++--
 .../dolphinscheduler/e2e/pages/LoginPage.java      |  13 +-
 .../e2e/pages/common/CodeEditor.java               |   5 +-
 .../e2e/pages/common/HttpInput.java                |   3 +-
 .../e2e/pages/common/NavBarPage.java               |  13 +-
 .../e2e/pages/datasource/DataSourcePage.java       |   5 +-
 .../e2e/pages/project/ProjectDetailPage.java       |  10 +
 .../e2e/pages/project/ProjectPage.java             |  30 ++-
 .../pages/project/workflow/TaskInstanceTab.java    |  25 +-
 .../e2e/pages/project/workflow/WorkflowForm.java   |   3 +-
 .../project/workflow/WorkflowInstanceTab.java      |   8 +
 .../pages/project/workflow/WorkflowRunDialog.java  |   3 +-
 .../project/workflow/task/SubWorkflowTaskForm.java |   5 +-
 .../project/workflow/task/SwitchTaskForm.java      |   5 +-
 .../pages/project/workflow/task/TaskNodeForm.java  |  49 ++--
 .../e2e/pages/resource/FileManagePage.java         |  95 +++++---
 .../e2e/pages/resource/ResourcePage.java           |  10 +-
 .../e2e/pages/security/EnvironmentPage.java        |   5 +-
 .../e2e/pages/security/SecurityPage.java           |  34 +--
 .../e2e/pages/security/TenantPage.java             |  64 +++--
 .../e2e/pages/security/TokenPage.java              |  11 +-
 .../e2e/pages/security/UserPage.java               |  20 +-
 .../e2e/core/DolphinSchedulerExtension.java        |   3 +-
 .../dolphinscheduler/e2e/core/WebDriverHolder.java |  37 +--
 .../e2e/core/WebDriverWaitFactory.java             |  45 ++++
 .../dolphinscheduler/spi/enums/ResourceType.java   |   2 +-
 .../task/components/node/fields/use-resources.ts   |   1 +
 .../views/projects/task/instance/batch-task.tsx    |   1 +
 58 files changed, 996 insertions(+), 335 deletions(-)

diff --git a/.github/workflows/api-test.yml b/.github/workflows/api-test.yml
index fd39bd0977..b7a1aa4303 100644
--- a/.github/workflows/api-test.yml
+++ b/.github/workflows/api-test.yml
@@ -35,7 +35,7 @@ jobs:
     outputs:
       not-ignore: ${{ steps.filter.outputs.not-ignore }}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - uses: dorny/paths-filter@b2feaf19c27470162a626bd6fa8438ae5b263721
         id: filter
         with:
@@ -49,7 +49,7 @@ jobs:
     runs-on: ubuntu-latest
     timeout-minutes: 20
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           submodules: true
       - name: Sanity Check
@@ -73,7 +73,7 @@ jobs:
         run: |
           docker save apache/dolphinscheduler-standalone-server:ci -o 
/tmp/standalone-image.tar \
           && du -sh /tmp/standalone-image.tar
-      - uses: actions/upload-artifact@v2
+      - uses: actions/upload-artifact@v4
         name: Upload Docker Images
         with:
           name: standalone-image
@@ -104,7 +104,7 @@ jobs:
     env:
       RECORDING_PATH: /tmp/recording-${{ matrix.case.name }}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           submodules: true
       - name: Cache local Maven repository
@@ -113,7 +113,7 @@ jobs:
           path: ~/.m2/repository
           key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}-api-test
           restore-keys: ${{ runner.os }}-maven-
-      - uses: actions/download-artifact@v2
+      - uses: actions/download-artifact@v4
         name: Download Docker Images
         with:
           name: standalone-image
@@ -127,7 +127,7 @@ jobs:
             -DfailIfNoTests=false \
             -Dspotless.skip=false \
             -Dtest=${{ matrix.case.class }} test
-      - uses: actions/upload-artifact@v2
+      - uses: actions/upload-artifact@v4
         if: always()
         name: Upload Recording
         with:
diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml
index 37fb1bcd7e..8e83cd9792 100644
--- a/.github/workflows/backend.yml
+++ b/.github/workflows/backend.yml
@@ -44,7 +44,7 @@ jobs:
       not-ignore: ${{ steps.filter.outputs.not-ignore }}
       db-schema: ${{ steps.filter.outputs.db-schema }}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - uses: dorny/paths-filter@b2feaf19c27470162a626bd6fa8438ae5b263721
         id: filter
         with:
@@ -63,7 +63,7 @@ jobs:
         java: [ '8', '11' ]
     timeout-minutes: 30
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           submodules: true
       - name: Set up JDK ${{ matrix.java }}
@@ -91,7 +91,7 @@ jobs:
                  -Dmaven.wagon.httpconnectionManager.ttlSeconds=120
       - name: Check dependency license
         run: tools/dependencies/check-LICENSE.sh
-      - uses: actions/upload-artifact@v2
+      - uses: actions/upload-artifact@v4
         if: ${{ matrix.java == '8' }}
         name: Upload Binary Package
         with:
@@ -115,10 +115,10 @@ jobs:
           - name: cluster-test-postgresql-with-postgresql-registry
             script: 
.github/workflows/cluster-test/postgresql_with_postgresql_registry/start-job.sh
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           submodules: true
-      - uses: actions/download-artifact@v2
+      - uses: actions/download-artifact@v4
         name: Download Binary Package
         with:
           # Only run cluster test on jdk8
@@ -165,7 +165,7 @@ jobs:
           mkdir -p dolphinscheduler/dev dolphinscheduler/${{ matrix.version }}
           curl -sSf https://atlasgo.sh | sh
       - name: Download Tarball
-        uses: actions/download-artifact@v2
+        uses: actions/download-artifact@v4
         with:
           name: binary-package-8
           path: dolphinscheduler/dev
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 591bb0a65b..81eb7f2073 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -30,7 +30,7 @@ jobs:
     timeout-minutes: 10
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: Style Check
         run: ./mvnw spotless:check
   img-check:
@@ -40,7 +40,7 @@ jobs:
       run:
         working-directory: docs
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: Set up Python 3.9
         uses: actions/setup-python@v2
         with:
@@ -54,7 +54,7 @@ jobs:
     runs-on: ubuntu-latest
     timeout-minutes: 30
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - run: sudo npm install -g [email protected]
       - run: sudo apt install plocate -y
       # NOTE: Change command from `find . -name "*.md"` to `find . -not -path 
"*/node_modules/*" -not -path "*/.tox/*" -name "*.md"`
@@ -70,7 +70,7 @@ jobs:
     outputs:
       helm-doc: ${{ steps.filter.outputs.helm-doc }}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - uses: dorny/paths-filter@b2feaf19c27470162a626bd6fa8438ae5b263721
         id: filter
         with:
@@ -84,7 +84,7 @@ jobs:
     runs-on: ubuntu-latest
     timeout-minutes: 20
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           submodules: true
       - name: Generating helm-doc
diff --git a/.github/workflows/e2e-k8s.yml b/.github/workflows/e2e-k8s.yml
index fb402b3c79..351aea8802 100644
--- a/.github/workflows/e2e-k8s.yml
+++ b/.github/workflows/e2e-k8s.yml
@@ -35,7 +35,7 @@ jobs:
     outputs:
       not-ignore: ${{ steps.filter.outputs.not-ignore }}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - uses: dorny/paths-filter@b2feaf19c27470162a626bd6fa8438ae5b263721
         id: filter
         with:
@@ -49,7 +49,7 @@ jobs:
     runs-on: ubuntu-latest
     timeout-minutes: 20
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           submodules: true
       - name: Build Image
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 5601dbbb3f..d9a3cd795e 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -35,7 +35,7 @@ jobs:
     outputs:
       not-ignore: ${{ steps.filter.outputs.not-ignore }}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - uses: dorny/paths-filter@b2feaf19c27470162a626bd6fa8438ae5b263721
         id: filter
         with:
@@ -49,7 +49,7 @@ jobs:
     runs-on: ubuntu-latest
     timeout-minutes: 20
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           submodules: true
       - name: Sanity Check
@@ -74,7 +74,7 @@ jobs:
         run: |
           docker save apache/dolphinscheduler-standalone-server:ci -o 
/tmp/standalone-image.tar \
           && du -sh /tmp/standalone-image.tar
-      - uses: actions/upload-artifact@v2
+      - uses: actions/upload-artifact@v4
         name: Upload Docker Images
         with:
           name: standalone-image
@@ -120,10 +120,12 @@ jobs:
             class: 
org.apache.dolphinscheduler.e2e.cases.ClickhouseDataSourceE2ETest
           - name: PostgresDataSource
             class: 
org.apache.dolphinscheduler.e2e.cases.PostgresDataSourceE2ETest
+          - name: ShellTaskE2ETest
+            class: org.apache.dolphinscheduler.e2e.cases.tasks.ShellTaskE2ETest
     env:
       RECORDING_PATH: /tmp/recording-${{ matrix.case.name }}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           submodules: true
       - name: Cache local Maven repository
@@ -132,7 +134,7 @@ jobs:
           path: ~/.m2/repository
           key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}-e2e
           restore-keys: ${{ runner.os }}-maven-
-      - uses: actions/download-artifact@v2
+      - uses: actions/download-artifact@v4
         name: Download Docker Images
         with:
           name: standalone-image
@@ -145,7 +147,7 @@ jobs:
           ./mvnw -B -f dolphinscheduler-e2e/pom.xml -am \
             -DfailIfNoTests=false \
             -Dtest=${{ matrix.case.class }} test
-      - uses: actions/upload-artifact@v2
+      - uses: actions/upload-artifact@v4
         if: always()
         name: Upload Recording
         with:
@@ -167,7 +169,7 @@ jobs:
     env:
       RECORDING_PATH: /tmp/recording-${{ matrix.case.name }}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           submodules: true
       - name: Cache local Maven repository
@@ -176,7 +178,7 @@ jobs:
           path: ~/.m2/repository
           key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}-e2e
           restore-keys: ${{ runner.os }}-maven-
-      - uses: actions/download-artifact@v2
+      - uses: actions/download-artifact@v4
         name: Download Docker Images
         with:
           name: standalone-image
@@ -189,7 +191,7 @@ jobs:
           ./mvnw -B -f dolphinscheduler-e2e/pom.xml -am \
             -DfailIfNoTests=false \
             -Dtest=${{ matrix.case.class }} test
-      - uses: actions/upload-artifact@v2
+      - uses: actions/upload-artifact@v4
         if: always()
         name: Upload Recording
         with:
diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml
index 502d669b66..87d178185a 100644
--- a/.github/workflows/frontend.yml
+++ b/.github/workflows/frontend.yml
@@ -41,7 +41,7 @@ jobs:
     outputs:
       not-ignore: ${{ steps.filter.outputs.not-ignore }}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - uses: dorny/paths-filter@b2feaf19c27470162a626bd6fa8438ae5b263721
         id: filter
         with:
@@ -58,7 +58,7 @@ jobs:
       matrix:
         os: [ ubuntu-latest, macos-latest ]
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           submodules: true
       - if: matrix.os == 'ubuntu-latest'
@@ -82,7 +82,7 @@ jobs:
     needs: [ build, paths-filter ]
     if: always()
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: Status
         run: |
           if [[ ${{ needs.paths-filter.outputs.not-ignore }} == 'false' && ${{ 
github.event_name }} == 'pull_request' ]]; then
diff --git a/.github/workflows/issue-robot.yml 
b/.github/workflows/issue-robot.yml
index ab00b34681..06a363d11f 100644
--- a/.github/workflows/issue-robot.yml
+++ b/.github/workflows/issue-robot.yml
@@ -26,7 +26,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: "Checkout ${{ github.ref }}"
-        uses: actions/checkout@v2
+        uses: actions/checkout@v4
         with:
           persist-credentials: false
           submodules: true
diff --git a/.github/workflows/owasp-dependency-check.yaml 
b/.github/workflows/owasp-dependency-check.yaml
index 54e51aafed..b99486f298 100644
--- a/.github/workflows/owasp-dependency-check.yaml
+++ b/.github/workflows/owasp-dependency-check.yaml
@@ -31,7 +31,7 @@ jobs:
   build:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           submodules: true
       - name: Set up JDK 8
@@ -42,7 +42,7 @@ jobs:
       - name: Run OWASP Dependency Check
         run: ./mvnw -B clean install verify dependency-check:check 
-DskipDepCheck=false -Dmaven.test.skip=true -Dspotless.skip=true
       - name: Upload report
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         if: ${{ cancelled() || failure() }}
         continue-on-error: true
         with:
diff --git a/.github/workflows/publish-docker.yaml 
b/.github/workflows/publish-docker.yaml
index 06aa8c6950..859e22f25c 100644
--- a/.github/workflows/publish-docker.yaml
+++ b/.github/workflows/publish-docker.yaml
@@ -33,7 +33,7 @@ jobs:
       packages: write
     timeout-minutes: 30
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: Cache local Maven repository
         uses: actions/cache@v3
         with:
diff --git a/.github/workflows/publish-helm-chart.yaml 
b/.github/workflows/publish-helm-chart.yaml
index e383373692..247a55f4cc 100644
--- a/.github/workflows/publish-helm-chart.yaml
+++ b/.github/workflows/publish-helm-chart.yaml
@@ -33,7 +33,7 @@ jobs:
       packages: write
     timeout-minutes: 30
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: Set environment variables
         run: |
           if [[ ${{ github.event_name }} == "release" ]]; then
diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
index a7e78a11f7..d0c88b9ca6 100644
--- a/.github/workflows/unit-test.yml
+++ b/.github/workflows/unit-test.yml
@@ -40,7 +40,7 @@ jobs:
     outputs:
       not-ignore: ${{ steps.filter.outputs.not-ignore }}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - uses: dorny/paths-filter@b2feaf19c27470162a626bd6fa8438ae5b263721
         id: filter
         with:
@@ -57,7 +57,7 @@ jobs:
         java: ['8', '11']
     timeout-minutes: 45
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           submodules: true
       - name: Sanity Check
diff --git 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ResourcesController.java
 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ResourcesController.java
index 0c9a34ee53..03eb42c5d6 100644
--- 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ResourcesController.java
+++ 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ResourcesController.java
@@ -92,6 +92,16 @@ public class ResourcesController extends BaseController {
     @Autowired
     private ResourcesService resourceService;
 
+    @Operation(summary = "queryResourceList", description = 
"QUERY_RESOURCE_LIST_NOTES")
+    @Parameter(name = "type", description = "RESOURCE_TYPE", required = true, 
schema = @Schema(implementation = ResourceType.class))
+    @GetMapping(value = "/list")
+    @ResponseStatus(HttpStatus.OK)
+    @ApiException(QUERY_RESOURCES_LIST_ERROR)
+    public Result<List<ResourceComponent>> queryResourceList(@Parameter(hidden 
= true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser,
+                                                             
@RequestParam(value = "type") ResourceType type) {
+        return Result.success(resourceService.queryResourceFiles(loginUser, 
type));
+    }
+
     @Operation(summary = "createDirectory", description = 
"CREATE_RESOURCE_NOTES")
     @Parameters({
             @Parameter(name = "type", description = "RESOURCE_TYPE", required 
= true, schema = @Schema(implementation = ResourceType.class)),
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/ClickhouseDataSourceE2ETest.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/ClickhouseDataSourceE2ETest.java
index e150e73e98..412b5f6a8a 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/ClickhouseDataSourceE2ETest.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/ClickhouseDataSourceE2ETest.java
@@ -23,6 +23,7 @@ package org.apache.dolphinscheduler.e2e.cases;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.LoginPage;
 import org.apache.dolphinscheduler.e2e.pages.datasource.DataSourcePage;
 
@@ -82,7 +83,7 @@ public class ClickhouseDataSourceE2ETest {
 
         page.createDataSource(dataSourceType, dataSourceName, 
dataSourceDescription, ip, port, userName, pgPassword, database, jdbcParams);
 
-        new WebDriverWait(page.driver(), 
Duration.ofSeconds(20)).until(ExpectedConditions.invisibilityOfElementLocated(
+        
WebDriverWaitFactory.createWebDriverWait(page.driver()).until(ExpectedConditions.invisibilityOfElementLocated(
                 new By.ByClassName("dialog-create-data-source")));
 
         Awaitility.await().untilAsserted(() -> 
assertThat(page.dataSourceItemsList())
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/FileManageE2ETest.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/FileManageE2ETest.java
index 1bfa997f85..ad041b9ac4 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/FileManageE2ETest.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/FileManageE2ETest.java
@@ -24,6 +24,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 import org.apache.dolphinscheduler.e2e.core.Constants;
 import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.LoginPage;
 import org.apache.dolphinscheduler.e2e.pages.resource.FileManagePage;
 import org.apache.dolphinscheduler.e2e.pages.resource.ResourcePage;
@@ -98,7 +99,7 @@ public class FileManageE2ETest {
         UserPage userPage = tenantPage.goToNav(SecurityPage.class)
             .goToTab(UserPage.class);
 
-        new WebDriverWait(userPage.driver(), 
Duration.ofSeconds(20)).until(ExpectedConditions.visibilityOfElementLocated(
+        
WebDriverWaitFactory.createWebDriverWait(userPage.driver()).until(ExpectedConditions.visibilityOfElementLocated(
                 new By.ByClassName("name")));
 
         userPage.update(user, user, email, phone, tenant)
@@ -285,7 +286,7 @@ public class FileManageE2ETest {
 
         page.uploadFile(testUnder1GBFilePath.toFile().getAbsolutePath());
 
-        new WebDriverWait(browser, 
Duration.ofSeconds(20)).until(ExpectedConditions.invisibilityOfElementLocated(By.id("fileUpdateDialog")));
+        
WebDriverWaitFactory.createWebDriverWait(browser).until(ExpectedConditions.invisibilityOfElementLocated(By.id("fileUpdateDialog")));
 
         Awaitility.await().untilAsserted(() -> {
             assertThat(page.fileList())
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/HiveDataSourceE2ETest.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/HiveDataSourceE2ETest.java
index 9b6e661f52..3726ec1acd 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/HiveDataSourceE2ETest.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/HiveDataSourceE2ETest.java
@@ -23,6 +23,7 @@ package org.apache.dolphinscheduler.e2e.cases;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.LoginPage;
 import org.apache.dolphinscheduler.e2e.pages.datasource.DataSourcePage;
 
@@ -82,7 +83,7 @@ public class HiveDataSourceE2ETest {
 
         page.createDataSource(dataSourceType, dataSourceName, 
dataSourceDescription, ip, port, userName, hivePassword, database, jdbcParams);
 
-        new WebDriverWait(page.driver(), 
Duration.ofSeconds(20)).until(ExpectedConditions.invisibilityOfElementLocated(
+        
WebDriverWaitFactory.createWebDriverWait(page.driver()).until(ExpectedConditions.invisibilityOfElementLocated(
                 new By.ByClassName("dialog-create-data-source")));
 
         Awaitility.await().untilAsserted(() -> 
assertThat(page.dataSourceItemsList())
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/MysqlDataSourceE2ETest.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/MysqlDataSourceE2ETest.java
index 5078f55e35..5c0746a5b5 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/MysqlDataSourceE2ETest.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/MysqlDataSourceE2ETest.java
@@ -23,6 +23,7 @@ package org.apache.dolphinscheduler.e2e.cases;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.LoginPage;
 import org.apache.dolphinscheduler.e2e.pages.datasource.DataSourcePage;
 
@@ -82,7 +83,7 @@ public class MysqlDataSourceE2ETest {
 
         page.createDataSource(dataSourceType, dataSourceName, 
dataSourceDescription, ip, port, userName, mysqlPassword, database, jdbcParams);
 
-        new WebDriverWait(page.driver(), 
Duration.ofSeconds(20)).until(ExpectedConditions.invisibilityOfElementLocated(
+        
WebDriverWaitFactory.createWebDriverWait(page.driver()).until(ExpectedConditions.invisibilityOfElementLocated(
                 new By.ByClassName("dialog-create-data-source")));
 
         Awaitility.await().untilAsserted(() -> 
assertThat(page.dataSourceItemsList())
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/PostgresDataSourceE2ETest.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/PostgresDataSourceE2ETest.java
index 647c667741..cbed2b3b8b 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/PostgresDataSourceE2ETest.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/PostgresDataSourceE2ETest.java
@@ -23,6 +23,7 @@ package org.apache.dolphinscheduler.e2e.cases;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.LoginPage;
 import org.apache.dolphinscheduler.e2e.pages.datasource.DataSourcePage;
 
@@ -82,7 +83,7 @@ public class PostgresDataSourceE2ETest {
 
         page.createDataSource(dataSourceType, dataSourceName, 
dataSourceDescription, ip, port, userName, pgPassword, database, jdbcParams);
 
-        new WebDriverWait(page.driver(), 
Duration.ofSeconds(20)).until(ExpectedConditions.invisibilityOfElementLocated(
+        
WebDriverWaitFactory.createWebDriverWait(page.driver()).until(ExpectedConditions.invisibilityOfElementLocated(
                 new By.ByClassName("dialog-create-data-source")));
 
         Awaitility.await().untilAsserted(() -> 
assertThat(page.dataSourceItemsList())
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/SqlServerDataSourceE2ETest.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/SqlServerDataSourceE2ETest.java
index 7777b9a441..7f6ee662dd 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/SqlServerDataSourceE2ETest.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/SqlServerDataSourceE2ETest.java
@@ -23,6 +23,7 @@ package org.apache.dolphinscheduler.e2e.cases;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.LoginPage;
 import org.apache.dolphinscheduler.e2e.pages.datasource.DataSourcePage;
 
@@ -82,7 +83,7 @@ public class SqlServerDataSourceE2ETest {
 
         page.createDataSource(dataSourceType, dataSourceName, 
dataSourceDescription, ip, port, userName, pgPassword, database, jdbcParams);
 
-        new WebDriverWait(page.driver(), 
Duration.ofSeconds(20)).until(ExpectedConditions.invisibilityOfElementLocated(
+        
WebDriverWaitFactory.createWebDriverWait(page.driver()).until(ExpectedConditions.invisibilityOfElementLocated(
                 new By.ByClassName("dialog-create-data-source")));
 
         Awaitility.await().untilAsserted(() -> 
assertThat(page.dataSourceItemsList())
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/UserE2ETest.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/UserE2ETest.java
index 3ed263ec0e..150e84b618 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/UserE2ETest.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/UserE2ETest.java
@@ -23,6 +23,7 @@ package org.apache.dolphinscheduler.e2e.cases;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.LoginPage;
 import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
 import org.apache.dolphinscheduler.e2e.pages.security.SecurityPage;
@@ -119,7 +120,7 @@ class UserE2ETest {
     void testEditUser() {
         UserPage page = new UserPage(browser);
 
-        new WebDriverWait(browser, 
Duration.ofSeconds(20)).until(ExpectedConditions.visibilityOfElementLocated(
+        
WebDriverWaitFactory.createWebDriverWait(browser).until(ExpectedConditions.visibilityOfElementLocated(
                 new By.ByClassName("name")));
 
         browser.navigate().refresh();
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkerGroupE2ETest.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkerGroupE2ETest.java
index b7f2a9474c..be8caf0913 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkerGroupE2ETest.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkerGroupE2ETest.java
@@ -23,6 +23,7 @@ package org.apache.dolphinscheduler.e2e.cases;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.LoginPage;
 import org.apache.dolphinscheduler.e2e.pages.security.SecurityPage;
 import org.apache.dolphinscheduler.e2e.pages.security.WorkerGroupPage;
@@ -59,7 +60,7 @@ class WorkerGroupE2ETest {
     void testCreateWorkerGroup() {
         final WorkerGroupPage page = new WorkerGroupPage(browser);
 
-        new WebDriverWait(page.driver(), Duration.ofSeconds(20))
+        WebDriverWaitFactory.createWebDriverWait(page.driver())
             
.until(ExpectedConditions.urlContains("/security/worker-group-manage"));
 
         page.create(workerGroupName);
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java
index 0b97ab02af..bbfcacbceb 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java
@@ -20,6 +20,7 @@
 package org.apache.dolphinscheduler.e2e.cases;
 
 import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.LoginPage;
 import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
 import org.apache.dolphinscheduler.e2e.pages.project.ProjectDetailPage;
@@ -76,7 +77,7 @@ class WorkflowE2ETest {
                 .goToNav(SecurityPage.class)
                 .goToTab(UserPage.class);
 
-        new WebDriverWait(userPage.driver(), 
Duration.ofSeconds(20)).until(ExpectedConditions.visibilityOfElementLocated(
+        
WebDriverWaitFactory.createWebDriverWait(userPage.driver()).until(ExpectedConditions.visibilityOfElementLocated(
                 new By.ByClassName("name")));
 
         userPage.update(user, user, email, phone, tenant)
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java
index 0993e61b81..3f78fd25f7 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java
@@ -20,6 +20,7 @@
 package org.apache.dolphinscheduler.e2e.cases;
 
 import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.LoginPage;
 import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
 import org.apache.dolphinscheduler.e2e.pages.project.ProjectDetailPage;
@@ -76,7 +77,7 @@ public class WorkflowHttpTaskE2ETest {
                 .goToNav(SecurityPage.class)
                 .goToTab(UserPage.class);
 
-        new WebDriverWait(userPage.driver(), 
Duration.ofSeconds(20)).until(ExpectedConditions.visibilityOfElementLocated(
+        
WebDriverWaitFactory.createWebDriverWait(userPage.driver()).until(ExpectedConditions.visibilityOfElementLocated(
                 new By.ByClassName("name")));
 
         userPage.update(user, user, email, phone, tenant)
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java
index 77c4e554bc..700ec03f43 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java
@@ -20,6 +20,7 @@
 package org.apache.dolphinscheduler.e2e.cases;
 
 import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.LoginPage;
 import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
 import org.apache.dolphinscheduler.e2e.pages.project.ProjectDetailPage;
@@ -92,7 +93,7 @@ public class WorkflowJavaTaskE2ETest {
                 .goToNav(SecurityPage.class)
                 .goToTab(UserPage.class);
 
-        new WebDriverWait(userPage.driver(), Duration.ofSeconds(20))
+        WebDriverWaitFactory.createWebDriverWait(userPage.driver())
                 .until(ExpectedConditions.visibilityOfElementLocated(new 
By.ByClassName("name")));
 
         userPage.update(user, user, email, phone, tenant)
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java
index edf4bc59e2..cee9b52b47 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java
@@ -178,8 +178,8 @@ class WorkflowSwitchE2ETest {
 
         Awaitility.await().untilAsserted(() -> {
             assertThat(taskInstances.size()).isEqualTo(3);
-            assertThat(taskInstances.stream().filter(row -> 
row.name().contains(ifBranchName)).count()).isEqualTo(1);
-            assertThat(taskInstances.stream().noneMatch(row -> 
row.name().contains(elseBranchName))).isTrue();
+            assertThat(taskInstances.stream().filter(row -> 
row.taskInstanceName().contains(ifBranchName)).count()).isEqualTo(1);
+            assertThat(taskInstances.stream().noneMatch(row -> 
row.taskInstanceName().contains(elseBranchName))).isTrue();
         });
     }
 }
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
new file mode 100644
index 0000000000..fb44bc843a
--- /dev/null
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java
@@ -0,0 +1,260 @@
+/*
+ * 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.cases.tasks;
+
+import org.apache.dolphinscheduler.e2e.cases.workflow.BaseWorkflowE2ETest;
+import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
+import org.apache.dolphinscheduler.e2e.pages.project.ProjectPage;
+import org.apache.dolphinscheduler.e2e.pages.project.workflow.TaskInstanceTab;
+import 
org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowDefinitionTab;
+import org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowForm;
+import 
org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowInstanceTab;
+import 
org.apache.dolphinscheduler.e2e.pages.project.workflow.task.ShellTaskForm;
+import org.apache.dolphinscheduler.e2e.pages.resource.FileManagePage;
+import org.apache.dolphinscheduler.e2e.pages.resource.ResourcePage;
+import org.junit.FixMethodOrder;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.runners.MethodSorters;
+import org.openqa.selenium.WebElement;
+import org.testcontainers.shaded.org.awaitility.Awaitility;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@DolphinScheduler(composeFiles = "docker/basic/docker-compose.yaml")
+public class ShellTaskE2ETest extends BaseWorkflowE2ETest {
+
+    @Test
+    void testRunShellTasks_SuccessCase() {
+        WorkflowDefinitionTab workflowDefinitionPage =
+                new ProjectPage(browser)
+                        .goToNav(ProjectPage.class)
+                        .goTo(projectName)
+                        .goToTab(WorkflowDefinitionTab.class);
+
+        // todo: use yaml to define the workflow
+        String workflowName = "SuccessCase";
+        String taskName = "ShellSuccess";
+        workflowDefinitionPage
+                .createWorkflow()
+                .<ShellTaskForm>addTask(WorkflowForm.TaskType.SHELL)
+                .script("echo hello world\n")
+                .name(taskName)
+                .submit()
+
+                .submit()
+                .name(workflowName)
+                .submit();
+
+        untilWorkflowDefinitionExist(workflowName);
+
+        workflowDefinitionPage.publish(workflowName);
+
+        runWorkflow(workflowName);
+        untilWorkflowInstanceExist(workflowName);
+        WorkflowInstanceTab.Row workflowInstance = 
untilWorkflowInstanceSuccess(workflowName);
+        assertThat(workflowInstance.executionTime()).isEqualTo(1);
+
+        TaskInstanceTab.Row taskInstance = 
untilTaskInstanceSuccess(workflowName, taskName);
+        assertThat(taskInstance.retryTimes()).isEqualTo(0);
+    }
+
+    @Test
+    void testRunShellTasks_WorkflowParamsCase() {
+        WorkflowDefinitionTab workflowDefinitionPage =
+                new ProjectPage(browser)
+                        .goToNav(ProjectPage.class)
+                        .goTo(projectName)
+                        .goToTab(WorkflowDefinitionTab.class);
+
+        // todo: use yaml to define the workflow
+        String workflowName = "WorkflowParamsCase";
+        String taskName = "ShellSuccess";
+        workflowDefinitionPage
+                .createWorkflow()
+                .<ShellTaskForm>addTask(WorkflowForm.TaskType.SHELL)
+                .script("[ \"${name}\" = \"tom\" ] && echo \"success\" || { 
echo \"failed\"; exit 1; }")
+                .name(taskName)
+                .submit()
+
+                .submit()
+                .name(workflowName)
+                .addGlobalParam("name", "tom")
+                .submit();
+
+        untilWorkflowDefinitionExist(workflowName);
+
+        workflowDefinitionPage.publish(workflowName);
+
+        runWorkflow(workflowName);
+        untilWorkflowInstanceExist(workflowName);
+        WorkflowInstanceTab.Row workflowInstance = 
untilWorkflowInstanceSuccess(workflowName);
+        assertThat(workflowInstance.executionTime()).isEqualTo(1);
+
+        TaskInstanceTab.Row taskInstance = 
untilTaskInstanceSuccess(workflowName, taskName);
+        assertThat(taskInstance.retryTimes()).isEqualTo(0);
+    }
+
+    @Test
+    void testRunShellTasks_LocalParamsCase() {
+        WorkflowDefinitionTab workflowDefinitionPage =
+                new ProjectPage(browser)
+                        .goToNav(ProjectPage.class)
+                        .goTo(projectName)
+                        .goToTab(WorkflowDefinitionTab.class);
+
+        String workflowName = "LocalParamsCase";
+        String taskName = "ShellSuccess";
+        workflowDefinitionPage
+                .createWorkflow()
+                .<ShellTaskForm>addTask(WorkflowForm.TaskType.SHELL)
+                .script("[ \"${name}\" = \"tom\" ] && echo \"success\" || { 
echo \"failed\"; exit 1; }")
+                .name(taskName)
+                .addParam("name", "tom")
+                .submit()
+
+                .submit()
+                .name(workflowName)
+                .submit();
+
+        untilWorkflowDefinitionExist(workflowName);
+
+        workflowDefinitionPage.publish(workflowName);
+
+        runWorkflow(workflowName);
+        untilWorkflowInstanceExist(workflowName);
+        WorkflowInstanceTab.Row workflowInstance = 
untilWorkflowInstanceSuccess(workflowName);
+        assertThat(workflowInstance.executionTime()).isEqualTo(1);
+
+        TaskInstanceTab.Row taskInstance = 
untilTaskInstanceSuccess(workflowName, taskName);
+        assertThat(taskInstance.retryTimes()).isEqualTo(0);
+    }
+
+    @Test
+    void testRunShellTasks_GlobalParamsOverrideLocalParamsCase() {
+        WorkflowDefinitionTab workflowDefinitionPage =
+                new ProjectPage(browser)
+                        .goToNav(ProjectPage.class)
+                        .goTo(projectName)
+                        .goToTab(WorkflowDefinitionTab.class);
+
+        String workflowName = "LocalParamsOverrideWorkflowParamsCase";
+        String taskName = "ShellSuccess";
+        workflowDefinitionPage
+                .createWorkflow()
+                .<ShellTaskForm>addTask(WorkflowForm.TaskType.SHELL)
+                .script("[ \"${name}\" = \"jerry\" ] && echo \"success\" || { 
echo \"failed\"; exit 1; }")
+                .name(taskName)
+                .addParam("name", "tom")
+                .submit()
+
+                .submit()
+                .name(workflowName)
+                .addGlobalParam("name", "jerry")
+                .submit();
+
+        untilWorkflowDefinitionExist(workflowName);
+
+        workflowDefinitionPage.publish(workflowName);
+
+        runWorkflow(workflowName);
+        untilWorkflowInstanceExist(workflowName);
+        WorkflowInstanceTab.Row workflowInstance = 
untilWorkflowInstanceSuccess(workflowName);
+        assertThat(workflowInstance.executionTime()).isEqualTo(1);
+
+        TaskInstanceTab.Row taskInstance = 
untilTaskInstanceSuccess(workflowName, taskName);
+        assertThat(taskInstance.retryTimes()).isEqualTo(0);
+    }
+
+    @Test
+    void testRunShellTasks_UsingResourceFile() {
+        String testFileName = "echo";
+        new ResourcePage(browser)
+                .goToNav(ResourcePage.class)
+                .goToTab(FileManagePage.class)
+                .createFileUntilSuccess(testFileName, "echo 123");
+
+        final WorkflowDefinitionTab workflowDefinitionPage =
+                new ProjectPage(browser)
+                        .goToNav(ProjectPage.class)
+                        .goTo(projectName)
+                        .goToTab(WorkflowDefinitionTab.class);
+
+        String workflowName = "UsingResourceFile";
+        String taskName = "ShellSuccess";
+        workflowDefinitionPage
+                .createWorkflow()
+                .<ShellTaskForm>addTask(WorkflowForm.TaskType.SHELL)
+                .script("cat " + testFileName + ".sh")
+                .name(taskName)
+                .selectResource(testFileName)
+                .submit()
+
+                .submit()
+                .name(workflowName)
+                .submit();
+
+        untilWorkflowDefinitionExist(workflowName);
+
+        workflowDefinitionPage.publish(workflowName);
+
+        runWorkflow(workflowName);
+        untilWorkflowInstanceExist(workflowName);
+        WorkflowInstanceTab.Row workflowInstance = 
untilWorkflowInstanceSuccess(workflowName);
+        assertThat(workflowInstance.executionTime()).isEqualTo(1);
+
+        TaskInstanceTab.Row taskInstance = 
untilTaskInstanceSuccess(workflowName, taskName);
+        assertThat(taskInstance.retryTimes()).isEqualTo(0);
+    }
+
+    @Test
+    void testRunShellTasks_FailedCase() {
+        WorkflowDefinitionTab workflowDefinitionPage =
+                new ProjectPage(browser)
+                        .goToNav(ProjectPage.class)
+                        .goTo(projectName)
+                        .goToTab(WorkflowDefinitionTab.class);
+
+        String workflowName = "FailedCase";
+        String taskName = "ShellFailed";
+        workflowDefinitionPage
+                .createWorkflow()
+                .<ShellTaskForm>addTask(WorkflowForm.TaskType.SHELL)
+                .script("echo 'I am failed'\n exit1\n")
+                .name(taskName)
+                .submit()
+
+                .submit()
+                .name(workflowName)
+                .submit();
+
+        untilWorkflowDefinitionExist(workflowName);
+
+        workflowDefinitionPage.publish(workflowName);
+
+        runWorkflow(workflowName);
+        untilWorkflowInstanceExist(workflowName);
+        WorkflowInstanceTab.Row workflowInstance = 
untilWorkflowInstanceFailed(workflowName);
+        assertThat(workflowInstance.executionTime()).isEqualTo(1);
+
+        TaskInstanceTab.Row taskInstance = 
untilTaskInstanceFailed(workflowName, taskName);
+        assertThat(taskInstance.retryTimes()).isEqualTo(0);
+    }
+
+}
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
new file mode 100644
index 0000000000..aab2c7c06f
--- /dev/null
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/workflow/BaseWorkflowE2ETest.java
@@ -0,0 +1,219 @@
+/*
+ * 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.cases.workflow;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dolphinscheduler.e2e.core.WebDriverHolder;
+import org.apache.dolphinscheduler.e2e.models.tenant.DefaultTenant;
+import org.apache.dolphinscheduler.e2e.models.users.AdminUser;
+import org.apache.dolphinscheduler.e2e.models.users.IUser;
+import org.apache.dolphinscheduler.e2e.pages.LoginPage;
+import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
+import org.apache.dolphinscheduler.e2e.pages.project.ProjectDetailPage;
+import org.apache.dolphinscheduler.e2e.pages.project.ProjectPage;
+import org.apache.dolphinscheduler.e2e.pages.project.workflow.TaskInstanceTab;
+import 
org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowDefinitionTab;
+import 
org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowInstanceTab;
+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 org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.openqa.selenium.remote.RemoteWebDriver;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
+
+@Slf4j
+public abstract class BaseWorkflowE2ETest {
+
+    protected static String projectName = UUID.randomUUID().toString();
+
+    protected static final AdminUser adminUser = new AdminUser();
+
+    protected static RemoteWebDriver browser;
+
+    @BeforeAll
+    public static void setup() {
+        browser = WebDriverHolder.getWebDriver();
+
+        TenantPage tenantPage = new LoginPage(browser)
+                .login(adminUser)
+                .goToNav(SecurityPage.class)
+                .goToTab(TenantPage.class);
+
+        if (tenantPage.tenants().stream().noneMatch(tenant -> 
tenant.tenantCode().equals(adminUser.getTenant()))) {
+            tenantPage
+                    .create(adminUser.getTenant())
+                    .goToNav(SecurityPage.class)
+                    .goToTab(UserPage.class)
+                    .update(adminUser);
+        }
+        tenantPage
+                .goToNav(ProjectPage.class)
+                .createProjectUntilSuccess(projectName);
+    }
+
+    protected void untilWorkflowDefinitionExist(String workflowName) {
+        WorkflowDefinitionTab workflowDefinitionPage = new ProjectPage(browser)
+                .goToNav(ProjectPage.class)
+                .goTo(projectName)
+                .goToTab(WorkflowDefinitionTab.class);
+
+        await().untilAsserted(() -> 
assertThat(workflowDefinitionPage.workflowList())
+                .as("Workflow list should contain newly-created workflow: %s", 
workflowName)
+                .anyMatch(
+                        it -> it.getText().contains(workflowName)
+                ));
+    }
+
+    protected void runWorkflow(String workflowName) {
+        final ProjectDetailPage projectPage = new ProjectPage(browser)
+                .goToNav(ProjectPage.class)
+                .goTo(projectName);
+
+        projectPage
+                .goToTab(WorkflowDefinitionTab.class)
+                .run(workflowName)
+                .submit();
+
+    }
+
+    protected WorkflowInstanceTab.Row untilWorkflowInstanceExist(String 
workflowName) {
+        final ProjectDetailPage projectPage = new ProjectPage(browser)
+                .goToNav(ProjectPage.class)
+                .goTo(projectName);
+
+        return await()
+                .until(() -> {
+                    browser.navigate().refresh();
+                    return projectPage
+                            .goToTab(WorkflowInstanceTab.class)
+                            .instances()
+                            .stream()
+                            .filter(it -> 
it.workflowInstanceName().startsWith(workflowName))
+                            .findFirst()
+                            .orElse(null);
+                }, Objects::nonNull);
+    }
+
+    protected WorkflowInstanceTab.Row untilWorkflowInstanceSuccess(String 
workflowName) {
+        final ProjectDetailPage projectPage = new ProjectPage(browser)
+                .goToNav(ProjectPage.class)
+                .goTo(projectName);
+        return await()
+                .until(() -> {
+                    browser.navigate().refresh();
+                    return projectPage
+                            .goToTab(WorkflowInstanceTab.class)
+                            .instances()
+                            .stream()
+                            .filter(it -> 
it.workflowInstanceName().startsWith(workflowName))
+                            .filter(WorkflowInstanceTab.Row::isSuccess)
+                            .findFirst()
+                            .orElse(null);
+                }, Objects::nonNull);
+    }
+
+    protected WorkflowInstanceTab.Row untilWorkflowInstanceFailed(String 
workflowName) {
+        final ProjectDetailPage projectPage = new ProjectPage(browser)
+                .goToNav(ProjectPage.class)
+                .goTo(projectName);
+        return await()
+                .until(() -> {
+                    browser.navigate().refresh();
+                    List<WorkflowInstanceTab.Row> workflowInstances = 
projectPage
+                            .goToTab(WorkflowInstanceTab.class)
+                            .instances()
+                            .stream()
+                            .filter(it -> 
it.workflowInstanceName().startsWith(workflowName))
+                            .filter(WorkflowInstanceTab.Row::isFailed)
+                            .collect(Collectors.toList());
+                    if (workflowInstances.isEmpty()) {
+                        return null;
+                    }
+                    if (workflowInstances.size() > 1) {
+                        throw new RuntimeException("More than one failed 
workflow instance found: " +
+                                workflowInstances.stream()
+                                        
.map(WorkflowInstanceTab.Row::workflowInstanceName).collect(Collectors.joining(",
 ")));
+                    }
+                    return workflowInstances.get(0);
+                }, Objects::nonNull);
+    }
+
+    protected TaskInstanceTab.Row untilTaskInstanceSuccess(String 
workflowName, String taskName) {
+        final ProjectDetailPage projectPage = new ProjectPage(browser)
+                .goToNav(ProjectPage.class)
+                .goTo(projectName);
+        return await()
+                .until(() -> {
+                    browser.navigate().refresh();
+                    List<TaskInstanceTab.Row> taskInstances = projectPage
+                            .goToTab(TaskInstanceTab.class)
+                            .instances()
+                            .stream()
+                            .filter(it -> 
it.taskInstanceName().startsWith(taskName))
+                            .filter(it -> 
it.workflowInstanceName().startsWith(workflowName))
+                            .filter(TaskInstanceTab.Row::isSuccess)
+                            .collect(Collectors.toList());
+
+                    if (taskInstances.isEmpty()) {
+                        return null;
+                    }
+                    if (taskInstances.size() > 1) {
+                        throw new RuntimeException("More than one failed task 
instance found: " +
+                                taskInstances.stream()
+                                        
.map(TaskInstanceTab.Row::taskInstanceName).collect(Collectors.joining(", ")));
+                    }
+                    return taskInstances.get(0);
+                }, Objects::nonNull);
+    }
+
+    protected TaskInstanceTab.Row untilTaskInstanceFailed(String workflowName, 
String taskName) {
+        final ProjectDetailPage projectPage = new ProjectPage(browser)
+                .goToNav(ProjectPage.class)
+                .goTo(projectName);
+        return await()
+                .until(() -> {
+                    browser.navigate().refresh();
+                    List<TaskInstanceTab.Row> taskInstances = projectPage
+                            .goToTab(TaskInstanceTab.class)
+                            .instances()
+                            .stream()
+                            .filter(it -> 
it.taskInstanceName().startsWith(taskName))
+                            .filter(it -> 
it.workflowInstanceName().startsWith(workflowName))
+                            .filter(TaskInstanceTab.Row::isFailed)
+                            .collect(Collectors.toList());
+
+                    if (taskInstances.isEmpty()) {
+                        return null;
+                    }
+                    if (taskInstances.size() > 1) {
+                        throw new RuntimeException("More than one failed task 
instance found: " +
+                                taskInstances.stream()
+                                        
.map(TaskInstanceTab.Row::taskInstanceName).collect(Collectors.joining(", ")));
+                    }
+                    return taskInstances.get(0);
+                }, Objects::nonNull);
+    }
+
+}
diff --git 
a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/models/tenant/BootstrapTenant.java
similarity index 54%
copy from 
dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
copy to 
dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/models/tenant/BootstrapTenant.java
index 09132ebac9..15af3197e2 100644
--- 
a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/models/tenant/BootstrapTenant.java
@@ -15,39 +15,17 @@
  * limitations under the License.
  */
 
-package org.apache.dolphinscheduler.spi.enums;
+package org.apache.dolphinscheduler.e2e.models.tenant;
 
-import lombok.Getter;
+public class BootstrapTenant implements ITenant {
 
-import com.baomidou.mybatisplus.annotation.EnumValue;
-
-/**
- * resource type
- */
-@Getter
-public enum ResourceType {
-
-    /**
-     * 0 file, 1 udf
-     */
-    FILE(0, "file"),
-    ALL(2, "all");
-
-    ResourceType(int code, String desc) {
-        this.code = code;
-        this.desc = desc;
+    @Override
+    public String getTenantCode() {
+        return System.getProperty("user.name");
     }
 
-    @EnumValue
-    private final int code;
-    private final String desc;
-
-    public static ResourceType getResourceType(int code) {
-        for (ResourceType resourceType : ResourceType.values()) {
-            if (resourceType.getCode() == code) {
-                return resourceType;
-            }
-        }
-        return null;
+    @Override
+    public String getDescription() {
+        return "bootstrap tenant";
     }
 }
diff --git 
a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/models/tenant/DefaultTenant.java
similarity index 54%
copy from 
dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
copy to 
dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/models/tenant/DefaultTenant.java
index 09132ebac9..598265fa96 100644
--- 
a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/models/tenant/DefaultTenant.java
@@ -15,39 +15,16 @@
  * limitations under the License.
  */
 
-package org.apache.dolphinscheduler.spi.enums;
+package org.apache.dolphinscheduler.e2e.models.tenant;
 
-import lombok.Getter;
-
-import com.baomidou.mybatisplus.annotation.EnumValue;
-
-/**
- * resource type
- */
-@Getter
-public enum ResourceType {
-
-    /**
-     * 0 file, 1 udf
-     */
-    FILE(0, "file"),
-    ALL(2, "all");
-
-    ResourceType(int code, String desc) {
-        this.code = code;
-        this.desc = desc;
+public class DefaultTenant implements ITenant {
+    @Override
+    public String getTenantCode() {
+        return "default";
     }
 
-    @EnumValue
-    private final int code;
-    private final String desc;
-
-    public static ResourceType getResourceType(int code) {
-        for (ResourceType resourceType : ResourceType.values()) {
-            if (resourceType.getCode() == code) {
-                return resourceType;
-            }
-        }
-        return null;
+    @Override
+    public String getDescription() {
+        return "";
     }
 }
diff --git 
a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/models/tenant/ITenant.java
similarity index 53%
copy from 
dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
copy to 
dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/models/tenant/ITenant.java
index 09132ebac9..641acbf9d7 100644
--- 
a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/models/tenant/ITenant.java
@@ -15,39 +15,12 @@
  * limitations under the License.
  */
 
-package org.apache.dolphinscheduler.spi.enums;
+package org.apache.dolphinscheduler.e2e.models.tenant;
 
-import lombok.Getter;
+public interface ITenant {
 
-import com.baomidou.mybatisplus.annotation.EnumValue;
+    String getTenantCode();
 
-/**
- * resource type
- */
-@Getter
-public enum ResourceType {
-
-    /**
-     * 0 file, 1 udf
-     */
-    FILE(0, "file"),
-    ALL(2, "all");
-
-    ResourceType(int code, String desc) {
-        this.code = code;
-        this.desc = desc;
-    }
-
-    @EnumValue
-    private final int code;
-    private final String desc;
+    String getDescription();
 
-    public static ResourceType getResourceType(int code) {
-        for (ResourceType resourceType : ResourceType.values()) {
-            if (resourceType.getCode() == code) {
-                return resourceType;
-            }
-        }
-        return null;
-    }
 }
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/models/users/AdminUser.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/models/users/AdminUser.java
new file mode 100644
index 0000000000..da23bc26bf
--- /dev/null
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/models/users/AdminUser.java
@@ -0,0 +1,62 @@
+/*
+ * 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.models.users;
+
+import lombok.Data;
+import org.apache.dolphinscheduler.e2e.models.tenant.BootstrapTenant;
+import org.apache.dolphinscheduler.e2e.models.tenant.ITenant;
+
+@Data
+public class AdminUser implements IUser {
+
+    private String userName;
+
+    private String password;
+
+    private String email;
+
+    private String phone;
+
+    private ITenant tenant;
+
+    @Override
+    public String getUserName() {
+        return "admin";
+    }
+
+    @Override
+    public String getPassword() {
+        return "dolphinscheduler123";
+    }
+
+    @Override
+    public String getEmail() {
+        return "[email protected]";
+    }
+
+    @Override
+    public String getPhone() {
+        return "15800000000";
+    }
+
+    @Override
+    public String getTenant() {
+        return new BootstrapTenant().getTenantCode();
+    }
+
+}
diff --git 
a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/models/users/IUser.java
similarity index 53%
copy from 
dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
copy to 
dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/models/users/IUser.java
index 09132ebac9..740a4b431e 100644
--- 
a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/models/users/IUser.java
@@ -15,39 +15,21 @@
  * limitations under the License.
  */
 
-package org.apache.dolphinscheduler.spi.enums;
+package org.apache.dolphinscheduler.e2e.models.users;
 
-import lombok.Getter;
+import org.apache.dolphinscheduler.e2e.models.tenant.ITenant;
+
+public interface IUser {
+
+    String getUserName();
+
+    String getPassword();
+
+    String getEmail();
+
+    String getPhone();
+
+    String getTenant();
 
-import com.baomidou.mybatisplus.annotation.EnumValue;
 
-/**
- * resource type
- */
-@Getter
-public enum ResourceType {
-
-    /**
-     * 0 file, 1 udf
-     */
-    FILE(0, "file"),
-    ALL(2, "all");
-
-    ResourceType(int code, String desc) {
-        this.code = code;
-        this.desc = desc;
-    }
-
-    @EnumValue
-    private final int code;
-    private final String desc;
-
-    public static ResourceType getResourceType(int code) {
-        for (ResourceType resourceType : ResourceType.values()) {
-            if (resourceType.getCode() == code) {
-                return resourceType;
-            }
-        }
-        return null;
-    }
 }
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/LoginPage.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/LoginPage.java
index cde8c9d778..a77149fac3 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/LoginPage.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/LoginPage.java
@@ -19,6 +19,8 @@
 
 package org.apache.dolphinscheduler.e2e.pages;
 
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
+import org.apache.dolphinscheduler.e2e.models.users.IUser;
 import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
 import org.apache.dolphinscheduler.e2e.pages.security.TenantPage;
 
@@ -59,17 +61,20 @@ public final class LoginPage extends NavBarPage {
     }
 
     @SneakyThrows
-    public NavBarPage login(String username, String password) {
-        new WebDriverWait(driver, 
Duration.ofSeconds(30)).until(ExpectedConditions.elementToBeClickable(buttonSwitchLanguage));
+    public NavBarPage login(IUser user) {
+        return login(user.getUserName(), user.getPassword());
+    }
 
+    @SneakyThrows
+    public NavBarPage login(String username, String password) {
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(buttonSwitchLanguage));
         buttonSwitchLanguage().click();
 
         inputUsername().sendKeys(username);
         inputPassword().sendKeys(password);
         buttonLogin().click();
 
-        new WebDriverWait(driver, Duration.ofSeconds(30))
-            .until(ExpectedConditions.urlContains("/home"));
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/home"));
 
         return new NavBarPage(driver);
     }
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 e55751c367..53ed30b362 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
@@ -19,11 +19,10 @@
  */
 package org.apache.dolphinscheduler.e2e.pages.common;
 
-import org.openqa.selenium.By;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.openqa.selenium.WebDriver;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.interactions.Actions;
-import org.openqa.selenium.remote.RemoteWebDriver;
 import org.openqa.selenium.support.FindBy;
 import org.openqa.selenium.support.FindBys;
 import org.openqa.selenium.support.PageFactory;
@@ -51,7 +50,7 @@ public final class CodeEditor {
     }
 
     public CodeEditor content(String content) {
-        new WebDriverWait(this.driver, 
Duration.ofSeconds(20)).until(ExpectedConditions.elementToBeClickable(editor));
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(editor));
 
         editor.click();
 
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/HttpInput.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/HttpInput.java
index ce3f07c819..770de59009 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/HttpInput.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/HttpInput.java
@@ -22,6 +22,7 @@
 package org.apache.dolphinscheduler.e2e.pages.common;
 
 import lombok.Getter;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.openqa.selenium.WebDriver;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
@@ -50,7 +51,7 @@ public class HttpInput {
     }
 
     public HttpInput content(String content) {
-        new WebDriverWait(this.driver, 
Duration.ofSeconds(20)).until(ExpectedConditions.elementToBeClickable(urlInput));
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(urlInput));
         urlInput().sendKeys(content);
         return this;
     }
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/NavBarPage.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/NavBarPage.java
index 0a6373977a..a6a64ccf92 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/NavBarPage.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/NavBarPage.java
@@ -20,6 +20,7 @@
 
 package org.apache.dolphinscheduler.e2e.pages.common;
 
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.datasource.DataSourcePage;
 import org.apache.dolphinscheduler.e2e.pages.project.ProjectPage;
 import org.apache.dolphinscheduler.e2e.pages.resource.ResourcePage;
@@ -64,26 +65,30 @@ public class NavBarPage {
 
     public <T extends NavBarItem> T goToNav(Class<T> nav) {
         if (nav == ProjectPage.class) {
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.elementToBeClickable(projectTab));
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(projectTab));
             projectTab.click();
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/projects/list"));
             return nav.cast(new ProjectPage(driver));
         }
 
         if (nav == SecurityPage.class) {
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.elementToBeClickable(securityTab));
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(securityTab));
             securityTab.click();
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/security/tenant-manage"));
             return nav.cast(new SecurityPage(driver));
         }
 
         if (nav == ResourcePage.class) {
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.elementToBeClickable(resourceTab));
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(resourceTab));
             ((JavascriptExecutor) 
driver).executeScript("arguments[0].click();", resourceTab());
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/resource/file-manage"));
             return nav.cast(new ResourcePage(driver));
         }
 
         if (nav == DataSourcePage.class) {
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.elementToBeClickable(dataSourceTab));
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(dataSourceTab));
             dataSourceTab.click();
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/datasource"));
             return nav.cast(new DataSourcePage(driver));
         }
 
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 bd2f7e795b..7f84ac29de 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
@@ -22,6 +22,7 @@ package org.apache.dolphinscheduler.e2e.pages.datasource;
 
 import lombok.Getter;
 
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
 
 import java.security.Key;
@@ -74,12 +75,12 @@ public class DataSourcePage extends NavBarPage implements 
NavBarPage.NavBarItem
                                            String jdbcParams) {
         buttonCreateDataSource().click();
 
-        new WebDriverWait(driver, 
Duration.ofSeconds(10)).until(ExpectedConditions.visibilityOfElementLocated(
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.visibilityOfElementLocated(
             new By.ByClassName("dialog-source-modal")));
 
         
dataSourceModal().findElement(By.className(dataSourceType.toUpperCase()+"-box")).click();
 
-        new WebDriverWait(driver, 
Duration.ofSeconds(10)).until(ExpectedConditions.textToBePresentInElement(driver.findElement(By.className("dialog-create-data-source")),
 dataSourceType.toUpperCase()));
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.textToBePresentInElement(driver.findElement(By.className("dialog-create-data-source")),
 dataSourceType.toUpperCase()));
 
         createDataSourceForm().inputDataSourceName().sendKeys(dataSourceName);
         
createDataSourceForm().inputDataSourceDescription().sendKeys(dataSourceDescription);
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectDetailPage.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectDetailPage.java
index 2ad24507ab..6e06db5039 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectDetailPage.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectDetailPage.java
@@ -19,16 +19,22 @@
  */
 package org.apache.dolphinscheduler.e2e.pages.project;
 
+import java.time.Duration;
+import lombok.SneakyThrows;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
 import org.apache.dolphinscheduler.e2e.pages.project.workflow.TaskInstanceTab;
 import 
org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowDefinitionTab;
 import 
org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowInstanceTab;
 
+import org.openqa.selenium.By;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.remote.RemoteWebDriver;
 import org.openqa.selenium.support.FindBy;
 
 import lombok.Getter;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
 
 @Getter
 public final class ProjectDetailPage extends NavBarPage {
@@ -45,17 +51,21 @@ public final class ProjectDetailPage extends NavBarPage {
         super(driver);
     }
 
+    @SneakyThrows
     public <T extends Tab> T goToTab(Class<T> tab) {
         if (tab == WorkflowDefinitionTab.class) {
             menuProcessDefinition().click();
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/workflow-definition"));
             return tab.cast(new WorkflowDefinitionTab(driver));
         }
         if (tab == WorkflowInstanceTab.class) {
             menuProcessInstances().click();
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/workflow/instances"));
             return tab.cast(new WorkflowInstanceTab(driver));
         }
         if (tab == TaskInstanceTab.class) {
             menuTaskInstances().click();
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/task/instances"));
             return tab.cast(new TaskInstanceTab(driver));
         }
 
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectPage.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectPage.java
index 6219fab099..8be97f5db9 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectPage.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectPage.java
@@ -35,6 +35,8 @@ import org.openqa.selenium.support.ui.ExpectedConditions;
 import org.openqa.selenium.support.ui.WebDriverWait;
 
 import lombok.Getter;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
 
 @Getter
 public final class ProjectPage extends NavBarPage implements NavBarItem {
@@ -64,17 +66,25 @@ public final class ProjectPage extends NavBarPage 
implements NavBarItem {
         buttonCreateProject().click();
         createProjectForm().inputProjectName().sendKeys(project);
         createProjectForm().buttonSubmit().click();
+        return this;
+    }
 
+    public ProjectPage createProjectUntilSuccess(String project) {
+        create(project);
+        await().untilAsserted(() -> assertThat(projectList())
+                .as("project list should contain newly-created project")
+                .anyMatch(it -> it.getText().contains(project)));
         return this;
     }
 
+
     public ProjectPage delete(String project) {
         projectList()
-            .stream()
-            .filter(it -> it.getText().contains(project))
-            .findFirst()
-            .orElseThrow(() -> new RuntimeException("Cannot find project: " + 
project))
-            .findElement(By.className("delete")).click();
+                .stream()
+                .filter(it -> it.getText().contains(project))
+                .findFirst()
+                .orElseThrow(() -> new RuntimeException("Cannot find project: 
" + project))
+                .findElement(By.className("delete")).click();
 
         ((JavascriptExecutor) driver).executeScript("arguments[0].click();", 
buttonConfirm());
 
@@ -83,11 +93,11 @@ public final class ProjectPage extends NavBarPage 
implements NavBarItem {
 
     public ProjectDetailPage goTo(String project) {
         projectList().stream()
-                     .filter(it -> it.getText().contains(project))
-                     .map(it -> 
it.findElement(By.className("project-name")).findElement(new 
By.ByTagName("button")))
-                     .findFirst()
-                     .orElseThrow(() -> new RuntimeException("Cannot click the 
project item"))
-                     .click();
+                .filter(it -> it.getText().contains(project))
+                .map(it -> 
it.findElement(By.className("project-name")).findElement(new 
By.ByTagName("button")))
+                .findFirst()
+                .orElseThrow(() -> new RuntimeException("Cannot click the 
project item"))
+                .click();
 
         return new ProjectDetailPage(driver);
     }
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/TaskInstanceTab.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/TaskInstanceTab.java
index 06f76676b8..d1d81b70e4 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/TaskInstanceTab.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/TaskInstanceTab.java
@@ -35,7 +35,8 @@ import java.util.stream.Collectors;
 
 @Getter
 public final class TaskInstanceTab extends NavBarPage implements 
ProjectDetailPage.Tab {
-    @FindBy(className = "items-task-instances")
+
+    @FindBy(className = "batch-task-instance-items")
     private List<WebElement> instanceList;
 
     public TaskInstanceTab(RemoteWebDriver driver) {
@@ -47,7 +48,6 @@ public final class TaskInstanceTab extends NavBarPage 
implements ProjectDetailPa
             .stream()
             .filter(WebElement::isDisplayed)
             .map(Row::new)
-            .filter(row -> !row.name().isEmpty())
             .collect(Collectors.toList());
     }
 
@@ -55,12 +55,25 @@ public final class TaskInstanceTab extends NavBarPage 
implements ProjectDetailPa
     public static class Row {
         private final WebElement row;
 
-        public String state() {
-            return 
row.findElement(By.className("task-instance-state")).getText();
+        public String taskInstanceName() {
+            return 
row.findElement(By.cssSelector("td[data-col-key=name]")).getText();
+        }
+
+        public String workflowInstanceName() {
+            return 
row.findElement(By.cssSelector("td[data-col-key=processInstanceName]")).getText();
+        }
+
+        public int retryTimes() {
+            return 
Integer.parseInt(row.findElement(By.cssSelector("td[data-col-key=retryTimes]")).getText());
+        }
+
+        public boolean isSuccess() {
+            return !row.findElements(By.className("success")).isEmpty();
         }
 
-        public String name() {
-            return 
row.findElement(By.className("task-instance-name")).getText();
+        public boolean isFailed() {
+            return !row.findElements(By.className("failed")).isEmpty();
         }
+
     }
 }
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 58c5c96051..69573ab7ff 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
@@ -19,6 +19,7 @@
  */
 package org.apache.dolphinscheduler.e2e.pages.project.workflow;
 
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import 
org.apache.dolphinscheduler.e2e.pages.project.workflow.task.HttpTaskForm;
 import 
org.apache.dolphinscheduler.e2e.pages.project.workflow.task.ShellTaskForm;
 import 
org.apache.dolphinscheduler.e2e.pages.project.workflow.task.SubWorkflowTaskForm;
@@ -93,7 +94,7 @@ public final class WorkflowForm {
     }
 
     public WebElement getTask(String taskName) {
-        List<WebElement> tasks = new WebDriverWait(driver, 
Duration.ofSeconds(20))
+        List<WebElement> tasks = 
WebDriverWaitFactory.createWebDriverWait(driver)
                 
.until(ExpectedConditions.visibilityOfAllElementsLocatedBy(By.cssSelector("svg 
> g > g[class^='x6-graph-svg-stage'] > g[data-shape^='dag-task']")));
 
         WebElement task = tasks.stream()
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowInstanceTab.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowInstanceTab.java
index f55c5d0263..2b7fa28274 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowInstanceTab.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowInstanceTab.java
@@ -86,6 +86,10 @@ public final class WorkflowInstanceTab extends NavBarPage 
implements ProjectDeta
     public static class Row {
         private final WebElement row;
 
+        public String workflowInstanceName() {
+            return row.findElement(By.className("workflow-name")).getText();
+        }
+
         public WebElement rerunButton() {
             return row.findElement(By.className("btn-rerun"));
         }
@@ -94,6 +98,10 @@ public final class WorkflowInstanceTab extends NavBarPage 
implements ProjectDeta
             return !row.findElements(By.className("success")).isEmpty();
         }
 
+        public boolean isFailed() {
+            return !row.findElements(By.className("failed")).isEmpty();
+        }
+
         public int executionTime() {
             return 
Integer.parseInt(row.findElement(By.className("workflow-run-times")).getText());
         }
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 9a3e24fb8a..9e337be43e 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
@@ -19,6 +19,7 @@
  */
 package org.apache.dolphinscheduler.e2e.pages.project.workflow;
 
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
 import org.openqa.selenium.support.PageFactory;
@@ -44,7 +45,7 @@ public final class WorkflowRunDialog {
     }
 
     public WorkflowDefinitionTab submit() {
-        new WebDriverWait(parent().driver(), 
Duration.ofSeconds(20)).until(ExpectedConditions.elementToBeClickable(buttonSubmit()));
+        
WebDriverWaitFactory.createWebDriverWait(parent.driver()).until(ExpectedConditions.elementToBeClickable(buttonSubmit()));
 
         buttonSubmit().click();
 
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/SubWorkflowTaskForm.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/SubWorkflowTaskForm.java
index d89037feaa..6f328ddf23 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/SubWorkflowTaskForm.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/SubWorkflowTaskForm.java
@@ -19,6 +19,7 @@
  */
 package org.apache.dolphinscheduler.e2e.pages.project.workflow.task;
 
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowForm;
 
 import lombok.Getter;
@@ -54,11 +55,11 @@ public final class SubWorkflowTaskForm extends TaskNodeForm 
{
     }
 
     public SubWorkflowTaskForm childNode(String node) {
-        new WebDriverWait(driver, 
Duration.ofSeconds(5)).until(ExpectedConditions.elementToBeClickable(btnSelectChildNodeDropdown));
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(btnSelectChildNodeDropdown));
         
         btnSelectChildNodeDropdown().click();
 
-        new WebDriverWait(driver, 
Duration.ofSeconds(5)).until(ExpectedConditions.visibilityOfElementLocated(By.className(
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.visibilityOfElementLocated(By.className(
                 "n-base-select-option__content")));
 
         selectChildNode()
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/SwitchTaskForm.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/SwitchTaskForm.java
index 988a00c7bd..480f5b410b 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/SwitchTaskForm.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/SwitchTaskForm.java
@@ -20,6 +20,7 @@
 package org.apache.dolphinscheduler.e2e.pages.project.workflow.task;
 
 import lombok.Getter;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowForm;
 import org.openqa.selenium.By;
 import org.openqa.selenium.JavascriptExecutor;
@@ -54,7 +55,7 @@ public final class SwitchTaskForm extends TaskNodeForm {
 
         final By optionsLocator = By.className("option-else-branches");
 
-        new WebDriverWait(parent().driver(), Duration.ofSeconds(10))
+        WebDriverWaitFactory.createWebDriverWait(parent().driver())
                 
.until(ExpectedConditions.visibilityOfElementLocated(optionsLocator));
 
         List<WebElement> webElements =  
parent().driver().findElements(optionsLocator);
@@ -79,7 +80,7 @@ public final class SwitchTaskForm extends TaskNodeForm {
 
         final By optionsLocator = By.className("option-if-branches");
 
-        new WebDriverWait(parent().driver(), Duration.ofSeconds(10))
+        WebDriverWaitFactory.createWebDriverWait(parent().driver())
                 
.until(ExpectedConditions.visibilityOfElementLocated(optionsLocator));
 
         List<WebElement> webElements =  
parent().driver().findElements(optionsLocator);
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 fadfd6c38b..5d0a944b02 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
@@ -20,6 +20,7 @@
 package org.apache.dolphinscheduler.e2e.pages.project.workflow.task;
 
 import lombok.Getter;
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowForm;
 import org.openqa.selenium.By;
 import org.openqa.selenium.JavascriptExecutor;
@@ -28,13 +29,11 @@ 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.pagefactory.ByChained;
 import org.openqa.selenium.support.ui.ExpectedConditions;
 import org.openqa.selenium.support.ui.WebDriverWait;
 
 import java.time.Duration;
 import java.util.List;
-import java.util.stream.Stream;
 
 @Getter
 public abstract class TaskNodeForm {
@@ -48,14 +47,14 @@ public abstract class TaskNodeForm {
     private WebElement buttonSubmit;
 
     @FindBys({
-        @FindBy(className = "input-param-key"),
-        @FindBy(tagName = "input"),
+            @FindBy(className = "input-param-key"),
+            @FindBy(tagName = "input"),
     })
     private List<WebElement> inputParamKey;
 
     @FindBys({
-        @FindBy(className = "input-param-value"),
-        @FindBy(tagName = "input"),
+            @FindBy(className = "input-param-value"),
+            @FindBy(tagName = "input"),
     })
     private List<WebElement> inputParamValue;
 
@@ -80,6 +79,13 @@ public abstract class TaskNodeForm {
     @FindBy(className = "btn-create-custom-parameter")
     private WebElement buttonCreateCustomParameters;
 
+    @FindBys({
+            @FindBy(className = "resource-select"),
+            @FindBy(className = "n-base-selection"),
+    })
+    private WebElement selectResource;
+
+
     private final WorkflowForm parent;
 
     TaskNodeForm(WorkflowForm parent) {
@@ -118,15 +124,15 @@ public abstract class TaskNodeForm {
         return this;
     }
 
-    public TaskNodeForm selectEnv(String envName){
-        
((JavascriptExecutor)parent().driver()).executeScript("arguments[0].click();", 
selectEnv);
+    public TaskNodeForm selectEnv(String envName) {
+        ((JavascriptExecutor) 
parent().driver()).executeScript("arguments[0].click();", selectEnv);
 
         final By optionsLocator = 
By.className("n-base-selection-input__content");
 
-        new WebDriverWait(parent.driver(), Duration.ofSeconds(20))
+        WebDriverWaitFactory.createWebDriverWait(parent().driver())
                 
.until(ExpectedConditions.visibilityOfElementLocated(optionsLocator));
 
-        List<WebElement> webElements =  
parent.driver().findElements(optionsLocator);
+        List<WebElement> webElements = 
parent.driver().findElements(optionsLocator);
 
         webElements.stream()
                 .filter(it -> it.getText().contains(envName))
@@ -138,14 +144,14 @@ public abstract class TaskNodeForm {
     }
 
     public TaskNodeForm preTask(String preTaskName) {
-        
((JavascriptExecutor)parent().driver()).executeScript("arguments[0].click();", 
selectPreTasks);
+        ((JavascriptExecutor) 
parent().driver()).executeScript("arguments[0].click();", selectPreTasks);
 
         final By optionsLocator = By.className("option-pre-tasks");
 
-        new WebDriverWait(parent.driver(), Duration.ofSeconds(20))
+        WebDriverWaitFactory.createWebDriverWait(parent.driver())
                 
.until(ExpectedConditions.visibilityOfElementLocated(optionsLocator));
 
-        List<WebElement> webElements =  
parent.driver().findElements(optionsLocator);
+        List<WebElement> webElements = 
parent.driver().findElements(optionsLocator);
         webElements.stream()
                 .filter(it -> it.getText().contains(preTaskName))
                 .findFirst()
@@ -157,6 +163,23 @@ public abstract class TaskNodeForm {
         return this;
     }
 
+    public TaskNodeForm selectResource(String resourceName) {
+        ((JavascriptExecutor) 
parent().driver()).executeScript("arguments[0].click();", selectResource);
+
+        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();
+        return this;
+    }
+
     public WorkflowForm submit() {
         buttonSubmit.click();
 
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 a2a780be9d..412acf0f2a 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
@@ -22,6 +22,7 @@ package org.apache.dolphinscheduler.e2e.pages.resource;
 
 import lombok.Getter;
 
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.common.CodeEditor;
 import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
 
@@ -42,6 +43,8 @@ import org.openqa.selenium.support.ui.WebDriverWait;
 import java.io.File;
 import java.time.Duration;
 import java.util.List;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
 
 
 @Getter
@@ -92,6 +95,7 @@ public class FileManagePage extends NavBarPage implements 
ResourcePage.Tab {
         uploadFileBox = new UploadFileBox();
 
         editFileBox = new EditFileBox();
+
     }
 
     public FileManagePage createDirectory(String name) {
@@ -114,13 +118,13 @@ public class FileManagePage extends NavBarPage implements 
ResourcePage.Tab {
 
     public FileManagePage rename(String currentName, String AfterName) {
         fileList()
-            .stream()
-            .filter(it -> it.getText().contains(currentName))
-            .flatMap(it -> 
it.findElements(By.className("btn-rename")).stream())
-            .filter(WebElement::isDisplayed)
-            .findFirst()
-            .orElseThrow(() -> new RuntimeException("No rename button in file 
manage list"))
-            .click();
+                .stream()
+                .filter(it -> it.getText().contains(currentName))
+                .flatMap(it -> 
it.findElements(By.className("btn-rename")).stream())
+                .filter(WebElement::isDisplayed)
+                .findFirst()
+                .orElseThrow(() -> new RuntimeException("No rename button in 
file manage list"))
+                .click();
 
         renameBox().inputName().sendKeys(Keys.CONTROL + "a");
         renameBox().inputName().sendKeys(Keys.BACK_SPACE);
@@ -132,12 +136,12 @@ public class FileManagePage extends NavBarPage implements 
ResourcePage.Tab {
 
     public FileManagePage createSubDirectory(String directoryName, String 
subDirectoryName) {
         fileList()
-            .stream()
-            .filter(it -> it.getText().contains(directoryName))
-            .filter(WebElement::isDisplayed)
-            .findFirst()
-            .orElseThrow(() -> new RuntimeException(String.format("No %s in 
file manage list", directoryName)))
-            .click();
+                .stream()
+                .filter(it -> it.getText().contains(directoryName))
+                .filter(WebElement::isDisplayed)
+                .findFirst()
+                .orElseThrow(() -> new RuntimeException(String.format("No %s 
in file manage list", directoryName)))
+                .click();
 
         buttonCreateDirectory().click();
 
@@ -149,42 +153,61 @@ public class FileManagePage extends NavBarPage implements 
ResourcePage.Tab {
 
     public FileManagePage delete(String name) {
         fileList()
-            .stream()
-            .filter(it -> it.getText().contains(name))
-            .flatMap(it -> 
it.findElements(By.className("btn-delete")).stream())
-            .filter(WebElement::isDisplayed)
-            .findFirst()
-            .orElseThrow(() -> new RuntimeException("No delete button in file 
manage list"))
-            .click();
+                .stream()
+                .filter(it -> it.getText().contains(name))
+                .flatMap(it -> 
it.findElements(By.className("btn-delete")).stream())
+                .filter(WebElement::isDisplayed)
+                .findFirst()
+                .orElseThrow(() -> new RuntimeException("No delete button in 
file manage list"))
+                .click();
 
         ((JavascriptExecutor) driver).executeScript("arguments[0].click();", 
buttonConfirm());
 
         return this;
     }
 
+    // todo: add file type
     public FileManagePage createFile(String fileName, String scripts) {
+
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(buttonCreateFile()));
+
         buttonCreateFile().click();
 
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/resource/file/create"));
+
         createFileBox().inputFileName().sendKeys(fileName);
         createFileBox().codeEditor().content(scripts);
         createFileBox().buttonSubmit().click();
+        // todo: check if the operation is successful
+        return this;
+    }
+
+    public FileManagePage createFileUntilSuccess(String fileName, String 
scripts) {
+
+        createFile(fileName, scripts);
 
+        await()
+                .untilAsserted(() ->
+                        assertThat(fileList())
+                                .as("File list should contain newly-created 
file: " + fileName)
+                                .extracting(WebElement::getText)
+                                .anyMatch(it -> it.contains(fileName)));
         return this;
     }
 
     public FileManagePage editFile(String fileName, String scripts) {
         fileList()
-            .stream()
-            .filter(it -> it.getText().contains(fileName))
-            .flatMap(it -> it.findElements(By.className("btn-edit")).stream())
-            .filter(WebElement::isDisplayed)
-            .findFirst()
-            .orElseThrow(() -> new RuntimeException("No edit button in file 
manage list"))
-            .click();
+                .stream()
+                .filter(it -> it.getText().contains(fileName))
+                .flatMap(it -> 
it.findElements(By.className("btn-edit")).stream())
+                .filter(WebElement::isDisplayed)
+                .findFirst()
+                .orElseThrow(() -> new RuntimeException("No edit button in 
file manage list"))
+                .click();
 
-        new WebDriverWait(driver, 
Duration.ofSeconds(20)).until(ExpectedConditions.urlContains("/edit"));
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/edit"));
 
-        new WebDriverWait(driver, 
Duration.ofSeconds(20)).until(ExpectedConditions.textToBePresentInElement(driver.findElement(By.tagName("body")),
 fileName));
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.textToBePresentInElement(driver.findElement(By.tagName("body")),
 fileName));
 
         editFileBox().codeEditor().content(scripts);
         editFileBox().buttonSubmit().click();
@@ -205,13 +228,13 @@ public class FileManagePage extends NavBarPage implements 
ResourcePage.Tab {
 
     public FileManagePage downloadFile(String fileName) {
         fileList()
-            .stream()
-            .filter(it -> it.getText().contains(fileName))
-            .flatMap(it -> 
it.findElements(By.className("btn-download")).stream())
-            .filter(WebElement::isDisplayed)
-            .findFirst()
-            .orElseThrow(() -> new RuntimeException("No download button in 
file manage list"))
-            .click();
+                .stream()
+                .filter(it -> it.getText().contains(fileName))
+                .flatMap(it -> 
it.findElements(By.className("btn-download")).stream())
+                .filter(WebElement::isDisplayed)
+                .findFirst()
+                .orElseThrow(() -> new RuntimeException("No download button in 
file manage list"))
+                .click();
 
         return this;
     }
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/ResourcePage.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/ResourcePage.java
index 933a6df85e..23264147f8 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/ResourcePage.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/ResourcePage.java
@@ -19,6 +19,7 @@
  */
 package org.apache.dolphinscheduler.e2e.pages.resource;
 
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
 
 import java.time.Duration;
@@ -27,6 +28,7 @@ import org.openqa.selenium.JavascriptExecutor;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.remote.RemoteWebDriver;
 import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.PageFactory;
 import org.openqa.selenium.support.ui.ExpectedConditions;
 import org.openqa.selenium.support.ui.WebDriverWait;
 
@@ -40,14 +42,16 @@ public class ResourcePage extends NavBarPage implements 
NavBarPage.NavBarItem {
 
     public ResourcePage(RemoteWebDriver driver) {
         super(driver);
+
+        PageFactory.initElements(driver, this);
     }
 
     public <T extends ResourcePage.Tab> T goToTab(Class<T> tab) {
         if (tab == FileManagePage.class) {
-            new WebDriverWait(driver, 
Duration.ofSeconds(20)).until(ExpectedConditions.urlContains("/resource"));
-            new WebDriverWait(driver, 
Duration.ofSeconds(20)).until(ExpectedConditions.elementToBeClickable(fileManageTab));
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/resource"));
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(fileManageTab));
             ((JavascriptExecutor) 
driver).executeScript("arguments[0].click();", fileManageTab());
-            new WebDriverWait(driver, 
Duration.ofSeconds(20)).until(ExpectedConditions.urlContains("/file-manage"));
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/file-manage"));
             return tab.cast(new FileManagePage(driver));
         }
 
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/EnvironmentPage.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/EnvironmentPage.java
index 5d9f9bea36..c318c257e3 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/EnvironmentPage.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/EnvironmentPage.java
@@ -19,6 +19,7 @@
 
 package org.apache.dolphinscheduler.e2e.pages.security;
 
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
 
 import java.time.Duration;
@@ -67,7 +68,7 @@ public final class EnvironmentPage extends NavBarPage 
implements SecurityPage.Ta
         createEnvironmentForm().inputEnvironmentDesc().sendKeys(desc);
 
         editEnvironmentForm().btnSelectWorkerGroupDropdown().click();
-        new WebDriverWait(driver, 
Duration.ofSeconds(20)).until(ExpectedConditions.visibilityOfElementLocated(new 
By.ByClassName(
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.visibilityOfElementLocated(new
 By.ByClassName(
                 "n-base-select-option__content")));
         editEnvironmentForm().selectWorkerGroupList()
                 .stream()
@@ -106,7 +107,7 @@ public final class EnvironmentPage extends NavBarPage 
implements SecurityPage.Ta
 
         if 
(editEnvironmentForm().selectedWorkerGroup().getAttribute("innerHTML").equals(workerGroup))
 {
             editEnvironmentForm().btnSelectWorkerGroupDropdown().click();
-            new WebDriverWait(driver, 
Duration.ofSeconds(20)).until(ExpectedConditions.visibilityOfElementLocated(new 
By.ByClassName(
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.visibilityOfElementLocated(new
 By.ByClassName(
                     "n-base-select-option__content")));
             editEnvironmentForm().selectWorkerGroupList()
                     .stream()
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/SecurityPage.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/SecurityPage.java
index 5a5bb9c277..11cb748f7b 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/SecurityPage.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/SecurityPage.java
@@ -20,6 +20,7 @@
 
 package org.apache.dolphinscheduler.e2e.pages.security;
 
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
 import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage.NavBarItem;
 
@@ -67,59 +68,60 @@ public class SecurityPage extends NavBarPage implements 
NavBarItem {
     }
 
     public <T extends SecurityPage.Tab> T goToTab(Class<T> tab) {
+
         if (tab == TenantPage.class) {
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.urlContains("/security"));
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.elementToBeClickable(menuTenantManage));
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(menuTenantManage));
             ((JavascriptExecutor) 
driver).executeScript("arguments[0].click();", menuTenantManage());
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/security/tenant-manage"));
             return tab.cast(new TenantPage(driver));
         }
 
         if (tab == UserPage.class) {
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.urlContains("/security"));
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.elementToBeClickable(menUserManage));
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(menUserManage));
             ((JavascriptExecutor) 
driver).executeScript("arguments[0].click();", menUserManage());
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/security/user-manage"));
             return tab.cast(new UserPage(driver));
         }
 
         if (tab == WorkerGroupPage.class) {
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.urlContains("/security"));
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.elementToBeClickable(menWorkerGroupManage));
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(menWorkerGroupManage));
             ((JavascriptExecutor) 
driver).executeScript("arguments[0].click();", menWorkerGroupManage());
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/security/worker-group-manage"));
             return tab.cast(new WorkerGroupPage(driver));
         }
 
         if (tab == QueuePage.class) {
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.urlContains("/security"));
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.elementToBeClickable(menuQueueManage));
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(menuQueueManage));
             ((JavascriptExecutor) 
driver).executeScript("arguments[0].click();", menuQueueManage());
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/security/yarn-queue-manage"));
             return tab.cast(new QueuePage(driver));
         }
 
         if (tab == EnvironmentPage.class) {
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.urlContains("/security"));
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.elementToBeClickable(menuEnvironmentManage));
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(menuEnvironmentManage));
             ((JavascriptExecutor) 
driver).executeScript("arguments[0].click();", menuEnvironmentManage());
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/security/environment-manage"));
             return tab.cast(new EnvironmentPage(driver));
         }
 
         if (tab == ClusterPage.class) {
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.urlContains("/security"));
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.elementToBeClickable(menuClusterManage));
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(menuClusterManage));
             ((JavascriptExecutor) 
driver).executeScript("arguments[0].click();", menuClusterManage());
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/security/cluster-manage"));
             return tab.cast(new ClusterPage(driver));
         }
 
         if (tab == TokenPage.class) {
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.urlContains("/security"));
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.elementToBeClickable(menuTokenManage));
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(menuTokenManage));
             ((JavascriptExecutor) 
driver).executeScript("arguments[0].click();", menuTokenManage());
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/security/token-manage"));
             return tab.cast(new TokenPage(driver));
         }
 
         if (tab == NamespacePage.class) {
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.urlContains("/security"));
-            new WebDriverWait(driver, 
Duration.ofSeconds(60)).until(ExpectedConditions.elementToBeClickable(menuNamespaceManage));
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(menuNamespaceManage));
             ((JavascriptExecutor) 
driver).executeScript("arguments[0].click();", menuNamespaceManage());
+            
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.urlContains("/security/k8s-namespace-manage"));
             return tab.cast(new NamespacePage(driver));
         }
 
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/TenantPage.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/TenantPage.java
index cb24af307f..e6b9f877c1 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/TenantPage.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/TenantPage.java
@@ -19,10 +19,14 @@
 
 package org.apache.dolphinscheduler.e2e.pages.security;
 
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import org.apache.dolphinscheduler.e2e.models.tenant.ITenant;
 import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
 
 import java.util.List;
 
+import 
org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowInstanceTab;
 import org.openqa.selenium.By;
 import org.openqa.selenium.JavascriptExecutor;
 import org.openqa.selenium.Keys;
@@ -33,6 +37,9 @@ import org.openqa.selenium.support.FindBys;
 import org.openqa.selenium.support.PageFactory;
 
 import lombok.Getter;
+import org.openqa.selenium.support.pagefactory.ByChained;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
 
 @Getter
 public final class TenantPage extends NavBarPage implements SecurityPage.Tab {
@@ -43,8 +50,8 @@ public final class TenantPage extends NavBarPage implements 
SecurityPage.Tab {
     private List<WebElement> tenantList;
 
     @FindBys({
-        @FindBy(className = "n-popconfirm__action"),
-        @FindBy(className = "n-button--primary-type"),
+            @FindBy(className = "n-popconfirm__action"),
+            @FindBy(className = "n-button--primary-type"),
     })
     private WebElement buttonConfirm;
 
@@ -61,11 +68,28 @@ public final class TenantPage extends NavBarPage implements 
SecurityPage.Tab {
         editTenantForm = new TenantForm();
     }
 
+    public List<Row> tenants() {
+        return tenantList.stream()
+                .filter(WebElement::isDisplayed)
+                .map(Row::new)
+                .collect(Collectors.toList());
+    }
+
+    public boolean containsTenant(String tenant) {
+        return tenantList.stream()
+                .anyMatch(it -> 
it.findElement(By.className("tenant-code")).getText().contains(tenant));
+    }
+
+    public TenantPage create(ITenant tenant) {
+        return create(tenant.getTenantCode(), tenant.getDescription());
+    }
+
     public TenantPage create(String tenant) {
         return create(tenant, "");
     }
 
     public TenantPage create(String tenant, String description) {
+
         buttonCreateTenant().click();
         tenantForm().inputTenantCode().sendKeys(tenant);
         tenantForm().inputDescription().sendKeys(description);
@@ -76,12 +100,12 @@ public final class TenantPage extends NavBarPage 
implements SecurityPage.Tab {
 
     public TenantPage update(String tenant, String description) {
         tenantList().stream()
-            .filter(it -> 
it.findElement(By.className("tenant-code")).getAttribute("innerHTML").contains(tenant))
-            .flatMap(it -> it.findElements(By.className("edit")).stream())
-            .filter(WebElement::isDisplayed)
-            .findFirst()
-            .orElseThrow(() -> new RuntimeException("No edit button in tenant 
list"))
-            .click();
+                .filter(it -> 
it.findElement(By.className("tenant-code")).getAttribute("innerHTML").contains(tenant))
+                .flatMap(it -> it.findElements(By.className("edit")).stream())
+                .filter(WebElement::isDisplayed)
+                .findFirst()
+                .orElseThrow(() -> new RuntimeException("No edit button in 
tenant list"))
+                .click();
 
         editTenantForm().inputDescription().sendKeys(Keys.CONTROL + "a");
         editTenantForm().inputDescription().sendKeys(Keys.BACK_SPACE);
@@ -93,13 +117,13 @@ public final class TenantPage extends NavBarPage 
implements SecurityPage.Tab {
 
     public TenantPage delete(String tenant) {
         tenantList()
-            .stream()
-            .filter(it -> it.getText().contains(tenant))
-            .flatMap(it -> it.findElements(By.className("delete")).stream())
-            .filter(WebElement::isDisplayed)
-            .findFirst()
-            .orElseThrow(() -> new RuntimeException("No delete button in user 
list"))
-            .click();
+                .stream()
+                .filter(it -> it.getText().contains(tenant))
+                .flatMap(it -> 
it.findElements(By.className("delete")).stream())
+                .filter(WebElement::isDisplayed)
+                .findFirst()
+                .orElseThrow(() -> new RuntimeException("No delete button in 
user list"))
+                .click();
 
         ((JavascriptExecutor) driver).executeScript("arguments[0].click();", 
buttonConfirm());
 
@@ -133,4 +157,14 @@ public final class TenantPage extends NavBarPage 
implements SecurityPage.Tab {
         @FindBy(className = "btn-cancel")
         private WebElement buttonCancel;
     }
+
+    @RequiredArgsConstructor
+    public static class Row {
+        private final WebElement row;
+
+        public String tenantCode() {
+            return 
row.findElement(By.cssSelector("td[data-col-key=tenantCode]")).getText();
+        }
+
+    }
 }
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/TokenPage.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/TokenPage.java
index 5def2ad64f..7be82a840a 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/TokenPage.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/TokenPage.java
@@ -19,6 +19,7 @@
 
 package org.apache.dolphinscheduler.e2e.pages.security;
 
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
 import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
 import org.apache.dolphinscheduler.e2e.pages.security.SecurityPage.Tab;
 
@@ -69,9 +70,9 @@ public final class TokenPage extends NavBarPage implements 
Tab {
     public TokenPage create(String userName) {
         buttonCreateToken().click();
 
-        new WebDriverWait(driver, 
Duration.ofSeconds(30)).until(ExpectedConditions.elementToBeClickable(createTokenForm().selectUserNameDropdown()));
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(createTokenForm().selectUserNameDropdown()));
         createTokenForm().selectUserNameDropdown().click();
-        new WebDriverWait(driver, 
Duration.ofSeconds(30)).until(ExpectedConditions.visibilityOfElementLocated(new 
By.ByClassName(
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.visibilityOfElementLocated(new
 By.ByClassName(
                 "n-base-select-option__content")));
         createTokenForm().selectUserNameList()
                 .stream()
@@ -81,7 +82,7 @@ public final class TokenPage extends NavBarPage implements 
Tab {
                         userName)))
                 .click();
 
-        new WebDriverWait(driver, 
Duration.ofSeconds(30)).until(ExpectedConditions.elementToBeClickable(createTokenForm().buttonGenerateToken()));
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(createTokenForm().buttonGenerateToken()));
         createTokenForm().buttonGenerateToken().click();
 
         createTokenForm().buttonSubmit().click();
@@ -98,9 +99,9 @@ public final class TokenPage extends NavBarPage implements 
Tab {
             .orElseThrow(() -> new RuntimeException("No edit button in token 
list"))
             .click();
 
-        new WebDriverWait(driver, 
Duration.ofSeconds(30)).until(ExpectedConditions.elementToBeClickable(editTokenForm().buttonGenerateToken()));
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(editTokenForm().buttonGenerateToken()));
         editTokenForm().buttonGenerateToken().click();
-        new WebDriverWait(driver, 
Duration.ofSeconds(30)).until(ExpectedConditions.elementToBeClickable(editTokenForm().buttonGenerateToken()));
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.elementToBeClickable(editTokenForm().buttonGenerateToken()));
 
         editTokenForm().buttonSubmit().click();
 
diff --git 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/UserPage.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/UserPage.java
index 26a236ad52..1ee52e8a28 100644
--- 
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/UserPage.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/UserPage.java
@@ -19,6 +19,8 @@
 
 package org.apache.dolphinscheduler.e2e.pages.security;
 
+import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory;
+import org.apache.dolphinscheduler.e2e.models.users.IUser;
 import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
 
 import java.time.Duration;
@@ -67,7 +69,7 @@ public final class UserPage extends NavBarPage implements 
SecurityPage.Tab {
 
         createUserForm().btnSelectTenantDropdown().click();
 
-        new WebDriverWait(driver, 
Duration.ofSeconds(30)).until(ExpectedConditions.visibilityOfElementLocated(new 
By.ByClassName(
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.visibilityOfElementLocated(new
 By.ByClassName(
                 "n-base-select-option__content")));
 
         createUserForm().selectTenant()
@@ -84,7 +86,19 @@ public final class UserPage extends NavBarPage implements 
SecurityPage.Tab {
         return this;
     }
 
-    public UserPage update(String user, String editUser, String editEmail, 
String editPhone,
+    public UserPage update(IUser user) {
+        return update(
+                user.getUserName(),
+                user.getUserName(),
+                user.getEmail(),
+                user.getPhone(),
+                user.getTenant());
+    }
+
+    public UserPage update(String user,
+                           String editUser,
+                           String editEmail,
+                           String editPhone,
                            String tenant) {
         userList().stream()
             .filter(it -> 
it.findElement(By.className("name")).getAttribute("innerHTML").contains(user))
@@ -100,7 +114,7 @@ public final class UserPage extends NavBarPage implements 
SecurityPage.Tab {
 
         createUserForm().btnSelectTenantDropdown().click();
 
-        new WebDriverWait(driver, 
Duration.ofSeconds(30)).until(ExpectedConditions.visibilityOfElementLocated(new 
By.ByClassName(
+        
WebDriverWaitFactory.createWebDriverWait(driver).until(ExpectedConditions.visibilityOfElementLocated(new
 By.ByClassName(
                 "n-base-select-option__content")));
 
         createUserForm().selectTenant()
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 dcf22a2d6d..d40afe1f3a 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
@@ -80,7 +80,7 @@ final class DolphinSchedulerExtension implements 
BeforeAllCallback, AfterAllCall
     @SuppressWarnings("UnstableApiUsage")
     public void beforeAll(ExtensionContext context) throws IOException {
         Awaitility.setDefaultTimeout(Duration.ofSeconds(60));
-        Awaitility.setDefaultPollInterval(Duration.ofSeconds(2));
+        Awaitility.setDefaultPollInterval(Duration.ofMillis(500));
 
         setRecordPath();
 
@@ -115,6 +115,7 @@ final class DolphinSchedulerExtension implements 
BeforeAllCallback, AfterAllCall
               .filter(it -> Modifier.isStatic(it.getModifiers()))
               .filter(f -> WebDriver.class.isAssignableFrom(f.getType()))
               .forEach(it -> setDriver(clazz, it));
+        WebDriverHolder.setWebDriver(driver);
     }
 
     private void runInLocal() {
diff --git 
a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/WebDriverHolder.java
similarity index 54%
copy from 
dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
copy to 
dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/WebDriverHolder.java
index 09132ebac9..48e8c53e92 100644
--- 
a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/WebDriverHolder.java
@@ -15,39 +15,20 @@
  * limitations under the License.
  */
 
-package org.apache.dolphinscheduler.spi.enums;
+package org.apache.dolphinscheduler.e2e.core;
 
-import lombok.Getter;
+import org.openqa.selenium.remote.RemoteWebDriver;
 
-import com.baomidou.mybatisplus.annotation.EnumValue;
+public class WebDriverHolder {
 
-/**
- * resource type
- */
-@Getter
-public enum ResourceType {
-
-    /**
-     * 0 file, 1 udf
-     */
-    FILE(0, "file"),
-    ALL(2, "all");
+    public static RemoteWebDriver browser;
 
-    ResourceType(int code, String desc) {
-        this.code = code;
-        this.desc = desc;
+    public static void setWebDriver(RemoteWebDriver driver) {
+        browser = driver;
     }
 
-    @EnumValue
-    private final int code;
-    private final String desc;
-
-    public static ResourceType getResourceType(int code) {
-        for (ResourceType resourceType : ResourceType.values()) {
-            if (resourceType.getCode() == code) {
-                return resourceType;
-            }
-        }
-        return null;
+    public static RemoteWebDriver getWebDriver() {
+        return browser;
     }
+
 }
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
new file mode 100644
index 0000000000..946fe071ab
--- /dev/null
+++ 
b/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/WebDriverWaitFactory.java
@@ -0,0 +1,45 @@
+/*
+ * 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.time.Duration;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+public class WebDriverWaitFactory {
+
+    private static final Duration DEFAULT_INTERVAL = Duration.ofMillis(500);
+
+    private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(60);
+
+    /**
+     * Create a WebDriverWait instance with default timeout 60s and interval 
100ms.
+     */
+    public static WebDriverWait createWebDriverWait(WebDriver driver) {
+        return createWebDriverWait(driver, DEFAULT_TIMEOUT);
+    }
+
+    public static WebDriverWait createWebDriverWait(WebDriver driver, Duration 
timeout) {
+        return new WebDriverWait(driver, timeout, DEFAULT_INTERVAL);
+    }
+
+    public static WebDriverWait createWebDriverWait(WebDriver driver, Duration 
timeout, Duration interval) {
+        return new WebDriverWait(driver, timeout, interval);
+    }
+
+}
diff --git 
a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
 
b/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
index 09132ebac9..8be58ebb15 100644
--- 
a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
+++ 
b/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java
@@ -28,7 +28,7 @@ import com.baomidou.mybatisplus.annotation.EnumValue;
 public enum ResourceType {
 
     /**
-     * 0 file, 1 udf
+     * 0 file
      */
     FILE(0, "file"),
     ALL(2, "all");
diff --git 
a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-resources.ts
 
b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-resources.ts
index 90ba0a40a7..f9fa187314 100644
--- 
a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-resources.ts
+++ 
b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-resources.ts
@@ -123,6 +123,7 @@ export function useResources(
   return {
     type: 'tree-select',
     field: 'resourceList',
+    class: 'resource-select',
     name: t('project.node.resources'),
     span: span,
     options: resourcesOptions,
diff --git 
a/dolphinscheduler-ui/src/views/projects/task/instance/batch-task.tsx 
b/dolphinscheduler-ui/src/views/projects/task/instance/batch-task.tsx
index 03ca177df2..9e2eed243b 100644
--- a/dolphinscheduler-ui/src/views/projects/task/instance/batch-task.tsx
+++ b/dolphinscheduler-ui/src/views/projects/task/instance/batch-task.tsx
@@ -283,6 +283,7 @@ const BatchTaskInstance = defineComponent({
         <Card title={t('project.task.batch_task')}>
           <NSpace vertical>
             <NDataTable
+              row-class-name='batch-task-instance-items'
               loading={loadingRef}
               columns={this.columns}
               data={this.tableData}


Reply via email to