diff --git a/axonivy-express-test/.classpath b/axonivy-express-test/.classpath
new file mode 100644
index 0000000..106dc80
--- /dev/null
+++ b/axonivy-express-test/.classpath
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/axonivy-express-test/.gitignore b/axonivy-express-test/.gitignore
new file mode 100644
index 0000000..9b0d458
--- /dev/null
+++ b/axonivy-express-test/.gitignore
@@ -0,0 +1,19 @@
+# general
+Thumbs.db
+.DS_Store
+*~
+*.log
+
+# java
+*.class
+hs_err_pid*
+
+# maven
+target/
+lib/mvn-deps/
+
+# ivy
+classes/
+src_dataClasses/
+src_wsproc/
+logs/
diff --git a/axonivy-express-test/.project b/axonivy-express-test/.project
new file mode 100644
index 0000000..f1fb2cc
--- /dev/null
+++ b/axonivy-express-test/.project
@@ -0,0 +1,53 @@
+
+
+ axonivy-express-test
+
+
+
+
+
+ ch.ivyteam.ivy.designer.dataClasses.ui.ivyDataClassBuilder
+
+
+
+
+ ch.ivyteam.ivy.designer.process.ui.ivyWebServiceProcessClassBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.wst.common.project.facet.core.builder
+
+
+
+
+ ch.ivyteam.ivy.dialog.form.build.ivyDialogFormBuilder
+
+
+
+
+ ch.ivyteam.ivy.designer.ide.ivyModelValidationBuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ ch.ivyteam.ivy.project.IvyProjectNature
+ org.eclipse.wst.common.modulecore.ModuleCoreNature
+ org.eclipse.jem.workbench.JavaEMFNature
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+ org.eclipse.jem.beaninfo.BeanInfoNature
+ org.eclipse.wst.common.project.facet.core.nature
+
+
diff --git a/axonivy-express-test/.settings/ch.ivyteam.ivy.designer.prefs b/axonivy-express-test/.settings/ch.ivyteam.ivy.designer.prefs
new file mode 100644
index 0000000..f7bebd8
--- /dev/null
+++ b/axonivy-express-test/.settings/ch.ivyteam.ivy.designer.prefs
@@ -0,0 +1,4 @@
+ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_NAMESPACE=com.axonivy.utils.axonivyexpress.test
+ch.ivyteam.ivy.project.preferences\:PRIMEFACES_VERSION=13
+ch.ivyteam.ivy.project.preferences\:PROJECT_VERSION=120001
+eclipse.preferences.version=1
diff --git a/axonivy-express-test/.settings/org.eclipse.core.resources.prefs b/axonivy-express-test/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..99f26c0
--- /dev/null
+++ b/axonivy-express-test/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/=UTF-8
diff --git a/axonivy-express-test/.settings/org.eclipse.jdt.core.prefs b/axonivy-express-test/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..23fa13b
--- /dev/null
+++ b/axonivy-express-test/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,9 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
+org.eclipse.jdt.core.compiler.compliance=21
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=21
diff --git a/axonivy-express-test/.settings/org.eclipse.wst.common.component b/axonivy-express-test/.settings/org.eclipse.wst.common.component
new file mode 100644
index 0000000..9cea4c0
--- /dev/null
+++ b/axonivy-express-test/.settings/org.eclipse.wst.common.component
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/axonivy-express-test/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml b/axonivy-express-test/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml
new file mode 100644
index 0000000..9b4b9fc
--- /dev/null
+++ b/axonivy-express-test/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/axonivy-express-test/.settings/org.eclipse.wst.common.project.facet.core.xml b/axonivy-express-test/.settings/org.eclipse.wst.common.project.facet.core.xml
new file mode 100644
index 0000000..c64310d
--- /dev/null
+++ b/axonivy-express-test/.settings/org.eclipse.wst.common.project.facet.core.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/axonivy-express-test/.settings/org.eclipse.wst.css.core.prefs b/axonivy-express-test/.settings/org.eclipse.wst.css.core.prefs
new file mode 100644
index 0000000..5ddc6bd
--- /dev/null
+++ b/axonivy-express-test/.settings/org.eclipse.wst.css.core.prefs
@@ -0,0 +1,2 @@
+css-profile/=org.eclipse.wst.css.core.cssprofile.css3
+eclipse.preferences.version=1
diff --git a/axonivy-express-test/config/custom-fields.yaml b/axonivy-express-test/config/custom-fields.yaml
new file mode 100644
index 0000000..fbe76a7
--- /dev/null
+++ b/axonivy-express-test/config/custom-fields.yaml
@@ -0,0 +1,22 @@
+# yaml-language-server: $schema=https://json-schema.axonivy.com/app/12.0.0/custom-fields.json
+#
+# == Custom Fields Information ==
+#
+# You can define here your project custom fields.
+# Have a look at our documentation for more information.
+#
+CustomFields:
+# Tasks:
+# MyTaskCustomField:
+# Label: My task custom field
+# Description: This new task custom field can be used to ...
+# Type: STRING
+# Cases:
+# MyCaseCustomField:
+# Label: My case custom field
+# Description: This new case custom field can be used to ...
+# Type: STRING
+# Starts:
+# MyStartCustomField:
+# Label: My start custom field
+# Description: This new start custom field can be used to ...
diff --git a/axonivy-express-test/config/databases.yaml b/axonivy-express-test/config/databases.yaml
new file mode 100644
index 0000000..657dabd
--- /dev/null
+++ b/axonivy-express-test/config/databases.yaml
@@ -0,0 +1,2 @@
+# yaml-language-server: $schema=https://json-schema.axonivy.com/app/12.0.0/databases.json
+Databases:
diff --git a/axonivy-express-test/config/overrides.any b/axonivy-express-test/config/overrides.any
new file mode 100644
index 0000000..f59ec20
--- /dev/null
+++ b/axonivy-express-test/config/overrides.any
@@ -0,0 +1 @@
+*
\ No newline at end of file
diff --git a/axonivy-express-test/config/persistence.xml b/axonivy-express-test/config/persistence.xml
new file mode 100644
index 0000000..d6b96d7
--- /dev/null
+++ b/axonivy-express-test/config/persistence.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/axonivy-express-test/config/rest-clients.yaml b/axonivy-express-test/config/rest-clients.yaml
new file mode 100644
index 0000000..a277cb1
--- /dev/null
+++ b/axonivy-express-test/config/rest-clients.yaml
@@ -0,0 +1,2 @@
+# yaml-language-server: $schema=https://json-schema.axonivy.com/app/12.0.0/rest-clients.json
+RestClients:
diff --git a/axonivy-express-test/config/roles.xml b/axonivy-express-test/config/roles.xml
new file mode 100644
index 0000000..802642f
--- /dev/null
+++ b/axonivy-express-test/config/roles.xml
@@ -0,0 +1,8 @@
+
+
+ Everybody
+
+ TestRole
+ TestRole
+
+
diff --git a/axonivy-express-test/config/users.xml b/axonivy-express-test/config/users.xml
new file mode 100644
index 0000000..b913358
--- /dev/null
+++ b/axonivy-express-test/config/users.xml
@@ -0,0 +1,13 @@
+
+
+
+ express
+ express
+ Express User
+
+
+ testUser
+ testUser
+ Test User
+
+
diff --git a/axonivy-express-test/config/variables.yaml b/axonivy-express-test/config/variables.yaml
new file mode 100644
index 0000000..e96d3ef
--- /dev/null
+++ b/axonivy-express-test/config/variables.yaml
@@ -0,0 +1,2 @@
+# yaml-language-server: $schema=https://json-schema.axonivy.com/app/12.0.0/variables.json
+Variables:
diff --git a/axonivy-express-test/config/webservice-clients.yaml b/axonivy-express-test/config/webservice-clients.yaml
new file mode 100644
index 0000000..5e614cf
--- /dev/null
+++ b/axonivy-express-test/config/webservice-clients.yaml
@@ -0,0 +1,2 @@
+# yaml-language-server: $schema=https://json-schema.axonivy.com/app/12.0.0/webservice-clients.json
+WebServiceClients:
diff --git a/axonivy-express-test/dataclasses/com/axonivy/utils/axonivyexpress/test/Data.d.json b/axonivy-express-test/dataclasses/com/axonivy/utils/axonivyexpress/test/Data.d.json
new file mode 100644
index 0000000..b0fa67d
--- /dev/null
+++ b/axonivy-express-test/dataclasses/com/axonivy/utils/axonivyexpress/test/Data.d.json
@@ -0,0 +1,6 @@
+{
+ "$schema" : "https://json-schema.axonivy.com/data-class/12.0.0/data-class.json",
+ "simpleName" : "Data",
+ "namespace" : "com.axonivy.utils.axonivyexpress.test",
+ "isBusinessCaseData" : false
+}
\ No newline at end of file
diff --git a/axonivy-express-test/dataclasses/com/axonivy/utils/axonivyexpress/test/HelperData.d.json b/axonivy-express-test/dataclasses/com/axonivy/utils/axonivyexpress/test/HelperData.d.json
new file mode 100644
index 0000000..2344f43
--- /dev/null
+++ b/axonivy-express-test/dataclasses/com/axonivy/utils/axonivyexpress/test/HelperData.d.json
@@ -0,0 +1,6 @@
+{
+ "$schema" : "https://json-schema.axonivy.com/data-class/12.0.0/data-class.json",
+ "simpleName" : "HelperData",
+ "namespace" : "com.axonivy.utils.axonivyexpress.test",
+ "isBusinessCaseData" : false
+}
\ No newline at end of file
diff --git a/axonivy-express-test/pom.xml b/axonivy-express-test/pom.xml
new file mode 100644
index 0000000..e7a8cc2
--- /dev/null
+++ b/axonivy-express-test/pom.xml
@@ -0,0 +1,97 @@
+
+
+ 4.0.0
+ com.axonivy.utils.axonivyexpress
+ axonivy-express-test
+ 12.0.0-SNAPSHOT
+ iar-integration-test
+
+ UTF-8
+
+
+
+ com.axonivy.utils.axonivyexpress
+ axonivy-express
+ ${project.version}
+ iar
+
+
+ com.axonivy.ivy.webtest
+ web-tester
+ 12.0.0
+ test
+
+
+
+
+
+ always
+
+ sonatype
+ https://oss.sonatype.org/content/repositories/snapshots
+
+
+
+
+
+ always
+
+ sonatype
+ https://oss.sonatype.org/content/repositories/snapshots
+
+
+
+ src_test
+
+
+
+ maven-deploy-plugin
+ 3.0.0-M1
+
+ true
+
+
+
+
+
+
+ com.axonivy.ivy.ci
+ project-build-plugin
+ 12.0.0
+ true
+
+ UTF-8
+ false
+
+
+
+ maven-surefire-plugin
+ 3.0.0-M4
+
+
+ default-test
+ test
+
+ true
+
+
+
+ selenium.web.tests
+ integration-test
+
+ test
+
+
+
+
+ true
+ -Dtest.engine.url=${test.engine.url}
+ -Dtest.engine.app=axonivyexpresstest
+
+ ${test.engine.url}
+
+
+
+
+
+
diff --git a/axonivy-express-test/processes/Start Processes/Helper.p.json b/axonivy-express-test/processes/Start Processes/Helper.p.json
new file mode 100644
index 0000000..2df75d0
--- /dev/null
+++ b/axonivy-express-test/processes/Start Processes/Helper.p.json
@@ -0,0 +1,43 @@
+{
+ "$schema" : "https://json-schema.axonivy.com/process/12.0.0/process.json",
+ "id" : "1943EF9CC7A1B392",
+ "config" : {
+ "data" : "com.axonivy.utils.axonivyexpress.test.HelperData"
+ },
+ "elements" : [ {
+ "id" : "f0",
+ "type" : "RequestStart",
+ "name" : "cleanData",
+ "config" : {
+ "signature" : "cleanData"
+ },
+ "visual" : {
+ "at" : { "x" : 96, "y" : 64 }
+ },
+ "connect" : [
+ { "id" : "f2", "to" : "f3" }
+ ]
+ }, {
+ "id" : "f1",
+ "type" : "TaskEnd",
+ "visual" : {
+ "at" : { "x" : 368, "y" : 64 },
+ "labelOffset" : { "x" : 13, "y" : 33 }
+ }
+ }, {
+ "id" : "f3",
+ "type" : "Script",
+ "config" : {
+ "output" : {
+ "code" : "ivy.var.set(\"Portal.Processes.ExpressProcesses\", \"\");"
+ },
+ "sudo" : true
+ },
+ "visual" : {
+ "at" : { "x" : 240, "y" : 64 }
+ },
+ "connect" : [
+ { "id" : "f4", "to" : "f1", "color" : "default" }
+ ]
+ } ]
+}
\ No newline at end of file
diff --git a/axonivy-express-test/resources/testFile/express-test.json b/axonivy-express-test/resources/testFile/express-test.json
new file mode 100644
index 0000000..3d9173a
--- /dev/null
+++ b/axonivy-express-test/resources/testFile/express-test.json
@@ -0,0 +1,125 @@
+{
+ "version": 1,
+ "expressWorkflow": [{
+ "expressProcess": {
+ "id": "f5da82a8ed4f437d95b477e9746d9a75",
+ "processName": "Leave request creation",
+ "processDescription": "Leave request creation description",
+ "processType": "AHWF",
+ "processPermissions": ["#admin"],
+ "processOwner": "#admin",
+ "isUseDefaultUI": false,
+ "processFolder": "e172808e-edd2-4457-8169-f6a48c70bc43",
+ "readyToExecute": true,
+ "processCoOwners": ["#admin"],
+ "isAbleToEdit": true
+ },
+ "expressTaskDefinitions": [{
+ "id": "7f0cac61d68346babbebe1e703c578cb",
+ "processID": "f5da82a8ed4f437d95b477e9746d9a75",
+ "type": "USER_TASK",
+ "responsibles": ["#express"],
+ "subject": "Express Task 1",
+ "description": "",
+ "taskPosition": 1,
+ "untilDays": 1
+ }
+ ],
+ "expressFormElements": [{
+ "id": "b9d14eba257c40b8b3e628d5defa5479",
+ "processID": "f5da82a8ed4f437d95b477e9746d9a75",
+ "elementID": "Test label2019-12-27 11:08:16",
+ "taskPosition": 1,
+ "label": "Test Label",
+ "required": false,
+ "intSetting": 0,
+ "elementType": "InputFieldText",
+ "optionStrs": [""],
+ "elementPosition": "HEADER",
+ "indexInPanel": 0,
+ "counter": 0
+ }
+ ]
+ }, {
+ "expressProcess": {
+ "id": "c3939e7508144f94a03dc71c8054570c",
+ "processName": "Quality Report",
+ "processDescription": "Quality Report description",
+ "processType": "AHWF",
+ "processPermissions": ["#admin"],
+ "processOwner": "#admin",
+ "isUseDefaultUI": false,
+ "processFolder": "e172808e-edd2-4457-8169-f6a48c70bc43",
+ "readyToExecute": true,
+ "processCoOwners": ["#admin"],
+ "isAbleToEdit": true
+ },
+ "expressTaskDefinitions": [{
+ "id": "209f65e4e7344d2ca7d46e0cb39f70b6",
+ "processID": "c3939e7508144f94a03dc71c8054570c",
+ "type": "USER_TASK",
+ "responsibles": ["#express"],
+ "subject": "Express Task 1",
+ "description": "",
+ "taskPosition": 1,
+ "untilDays": 1
+ }
+ ],
+ "expressFormElements": [{
+ "id": "4a23be65f8bf4dbb97f0d126a146bfb0",
+ "processID": "c3939e7508144f94a03dc71c8054570c",
+ "elementID": "Test label2019-12-27 11:08:16",
+ "taskPosition": 1,
+ "label": "Test Label",
+ "required": false,
+ "intSetting": 0,
+ "elementType": "InputFieldText",
+ "optionStrs": [""],
+ "elementPosition": "HEADER",
+ "indexInPanel": 0,
+ "counter": 0
+ }
+ ]
+ }, {
+ "expressProcess": {
+ "id": "c8166c0a53144adc9eaa1df9c2902f04",
+ "processName": "Express Test 1",
+ "processDescription": "Express Test 1",
+ "processType": "AHWF",
+ "processPermissions": ["#admin"],
+ "processOwner": "#admin",
+ "isUseDefaultUI": false,
+ "processFolder": "e172808e-edd2-4457-8169-f6a48c70bc43",
+ "readyToExecute": true,
+ "processCoOwners": ["#admin"],
+ "isAbleToEdit": true
+ },
+ "expressTaskDefinitions": [{
+ "id": "69cd5fe6730842d394e9025b2b58b56d",
+ "processID": "c8166c0a53144adc9eaa1df9c2902f04",
+ "type": "USER_TASK",
+ "responsibles": ["#admin"],
+ "subject": "Express Task 1",
+ "description": "",
+ "taskPosition": 1,
+ "untilDays": 1
+ }
+ ],
+ "expressFormElements": [{
+ "id": "269bbeb00a7143c18753e3dc87a273b8",
+ "processID": "c8166c0a53144adc9eaa1df9c2902f04",
+ "elementID": "Test label2019-12-27 11:08:16",
+ "taskPosition": 1,
+ "label": "Test Label",
+ "required": false,
+ "intSetting": 0,
+ "elementType": "InputFieldText",
+ "optionStrs": [""],
+ "elementPosition": "HEADER",
+ "indexInPanel": 0,
+ "counter": 0
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/axonivy-express-test/resources/testFile/express-wf-request-resource.json b/axonivy-express-test/resources/testFile/express-wf-request-resource.json
new file mode 100644
index 0000000..45fe4e6
--- /dev/null
+++ b/axonivy-express-test/resources/testFile/express-wf-request-resource.json
@@ -0,0 +1,150 @@
+{
+ "version": 1,
+ "expressWorkflow": [{
+ "expressProcess": {
+ "id": "d623ed55776a4d70a94f38383c9ef9e0",
+ "processName": "Request new Resources - Express process",
+ "processDescription": "Express process test ",
+ "processType": "AHWF",
+ "processPermissions": ["#demo", "#david"],
+ "processOwner": "#admin",
+ "isUseDefaultUI": false,
+ "processFolder": "f647d845-6f04-48c8-82df-5db1ec7da1a4",
+ "readyToExecute": true,
+ "processCoOwners": ["Everybody"],
+ "isAbleToEdit": true
+ },
+ "expressTaskDefinitions": [{
+ "id": "ee61f6e8907b456786e343b6e864e4e0",
+ "processID": "d623ed55776a4d70a94f38383c9ef9e0",
+ "type": "USER_TASK",
+ "responsibles": ["#demo", "#david"],
+ "subject": "Promote new resource",
+ "description": "Request new resource - Task 1",
+ "taskPosition": 1,
+ "untilDays": 1
+ }, {
+ "id": "10006b755bb74e6b8636182d064f7150",
+ "processID": "d623ed55776a4d70a94f38383c9ef9e0",
+ "type": "USER_TASK_WITH_EMAIL",
+ "responsibles": ["Everybody"],
+ "subject": "Send email to HR",
+ "description": "Send email to HR for review - Task 2",
+ "taskPosition": 2,
+ "untilDays": 1
+ }, {
+ "id": "5fd26253cb0046e7a3141f1cd3ffbca6",
+ "processID": "d623ed55776a4d70a94f38383c9ef9e0",
+ "type": "USER_TASK",
+ "responsibles": ["#admin"],
+ "subject": "HR review",
+ "description": "Wait HR review - Task 3",
+ "taskPosition": 3,
+ "untilDays": 2
+ }, {
+ "id": "8fdee4750b864260a5878cbac12e1510",
+ "processID": "d623ed55776a4d70a94f38383c9ef9e0",
+ "type": "APPROVAL",
+ "responsibles": ["#admin", "Everybody"],
+ "subject": "Approval Request",
+ "description": "Approval Request",
+ "taskPosition": 4,
+ "untilDays": 1
+ }
+ ],
+ "expressFormElements": [{
+ "id": "ec21202fd937424094f4831dd3caacd8",
+ "processID": "d623ed55776a4d70a94f38383c9ef9e0",
+ "elementID": "Applicant name2020-07-07 09:29:29",
+ "taskPosition": 1,
+ "label": "Applicant name",
+ "required": true,
+ "intSetting": 0,
+ "elementType": "InputFieldText",
+ "optionStrs": [""],
+ "elementPosition": "HEADER",
+ "indexInPanel": 0,
+ "counter": 0
+ }, {
+ "id": "e4537a5f9f4c44d9b99a99868808f7dc",
+ "processID": "d623ed55776a4d70a94f38383c9ef9e0",
+ "elementID": "Email2020-07-07 09:29:43",
+ "taskPosition": 1,
+ "label": "Email",
+ "required": false,
+ "intSetting": 0,
+ "elementType": "InputFieldText",
+ "optionStrs": [""],
+ "elementPosition": "LEFTPANEL",
+ "indexInPanel": 0,
+ "counter": 0
+ }, {
+ "id": "dfef3f165b6049bb91a92534ceb80ea7",
+ "processID": "d623ed55776a4d70a94f38383c9ef9e0",
+ "elementID": "Address2020-07-07 09:29:35",
+ "taskPosition": 1,
+ "label": "Address",
+ "required": true,
+ "intSetting": 0,
+ "elementType": "InputFieldText",
+ "optionStrs": [""],
+ "elementPosition": "RIGHTPANEL",
+ "indexInPanel": 0,
+ "counter": 0
+ }, {
+ "id": "904ee50d831a42438254d382af788c0d",
+ "processID": "d623ed55776a4d70a94f38383c9ef9e0",
+ "elementID": "Welcome2020-07-07 09:30:25",
+ "taskPosition": 2,
+ "label": "Welcome",
+ "required": true,
+ "intSetting": 0,
+ "elementType": "InputFieldText",
+ "optionStrs": [""],
+ "elementPosition": "HEADER",
+ "indexInPanel": 0,
+ "counter": 0
+ }, {
+ "id": "3d69886d2eb0466fb2ee1888f1d35aed",
+ "processID": "d623ed55776a4d70a94f38383c9ef9e0",
+ "elementID": "Start date2020-07-07 09:30:37",
+ "taskPosition": 2,
+ "label": "Start date",
+ "required": true,
+ "intSetting": 0,
+ "elementType": "InputFieldDate",
+ "optionStrs": [""],
+ "elementPosition": "LEFTPANEL",
+ "indexInPanel": 0,
+ "counter": 0
+ }, {
+ "id": "9b911a68e81246bb91216f557c4e9280",
+ "processID": "d623ed55776a4d70a94f38383c9ef9e0",
+ "elementID": "Comment2020-07-07 09:31:05",
+ "taskPosition": 3,
+ "label": "Comment",
+ "required": true,
+ "intSetting": 0,
+ "elementType": "InputFieldText",
+ "optionStrs": [""],
+ "elementPosition": "LEFTPANEL",
+ "indexInPanel": 0,
+ "counter": 0
+ }, {
+ "id": "840d8b5224e541e8b1afc26e4da04fc0",
+ "processID": "d623ed55776a4d70a94f38383c9ef9e0",
+ "elementID": "Approval date2020-07-07 09:31:15",
+ "taskPosition": 3,
+ "label": "Approval date",
+ "required": true,
+ "intSetting": 0,
+ "elementType": "InputFieldDate",
+ "optionStrs": [""],
+ "elementPosition": "RIGHTPANEL",
+ "indexInPanel": 0,
+ "counter": 0
+ }
+ ]
+ }
+ ]
+}
diff --git a/axonivy-express-test/resources/testFile/express-wf-with-disabled-user.json b/axonivy-express-test/resources/testFile/express-wf-with-disabled-user.json
new file mode 100644
index 0000000..837fcba
--- /dev/null
+++ b/axonivy-express-test/resources/testFile/express-wf-with-disabled-user.json
@@ -0,0 +1,67 @@
+{
+ "version": 1,
+ "expressWorkflow": [{
+ "expressProcess": {
+ "id": "1aa339cba44148c8a7215b840b0911b4",
+ "processName": "Test disabled user",
+ "processDescription": "Test disabled user",
+ "processType": "AHWF",
+ "processPermissions": ["#visibility_test_user", "#demo"],
+ "processOwner": "#admin",
+ "isUseDefaultUI": false,
+ "processFolder": "e26cfb17-4fdd-405c-9aed-4dbfdd7eefe1",
+ "readyToExecute": true,
+ "processCoOwners": ["#admin", "#visibility_test_user"],
+ "isAbleToEdit": true
+ },
+ "expressTaskDefinitions": [{
+ "id": "3a059f99084d488a99a8992157589d1c",
+ "processID": "1aa339cba44148c8a7215b840b0911b4",
+ "type": "USER_TASK",
+ "responsibles": ["#visibility_test_user", "#demo"],
+ "subject": "Task 1 - Test disabled user",
+ "description": "",
+ "taskPosition": 1,
+ "untilDays": 1
+ }, {
+ "id": "4491316e17e947be86ce02ed1e91ba9a",
+ "processID": "1aa339cba44148c8a7215b840b0911b4",
+ "type": "USER_TASK",
+ "responsibles": ["#visibility_test_user"],
+ "subject": "Task 2 - Test disabled user",
+ "description": "",
+ "taskPosition": 2,
+ "untilDays": 1
+ }
+ ],
+ "expressFormElements": [{
+ "id": "b8dba0d41b1a4290b9f125a29fa2f4f5",
+ "processID": "1aa339cba44148c8a7215b840b0911b4",
+ "elementID": "Hello2020-06-01 04:45:15",
+ "taskPosition": 1,
+ "label": "Hello",
+ "required": false,
+ "intSetting": 0,
+ "elementType": "InputFieldText",
+ "optionStrs": [""],
+ "elementPosition": "LEFTPANEL",
+ "indexInPanel": 0,
+ "counter": 0
+ }, {
+ "id": "fb18bb0bf7f2400b949f244fea1bbfc0",
+ "processID": "1aa339cba44148c8a7215b840b0911b4",
+ "elementID": "Test 22020-06-01 04:45:27",
+ "taskPosition": 2,
+ "label": "Test 2",
+ "required": false,
+ "intSetting": 0,
+ "elementType": "InputFieldText",
+ "optionStrs": [""],
+ "elementPosition": "HEADER",
+ "indexInPanel": 0,
+ "counter": 0
+ }
+ ]
+ }
+ ]
+}
diff --git a/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/common/ExpressResponsible.java b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/common/ExpressResponsible.java
new file mode 100644
index 0000000..f62ff22
--- /dev/null
+++ b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/common/ExpressResponsible.java
@@ -0,0 +1,34 @@
+package com.axonivy.utils.axonivyexpress.test.common;
+
+public class ExpressResponsible {
+
+ private String responsibleName;
+ private boolean isGroup;
+
+ public ExpressResponsible() {
+ }
+
+
+ public ExpressResponsible(String responsibleName, boolean isGroup) {
+ super();
+ this.responsibleName = responsibleName;
+ this.isGroup = isGroup;
+ }
+
+ public String getResponsibleName() {
+ return responsibleName;
+ }
+
+ public boolean getIsGroup() {
+ return isGroup;
+ }
+
+ public void setResponsibleName(String responsibleName) {
+ this.responsibleName = responsibleName;
+ }
+
+ public void setIsGroup(boolean isGroup) {
+ this.isGroup = isGroup;
+ }
+
+}
\ No newline at end of file
diff --git a/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/common/FileHelper.java b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/common/FileHelper.java
new file mode 100644
index 0000000..2eec726
--- /dev/null
+++ b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/common/FileHelper.java
@@ -0,0 +1,8 @@
+package com.axonivy.utils.axonivyexpress.test.common;
+
+public class FileHelper {
+ public static String getAbsolutePathToTestFile(String fileName) {
+ return System.getProperty("user.dir") + "\\resources\\testFile\\"
+ + fileName;
+ }
+}
diff --git a/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/common/ResizeUtils.java b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/common/ResizeUtils.java
new file mode 100644
index 0000000..e992d76
--- /dev/null
+++ b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/common/ResizeUtils.java
@@ -0,0 +1,12 @@
+package com.axonivy.utils.axonivyexpress.test.common;
+
+import org.openqa.selenium.Dimension;
+
+import com.codeborne.selenide.WebDriverRunner;
+
+public class ResizeUtils {
+
+ public static void resizeBrowser(Dimension size) {
+ WebDriverRunner.getWebDriver().manage().window().setSize(size);
+ }
+}
diff --git a/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/common/WaitHelper.java b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/common/WaitHelper.java
new file mode 100644
index 0000000..67b2f96
--- /dev/null
+++ b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/common/WaitHelper.java
@@ -0,0 +1,97 @@
+package com.axonivy.utils.axonivyexpress.test.common;
+
+import static com.codeborne.selenide.Condition.exist;
+import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.Selenide.$$;
+import static java.time.Duration.ZERO;
+
+import java.time.Duration;
+import java.util.function.Supplier;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import com.codeborne.selenide.CollectionCondition;
+import com.codeborne.selenide.Condition;
+import com.codeborne.selenide.WebDriverRunner;
+
+public final class WaitHelper {
+
+ protected static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(45);
+
+ public static void waitForNavigation(Runnable navigationAcion) {
+ String viewState = $(
+ "input[name='javax.faces.ViewState'][id$='javax.faces.ViewState:1']")
+ .getAttribute("value");
+ navigationAcion.run();
+ $$("input[value='" + viewState + "']").shouldHave(
+ CollectionCondition.sizeLessThanOrEqual(0), DEFAULT_TIMEOUT);
+ }
+
+ public static void waitForIFrameAvailable(WebDriver driver, String frameId) {
+ try {
+ wait(driver)
+ .until(ExpectedConditions.frameToBeAvailableAndSwitchToIt(frameId));
+ } catch (Exception e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+ }
+
+ private static WebDriverWait wait(WebDriver driver) {
+ return new WebDriverWait(driver, DEFAULT_TIMEOUT);
+ }
+
+ public static void assertTrueWithWait(Supplier supplier) {
+ try {
+ wait(WebDriverRunner.getWebDriver()).until(webDriver -> supplier.get());
+ } catch (Exception e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+ }
+
+ public static void waitForPresenceOfElementLocatedInFrame(
+ String cssSelector) {
+ try {
+ wait(WebDriverRunner.getWebDriver()).until(ExpectedConditions
+ .presenceOfElementLocated(By.cssSelector(cssSelector)));
+ } catch (Exception e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+ }
+
+ /**
+ * Some UI are the same before and after AJAX, use this method only in that
+ * scenario. Ask the team if using this
+ */
+ public static void waitForActionComplete(String cssSelector,
+ Runnable action) {
+ ((JavascriptExecutor) WebDriverRunner.getWebDriver())
+ .executeScript("$('" + cssSelector.replace("\\", "\\\\")
+ + "').css('background-color', 'rgb(250, 0, 0)')");
+ $(cssSelector).getCssValue("background-color");
+ $(cssSelector)
+ .shouldHave(Condition.cssValue("background-color", "rgb(250, 0, 0)"));
+ action.run();
+ $(cssSelector).shouldNotHave(
+ Condition.cssValue("background-color", "rgb(250, 0, 0)"));
+ }
+
+ /**
+ * Use this instead of {@code Assertions} methods so that Selenide would take
+ * screenshots if errors. This is a workaround because we cannot
+ * use @ExtendWith({ScreenShooterExtension.class}) with
+ * `WebDriverRunner.getWebDriver().quit();` in `@AfterEach`
+ */
+ public static void assertTrue(boolean condition) {
+ if (!condition) {
+ $("ASSERTION FAILED, CHECK STACK TRACE from BaseTest.assertTrue")
+ .shouldBe(exist, ZERO);
+ }
+ }
+}
\ No newline at end of file
diff --git a/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/page/BasePage.java b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/page/BasePage.java
new file mode 100644
index 0000000..b94d812
--- /dev/null
+++ b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/page/BasePage.java
@@ -0,0 +1,62 @@
+package com.axonivy.utils.axonivyexpress.test.page;
+
+import static com.codeborne.selenide.Condition.appear;
+import static com.codeborne.selenide.Condition.disappear;
+import static com.codeborne.selenide.Selenide.$;
+
+import java.time.Duration;
+
+import org.openqa.selenium.By;
+
+import com.codeborne.selenide.Condition;
+import com.codeborne.selenide.SelenideElement;
+import com.codeborne.selenide.WebElementCondition;
+
+public abstract class BasePage {
+ protected static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(15);
+ public static final String CLASS_PROPERTY = "class";
+
+ protected BasePage() {
+ waitPageLoaded();
+ }
+
+ public void waitPageLoaded() {
+ $(getLoadedLocator()).shouldBe(appear, DEFAULT_TIMEOUT);
+ }
+
+ /**
+ * This abstract method is used to determine identity of a page.
+ *
+ * @return A unique CSS selector for the particular page.
+ */
+ protected abstract String getLoadedLocator();
+
+ protected WebElementCondition getClickableCondition() {
+ return Condition.and("should be clickable", Condition.visible, Condition.exist);
+ }
+
+ public SelenideElement findElementById(String selector) {
+ return $(String.format("[id$='%s']", selector)).shouldBe(appear,
+ DEFAULT_TIMEOUT);
+ }
+
+ public void waitForElementClickableThenClick(SelenideElement element) {
+ element.shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ }
+
+ public void waitForElementClickableThenClick(By by) {
+ $(by).shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ }
+
+ public void waitForElementDisplayed(By element, boolean expected) {
+ if (expected) {
+ $(element).shouldBe(appear, DEFAULT_TIMEOUT);
+ } else {
+ $(element).shouldBe(disappear, DEFAULT_TIMEOUT);
+ }
+ }
+
+ public boolean isElementPresent(By element) {
+ return $(element).is(Condition.visible);
+ }
+}
diff --git a/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/page/ExpressManagementPage.java b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/page/ExpressManagementPage.java
new file mode 100644
index 0000000..430c95a
--- /dev/null
+++ b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/page/ExpressManagementPage.java
@@ -0,0 +1,113 @@
+package com.axonivy.utils.axonivyexpress.test.page;
+
+import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.Selenide.$$;
+
+import org.openqa.selenium.NoSuchElementException;
+
+import com.axonivy.utils.axonivyexpress.test.common.FileHelper;
+import com.codeborne.selenide.CollectionCondition;
+import com.codeborne.selenide.Condition;
+import com.codeborne.selenide.SelenideElement;
+
+public class ExpressManagementPage extends BasePage {
+
+ @Override
+ protected String getLoadedLocator() {
+ return "form[id='express-form']";
+ }
+
+ public void uploadExpressJsonFile(String fileName) {
+ openImportDialog();
+ selectJSONFile(FileHelper.getAbsolutePathToTestFile(fileName));
+ clickOnDeployExpress();
+ closeImportDialog();
+ }
+
+ private void selectJSONFile(String pathToFile) {
+ $("*[id$=':express-process-upload_input']").sendKeys(pathToFile);
+ $$(".ui-fileupload-upload").shouldBe(CollectionCondition.size(1),
+ DEFAULT_TIMEOUT);
+ }
+
+ private void openImportDialog() {
+ $("[id='express-form:import-button']")
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ $("[id='import-express-dialog']").shouldBe(Condition.visible,
+ DEFAULT_TIMEOUT);
+ }
+
+ private void closeImportDialog() {
+ $("[id='import-express-dialog']").find("[id='close-import-express']")
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ $("[id='import-express-dialog']").shouldBe(Condition.disappear,
+ DEFAULT_TIMEOUT);
+ }
+
+
+ public void clickOnDeployExpress() {
+ $("[id='import-express-dialog']").$(".ui-fileupload-upload")
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ $("[id='import-express-dialog']").find("pre.express-import-result")
+ .shouldBe(Condition.appear, DEFAULT_TIMEOUT);
+ }
+
+ public boolean hasExpressProcessWithName(String name) {
+ $("[id='express-form:express-process-table']").shouldBe(Condition.visible,
+ DEFAULT_TIMEOUT);
+ try {
+ SelenideElement processNameElem = $(
+ "[id='express-form:express-process-table']")
+ .findAll("tr.ui-widget-content .express-name").asDynamicIterable()
+ .stream().filter(cell -> cell.getText().contentEquals(name))
+ .findFirst().get();
+ return processNameElem != null;
+ } catch (NoSuchElementException e) {
+ return false;
+ }
+ }
+
+ private SelenideElement openMenuByIndex(int index) {
+ String menuQuery = String.format(
+ "[id='express-form:express-process-table:%d:action-button']", index);
+ $("[id='express-form:express-process-table']")
+ .shouldBe(Condition.visible, DEFAULT_TIMEOUT).find(menuQuery)
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT)
+ .click();
+
+ String menuPanelQuery = String.format(
+ "[id='express-form:express-process-table:%d:action-menu']", index);
+ return $(menuPanelQuery).shouldBe(Condition.appear, DEFAULT_TIMEOUT);
+ }
+
+ public void delete(int index) {
+ openMenuByIndex(index).find("a[id$=':delete']")
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+
+ $("[id='express-form:remove-button']")
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+
+ $("[id='express-form:remove-button']")
+ .shouldBe(Condition.disappear, DEFAULT_TIMEOUT);
+ }
+
+ public int countRows() {
+ $("[id='express-form:express-process-table']").shouldBe(Condition.visible,
+ DEFAULT_TIMEOUT);
+ return $("[id='express-form:express-process-table']")
+ .findAll("tr.ui-widget-content .express-name").size();
+ }
+
+ public WorkflowDefinitionPage edit(int index) {
+ openMenuByIndex(index).find("a[id$=':edit']")
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+
+ return new WorkflowDefinitionPage();
+ }
+
+ public WorkflowDefinitionPage create() {
+ $("[id='express-form:create-button'")
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ return new WorkflowDefinitionPage();
+ }
+}
diff --git a/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/page/ExpressTaskPage.java b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/page/ExpressTaskPage.java
new file mode 100644
index 0000000..205aa96
--- /dev/null
+++ b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/page/ExpressTaskPage.java
@@ -0,0 +1,44 @@
+package com.axonivy.utils.axonivyexpress.test.page;
+
+import static com.codeborne.selenide.Condition.appear;
+import static com.codeborne.selenide.Selenide.$;
+
+import org.openqa.selenium.By;
+
+import com.codeborne.selenide.SelenideElement;
+
+public class ExpressTaskPage extends BasePage {
+ @Override
+ protected String getLoadedLocator() {
+ return "[id='form:dynaform-fieldset']";
+ }
+
+ public SelenideElement findExpressTask() {
+ return $(".js-task-header-container").shouldBe(appear, DEFAULT_TIMEOUT)
+ .$("div[id='task-template-title']");
+ }
+
+ public void waitForExpressFieldSetDisplay() {
+ $(".express-fieldset").shouldBe(appear, DEFAULT_TIMEOUT);
+ }
+
+ public void enterRequiredInputFieldByLabel(String label, String data) {
+ $(String.format("input[data-p-rmsg*='%s']", label))
+ .shouldBe(appear, DEFAULT_TIMEOUT).sendKeys(data);
+ }
+
+ public void finish() {
+ $("[id='form:ok-btn']").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT)
+ .click();
+ }
+
+ public boolean isDocumentTableVisible() {
+ return isElementPresent(
+ By.xpath("//div[contains(@id, 'fileUploadComponent:document-table')]"));
+ }
+
+ public boolean isDocumentUploadButtonVisible() {
+ return isElementPresent(By
+ .xpath("//div[contains(@id, 'fileUploadComponent:document-upload')]"));
+ }
+}
diff --git a/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/page/FormDefinitionPage.java b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/page/FormDefinitionPage.java
new file mode 100644
index 0000000..701edca
--- /dev/null
+++ b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/page/FormDefinitionPage.java
@@ -0,0 +1,306 @@
+package com.axonivy.utils.axonivyexpress.test.page;
+
+import static com.codeborne.selenide.Condition.appear;
+import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.Selenide.$$;
+
+import java.util.Random;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Action;
+import org.openqa.selenium.interactions.Actions;
+
+import com.axonivy.utils.axonivyexpress.test.common.WaitHelper;
+import com.codeborne.selenide.CollectionCondition;
+import com.codeborne.selenide.Condition;
+import com.codeborne.selenide.WebDriverRunner;
+
+public class FormDefinitionPage extends BasePage {
+
+ private static final String LEFT_POSITION = "leftpanel";
+ private static final String RIGHT_POSITION = "rightpanel";
+ private static final String HEADER_POSITION = "header";
+ private static final String FOOTER_POSITION = "footer";
+ private static final String[] POSITIONS = { LEFT_POSITION, HEADER_POSITION,
+ FOOTER_POSITION };
+
+ @Override
+ protected String getLoadedLocator() {
+ return "[id$='defined-task-container']";
+ }
+
+ public ExpressManagementPage clickFinish() {
+ $("[id='finish-button']").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT)
+ .click();
+ return new ExpressManagementPage();
+ }
+
+ public void createUploadComponent(String label) {
+ clickOnFormCreationTabIndex(5);
+ $("input[id$='form:create-tabs:file-upload-label']").sendKeys(label);
+ $("[id='form:create-tabs:add-upload-file-btn']")
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ $("input[id$='form:create-tabs:file-upload-label']")
+ .shouldBe(Condition.empty);
+ }
+
+ public void moveAllElementToDragAndDrogPanel() {
+ int size = $$(
+ By.xpath("//div[@id='form:available-form-elements_content']/ul/li"))
+ .size();
+ long startIndex = size - 1;
+
+ for (long i = startIndex; i >= 0; i--) {
+ String panelSelector = "[id='"
+ + String.format("form:available-form-elements:%d:pnl", i) + "']";
+ $(panelSelector).shouldBe(appear, DEFAULT_TIMEOUT);
+
+ if (i == startIndex) {
+ moveFormElementToPanel(i, RIGHT_POSITION);
+ } else {
+ moveFormElementToPanel(i, getRandomPosition());
+ }
+ $$(panelSelector).shouldBe(CollectionCondition.empty);
+ }
+ }
+
+ public void countElementPrepareToDrag(int size) {
+ $$(By.xpath("//div[@id='form:available-form-elements_content']/ul/li"))
+ .shouldBe(CollectionCondition.size(size), DEFAULT_TIMEOUT);
+ }
+
+ private void moveFormElementToPanel(long index, String position) {
+ // TODO Need to be fixed - Workaround for scroll-bar issue
+ JavascriptExecutor jse = (JavascriptExecutor) WebDriverRunner
+ .getWebDriver();
+ jse.executeScript("window.scrollTo(0, document.body.scrollHeight);");
+
+ var formElementSelector = String
+ .format("[id$='form:available-form-elements:%d:pnl_content']", index);
+ // If elements is FileUpload, move to footer
+ if (formElementIsFileUpload(
+ $(formElementSelector).shouldBe(appear, DEFAULT_TIMEOUT))) {
+ position = FOOTER_POSITION;
+ }
+
+ var panelSelector = String
+ .format("[id='form:selected-form-elements-%s-panel']", position);
+
+ Actions builder = new Actions(WebDriverRunner.getWebDriver());
+ Action moveProcessSequence = builder
+ .dragAndDrop($(formElementSelector), $(panelSelector)).build();
+ moveProcessSequence.perform();
+ }
+
+ private boolean formElementIsFileUpload(WebElement formElement) {
+ WebElement icon = formElement.findElement(By.tagName("img"));
+ return icon.getAttribute("src").contains("FileUpload");
+ }
+
+ private String getRandomPosition() {
+ int idx = new Random().nextInt(POSITIONS.length);
+ return (POSITIONS[idx]);
+ }
+
+ public void nextStep() {
+ $("[id='next-button']").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT)
+ .click();
+ }
+
+ public void waitForEmailEditorDisplayed() {
+ $("[id='form:information-email:email-container']").shouldBe(appear,
+ DEFAULT_TIMEOUT);
+ }
+
+ public WebElement getPageElement() {
+ return $(".portal-layout-container").shouldBe(appear, DEFAULT_TIMEOUT);
+ }
+
+ public void executeWorkflow() {
+ waitForElementClickableThenClick(By.id("execute-button"));
+ waitForElementDisplayed(By.id("form:dynaform-fieldset"), true);
+ }
+
+ public void createTextInputField(String label, int inputFieldTypeIndex,
+ boolean isRequired) {
+ waitForElementClickableThenClick(
+ By.xpath("//*[@id='form:create-tabs']/ul/li[@role='tab'][1]"));
+ waitForElementDisplayed(By.id("form:create-tabs:create-input-field-tab"),
+ true);
+ $(By.id("form:create-tabs:input-field-label")).sendKeys(label);
+ chooseInputFieldType(inputFieldTypeIndex);
+ if (isRequired) {
+ waitForElementClickableThenClick(
+ By.cssSelector("div[id='form:create-tabs:input-field-required']"));
+ }
+ waitForElementClickableThenClick(
+ By.id("form:create-tabs:add-input-text-btn"));
+ }
+
+ private void chooseInputFieldType(int inputTypeIndex) {
+ waitForElementClickableThenClick(
+ By.id("form:create-tabs:input-field-type_label"));
+ waitForElementDisplayed(By.id("form:create-tabs:input-field-type_panel"),
+ true);
+ waitForElementClickableThenClick(By.id(
+ String.format("form:create-tabs:input-field-type_%d", inputTypeIndex)));
+ }
+
+ public void finishWorkflow() {
+ waitForElementClickableThenClick(By.id("finish-button"));
+ new ExpressManagementPage();
+ }
+
+ public ExpressManagementPage save() {
+ waitForElementClickableThenClick(By.id("save-button"));
+ return new ExpressManagementPage();
+ }
+
+ public ExpressManagementPage cancel() {
+ waitForElementClickableThenClick(By.id("cancel-button"));
+ waitForElementDisplayed(By.id("yes-button"), true);
+ waitForElementClickableThenClick(By.id("yes-button"));
+ return new ExpressManagementPage();
+ }
+
+ public void createRadioButtonField(String label, int numberOfOption) {
+ clickOnFormCreationTabIndex(4);
+ waitForElementDisplayed(By.id("form:create-tabs:one-radio-label"), true);
+ addRadioOptions(numberOfOption);
+ $("input[id$='form:create-tabs:one-radio-label']").sendKeys(label);
+ waitForElementClickableThenClick(By.id("form:create-tabs:add-radio-btn"));
+ $("input[id$='form:create-tabs:one-radio-label']")
+ .shouldBe(Condition.empty);
+ }
+
+ private void addRadioOptions(int numberOfOptions) {
+ for (int i = 1; i <= numberOfOptions; i++) {
+ waitForElementClickableThenClick(
+ By.id("form:create-tabs:one-radio-options:add-radio-option-btn"));
+ $(By.xpath(String.format(
+ "//*[@id='form:create-tabs:one-radio-options_data']/tr[%d]/td/input",
+ i))).sendKeys("Radio " + i);
+ }
+ }
+
+ public void createCheckboxField(String label, int numberOfSelection) {
+ clickOnFormCreationTabIndex(3);
+ waitForElementDisplayed(By.id("form:create-tabs:many-checkbox-options"),
+ true);
+ $("input[id='form:create-tabs:many-checkbox-label']").sendKeys(label);
+ addCheckboxOptions(numberOfSelection);
+ waitForElementClickableThenClick(
+ By.id("form:create-tabs:add-checkbox-btn"));
+ $("input[id='form:create-tabs:many-checkbox-label']")
+ .shouldBe(Condition.empty);
+ }
+
+ private void addCheckboxOptions(int numberOfSelection) {
+ for (int i = 1; i <= numberOfSelection; i++) {
+ waitForElementClickableThenClick(By.id(
+ "form:create-tabs:many-checkbox-options:add-checkbox-option-btn"));
+ $(By.xpath(String.format(
+ "//*[@id='form:create-tabs:many-checkbox-options_data']/tr[%d]/td/input",
+ i))).sendKeys("Option " + i);
+ }
+ }
+
+ public void createTextAreaField(String label, boolean isRequired) {
+ clickOnFormCreationTabIndex(2);
+ waitForElementDisplayed(By.id("form:create-tabs:create-input-area-tab"),
+ true);
+ $(By.id("form:create-tabs:input-area-label")).sendKeys(label);
+ if (isRequired) {
+ waitForElementClickableThenClick(
+ By.cssSelector("div[id='form:create-tabs:input-area-required']"));
+ }
+ waitForElementClickableThenClick(
+ By.id("form:create-tabs:add-text-area-btn"));
+ $(By.id("form:create-tabs:input-area-label")).shouldBe(Condition.empty);
+ }
+
+ public void createCheckboxFieldWithDataProvider(String label) {
+ fillDataForCheckboxProvider(label);
+ waitForElementClickableThenClick(
+ By.id("form:create-tabs:add-checkbox-btn"));
+ $(By.id("form:create-tabs:many-checkbox-label")).shouldBe(Condition.empty);
+ }
+
+ public void fillDataForCheckboxProvider(String label) {
+ clickOnFormCreationTabIndex(3);
+ waitForElementDisplayed(By.id("form:create-tabs:create-many-checkbox-tab"),
+ true);
+ waitForElementClickableThenClick(
+ By.id("form:create-tabs:DataProvider_label"));
+ waitForElementClickableThenClick(
+ By.xpath("//*[@data-label='TestDataProviderForPortalExpress']"));
+ waitForElementDisplayed(By.cssSelector("div[id$='value-checkbox-label']"),
+ false);
+ waitForElementDisplayed(By.id("form:create-tabs:many-checkbox-label"),
+ true);
+ $(By.id("form:create-tabs:many-checkbox-label")).sendKeys(label);
+ }
+
+ public int countNumberOfElementsInPreviewDialog() {
+ waitForElementClickableThenClick(By.id("form:show-preview-button"));
+ waitForElementDisplayed(By.id("form:preview-dialog"), true);
+ WebElement previewDialog = findElementById("form:preview-dialog");
+ int numberOfInput = previewDialog
+ .findElements(By.xpath("//table[@id='form:dyna-form']//input")).size();
+ int numberOfTextArea = previewDialog
+ .findElements(By.xpath("//table[@id='form:dyna-form']//textarea"))
+ .size();
+ int numberOfUploadFile = previewDialog
+ .findElements(By
+ .xpath("//div[contains(@id,'fileUploadComponent:document-table')]"))
+ .size();
+ return numberOfInput + numberOfTextArea + numberOfUploadFile;
+ }
+
+ public int countNumberOfSteps() {
+ return $("div[id='defined-task-container']").findAll("button").size();
+ }
+
+ public void inputMailRecipient(String content) {
+ findElementById("form:information-email:email-recipients")
+ .sendKeys(content);
+ }
+
+ public void inputMailSubject(String content) {
+ findElementById("form:information-email:email-subject").sendKeys(content);
+ }
+
+ public void inputMailContent(String content) {
+ ((JavascriptExecutor) WebDriverRunner.getWebDriver()).executeScript(
+ "document.querySelector(\"input[name='form:information-email:email-content_input'\").value='"
+ + content + "';");
+ }
+
+ public void executeWorkflowAndWaitForUserTaskWithEmailDisplay() {
+ waitForElementClickableThenClick(By.id("execute-button"));
+ waitForElementDisplayed(By.id("task-form:task-view:dyna-form-fieldset"),
+ true);
+ }
+
+ private void clickOnFormCreationTabIndex(int tabIndex) {
+ WaitHelper.waitForActionComplete("#form\\:create-tabs ul",
+ () -> waitForElementClickableThenClick(
+ By.xpath("//*[@id='form:create-tabs']/ul/li[@role='tab']["
+ + tabIndex + "]")));
+ }
+
+ public void switchToCheckBoxTab() {
+ waitForElementDisplayed(
+ By.xpath("//*[@id='form:create-tabs']/ul/li[@role='tab'][3]"), true);
+ $(By.xpath("//*[@id='form:create-tabs']/ul/li[@role='tab'][3]"))
+ .shouldBe(getClickableCondition()).click();
+ WaitHelper.assertTrueWithWait(() -> {
+ var checkboxTab = $(
+ By.xpath("//*[@id='form:create-tabs']/ul/li[@role='tab'][3]"));
+ return checkboxTab.getAttribute(CLASS_PROPERTY)
+ .contains("ui-state-active");
+ });
+ }
+}
diff --git a/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/page/WorkflowDefinitionPage.java b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/page/WorkflowDefinitionPage.java
new file mode 100644
index 0000000..c71e509
--- /dev/null
+++ b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/page/WorkflowDefinitionPage.java
@@ -0,0 +1,199 @@
+package com.axonivy.utils.axonivyexpress.test.page;
+
+import static com.codeborne.selenide.Condition.appear;
+import static com.codeborne.selenide.Condition.disappear;
+import static com.codeborne.selenide.Selenide.$;
+
+import java.util.List;
+
+import org.openqa.selenium.Dimension;
+import org.openqa.selenium.WebElement;
+
+import com.axonivy.utils.axonivyexpress.test.common.ExpressResponsible;
+import com.axonivy.utils.axonivyexpress.test.common.ResizeUtils;
+import com.codeborne.selenide.Condition;
+
+public class WorkflowDefinitionPage extends BasePage {
+
+ @Override
+ protected String getLoadedLocator() {
+ return "fieldset[id='form:process-setting-fieldset']";
+ }
+
+ public void changeName(String newName) {
+ $("[id='form:process-name']").shouldBe(Condition.appear, DEFAULT_TIMEOUT)
+ .clear();
+ $("[id='form:process-name']").shouldBe(Condition.appear, DEFAULT_TIMEOUT)
+ .sendKeys(newName);
+ }
+
+ public void save() {
+ $("[id='form:save']").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT)
+ .click();
+ }
+
+ public FormDefinitionPage proceedToFormDefinitionPage() {
+ save();
+ return new FormDefinitionPage();
+ }
+
+ public void createDefaultTask(int taskIndex, String taskName, List responsibles) {
+ if (taskName != null) {
+ $("[id='" + String.format("form:defined-tasks-list:%d:default-task-name", taskIndex) + "']")
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).sendKeys(taskName);
+ }
+ $("[id='" + String.format("form:defined-tasks-list:%d:default-task-responsible-link", taskIndex) + "']")
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ addResponsible(responsibles);
+ }
+
+ private void addResponsible(List responsibles) {
+ $("[id='choose-responsible-dialog']").shouldBe(appear, DEFAULT_TIMEOUT);
+ for (ExpressResponsible responsible : responsibles) {
+ chooseResponsible(responsible.getResponsibleName(), responsible.getIsGroup());
+ }
+ $("[id='assignee-selection-form:save-assignee-button']").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ $("[id='choose-responsible-dialog']").shouldBe(disappear, DEFAULT_TIMEOUT);
+ }
+
+ private void chooseResponsible(String responsible, boolean isGroup) {
+ if (isGroup) {
+ $("label[for='assignee-selection-form:assignee-type:1']").shouldBe(appear, DEFAULT_TIMEOUT).click();
+ $("[id='assignee-selection-form:role-selection-component:role-selection_input']")
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).clear();
+ $("[id='assignee-selection-form:role-selection-component:role-selection_input']").sendKeys(responsible);
+ $("[id='assignee-selection-form:role-selection-component:role-selection_panel']").shouldBe(appear,
+ DEFAULT_TIMEOUT);
+ $("span[id='assignee-selection-form:role-selection-component:role-selection_panel'] .gravatar")
+ .shouldBe(appear, DEFAULT_TIMEOUT).click();
+ } else {
+ $("[id='assignee-selection-form:user-selection-component:user-selection_input']")
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).clear();
+ $("[id='assignee-selection-form:user-selection-component:user-selection_input']").sendKeys(responsible);
+ $("[id='assignee-selection-form:user-selection-component:user-selection_panel']").shouldBe(appear,
+ DEFAULT_TIMEOUT);
+ $("span[id='assignee-selection-form:user-selection-component:user-selection_panel'] .gravatar")
+ .shouldBe(appear, DEFAULT_TIMEOUT).click();
+ }
+
+ $("[id='assignee-selection-form:add-assignee-button']").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ $(".assignee-name-col").shouldBe(appear, DEFAULT_TIMEOUT);
+ }
+
+ public void addNewTask(int currentTaskIndex) {
+ $("[id='" + String.format("form:defined-tasks-list:%d:add-step-button", currentTaskIndex) + "']")
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ $("[id='" + String.format("form:defined-tasks-list:%d:process-flow-field", currentTaskIndex + 1) + "']")
+ .shouldBe(appear, DEFAULT_TIMEOUT);
+ }
+
+ public void clickSave() {
+ $("[id='form:save']").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ }
+
+ public FormDefinitionPage goToFormDefinition() {
+ goToFormDefinitionDefaultResolution();
+ ResizeUtils.resizeBrowser(new Dimension(2560, 1440));
+ return new FormDefinitionPage();
+ }
+
+ public FormDefinitionPage goToFormDefinitionDefaultResolution() {
+ clickSave();
+ return new FormDefinitionPage();
+ }
+
+ public WebElement getDefineTaskStep(int stepIndex) {
+ String defineTaskStepId = String.format(":defined-tasks-list:%s:process-flow-field", stepIndex);
+ return $("[id$='" + defineTaskStepId + "']").shouldBe(appear, DEFAULT_TIMEOUT);
+ }
+
+ public void fillProcessProperties(boolean isAdhocWF, boolean isCreateOwn, String processName,
+ String processDescription) {
+ if (isAdhocWF) {
+ $("div[id='form:process-type']").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ $("span[id='form:process-type-group']").$(".switch-active").shouldBe(Condition.text("One time"), DEFAULT_TIMEOUT);
+ }
+
+ if (!isCreateOwn) {
+ $("div[id='form:user-interface-type']").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ agreeToDeleteAllDefineTasks();
+
+ }
+ $("[id='form:process-name']").shouldBe(appear, DEFAULT_TIMEOUT).sendKeys(processName);
+ $("[id='form:process-description']").shouldBe(appear, DEFAULT_TIMEOUT).sendKeys(processDescription);
+ }
+
+ private void agreeToDeleteAllDefineTasks() {
+ $("[id='delete-all-defined-tasks-warning']").shouldBe(appear, DEFAULT_TIMEOUT);
+ $("[id='delete-all-defined-tasks-warning-ok']").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ $("[id='delete-all-defined-tasks-warning']").shouldBe(disappear, DEFAULT_TIMEOUT);
+ }
+
+ public void createTask(int taskIndex, int typeIndex, String taskName, String taskDescription,
+ List responsibles) {
+ final String TASK_NAME_FORMAT = "input[id$='%d:task-name']";
+ final int INFORMATION_EMAIL_INDEX = 2;
+
+ chooseTaskType(taskIndex, typeIndex);
+ if (typeIndex != INFORMATION_EMAIL_INDEX) {
+ $("[id='" + String.format("form:defined-tasks-list:%d:task-responsible-link", taskIndex) + "']")
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ $("[id='choose-responsible-dialog']").shouldBe(appear, DEFAULT_TIMEOUT);
+ addResponsible(responsibles);
+
+ $(String.format(TASK_NAME_FORMAT, taskIndex)).shouldBe(appear, DEFAULT_TIMEOUT).sendKeys(taskName);
+ $(String.format("input[id$='%d:task-description']", taskIndex)).shouldBe(appear, DEFAULT_TIMEOUT)
+ .sendKeys(taskDescription);
+ }
+ }
+
+ private void chooseTaskType(int taskIndex, int typeIndex) {
+ if (typeIndex == 0) {
+ // If the selected task type is already task type? ignore click on the drop-down
+ return;
+ }
+
+ final String TASK_TYPE_FORMAT = "li[id$=':%d:task-type_%d']";
+ final String TASK_TYPE_LABEL_FORMAT = "[id$=':%d:task-type_label']";
+
+ $(String.format(TASK_TYPE_LABEL_FORMAT, taskIndex)).shouldBe(appear, DEFAULT_TIMEOUT)
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ $(String.format("[id$=':%d:task-type_panel']", taskIndex)).shouldBe(appear, DEFAULT_TIMEOUT);
+ $(String.format(TASK_TYPE_FORMAT, taskIndex, typeIndex)).shouldBe(appear, DEFAULT_TIMEOUT)
+ .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click();
+ }
+
+ public void waitUntilExpressProcessDisplay() {
+ $("[id='form:process-setting-fieldset']").shouldBe(appear, DEFAULT_TIMEOUT);
+ }
+
+ public String getProcessName() {
+ return findElementById("form:process-name").getAttribute("value");
+ }
+
+ public String getProcessOwnerNames() {
+ return findElementById("form:process-owner-link").getText();
+ }
+
+ public String getAbleToStartNames() {
+ return getResponsiblesOfTask(0);
+ }
+
+ public String getResponsiblesOfTask(int taskIndex) {
+ return findElementById(String.format("form:defined-tasks-list:%d:task-responsible-link", taskIndex)).getText();
+ }
+
+ public void executeDirectly() {
+ waitForElementClickableThenClick($("[id$='form:save']"));
+ }
+
+ public ExpressManagementPage cancelWorkflowDefinition() {
+ waitForElementClickableThenClick($("[id='form:cancel-workflow-button']"));
+ return new ExpressManagementPage();
+ }
+
+ public void fillProcessOwners(List responsibles) {
+ waitForElementClickableThenClick($("[id='form:process-owner-link']"));
+ addResponsible(responsibles);
+ }
+}
diff --git a/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/webtest/BaseTest.java b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/webtest/BaseTest.java
new file mode 100644
index 0000000..874cb16
--- /dev/null
+++ b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/webtest/BaseTest.java
@@ -0,0 +1,28 @@
+package com.axonivy.utils.axonivyexpress.test.webtest;
+
+import static com.codeborne.selenide.Selenide.open;
+
+import org.junit.jupiter.api.AfterEach;
+
+import com.axonivy.ivy.webtest.engine.EngineUrl;
+import com.axonivy.ivy.webtest.engine.WebAppFixture;
+import com.axonivy.utils.axonivyexpress.test.page.ExpressManagementPage;
+
+public abstract class BaseTest {
+
+ @AfterEach
+ public void clean() {
+ open(EngineUrl.createProcessUrl(
+ "/axonivy-express-test/1943EF9CC7A1B392/cleanData.ivp"));
+ }
+
+ protected ExpressManagementPage navigateToExpressManagementPage() {
+ open(EngineUrl.createProcessUrl(
+ "/axonivy-express/17326FC2F133FBEA/expressManagement.ivp"));
+ return new ExpressManagementPage();
+ }
+
+ protected void loginAsDeveloper(WebAppFixture fixture) {
+ fixture.login("Developer", "Developer");
+ }
+}
diff --git a/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/webtest/ExpressManagementTest.java b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/webtest/ExpressManagementTest.java
new file mode 100644
index 0000000..12cee48
--- /dev/null
+++ b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/webtest/ExpressManagementTest.java
@@ -0,0 +1,46 @@
+package com.axonivy.utils.axonivyexpress.test.webtest;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.axonivy.ivy.webtest.IvyWebTest;
+import com.axonivy.ivy.webtest.engine.WebAppFixture;
+import com.axonivy.utils.axonivyexpress.test.page.ExpressManagementPage;
+import com.axonivy.utils.axonivyexpress.test.page.WorkflowDefinitionPage;
+
+@IvyWebTest
+public class ExpressManagementTest extends BaseTest {
+
+ @BeforeEach
+ public void setup(WebAppFixture fixture) {
+ fixture.login("express", "express");
+ navigateToExpressManagementPage();
+ ExpressManagementPage page = new ExpressManagementPage();
+ page.uploadExpressJsonFile("express-test.json");
+ }
+
+ @Test
+ public void testImportProcess() {
+ ExpressManagementPage page = new ExpressManagementPage();
+ assertTrue(page.hasExpressProcessWithName("Express Test 1"));
+ }
+
+ @Test
+ public void testDeleteProcess() {
+ ExpressManagementPage page = new ExpressManagementPage();
+ page.delete(0);
+ assertEquals(2, page.countRows());
+ }
+
+ @Test
+ public void testEditProcess() {
+ ExpressManagementPage page = new ExpressManagementPage();
+ WorkflowDefinitionPage definitionPage = page.edit(1);
+ definitionPage.changeName("Test Name");
+ page = definitionPage.proceedToFormDefinitionPage().clickFinish();
+ assertTrue(page.hasExpressProcessWithName("Test Name"));
+ }
+}
diff --git a/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/webtest/ExpressProcessTest.java b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/webtest/ExpressProcessTest.java
new file mode 100644
index 0000000..a625a8d
--- /dev/null
+++ b/axonivy-express-test/src_test/com/axonivy/utils/axonivyexpress/test/webtest/ExpressProcessTest.java
@@ -0,0 +1,101 @@
+package com.axonivy.utils.axonivyexpress.test.webtest;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.axonivy.ivy.webtest.IvyWebTest;
+import com.axonivy.ivy.webtest.engine.WebAppFixture;
+import com.axonivy.utils.axonivyexpress.test.common.ExpressResponsible;
+import com.axonivy.utils.axonivyexpress.test.page.ExpressManagementPage;
+import com.axonivy.utils.axonivyexpress.test.page.ExpressTaskPage;
+import com.axonivy.utils.axonivyexpress.test.page.FormDefinitionPage;
+import com.axonivy.utils.axonivyexpress.test.page.WorkflowDefinitionPage;
+
+@IvyWebTest
+public class ExpressProcessTest extends BaseTest {
+ private static final int USER_TASK_INDEX = 0;
+ private static final int APPROVAL_INDEX = 3;
+
+ private static final int INPUT_TEXT_TYPE_INDEX = 0;
+ private static final int INPUT_NUMBER_TYPE_INDEX = 1;
+
+ @BeforeEach
+ public void setup(WebAppFixture fixture) {
+ fixture.login("express", "express");
+ navigateToExpressManagementPage();
+ }
+
+ @Test
+ public void testOneTimeWorkflow() {
+ ExpressManagementPage page = new ExpressManagementPage();
+ WorkflowDefinitionPage expressProcessPage = page.create();
+ expressProcessPage.fillProcessProperties(true, true, "Test approval",
+ "Test description");
+ FormDefinitionPage formDefinition = configureExpressProcessWhenMultiApproval(
+ expressProcessPage);
+ formDefinition.executeWorkflow();
+ executeExpressProcessWhenMultiApproval();
+ page = navigateToExpressManagementPage();
+ assertEquals(0, page.countRows());
+ }
+
+ @Test
+ public void testCreateThenExecuteWorkflow() {
+ ExpressManagementPage page = new ExpressManagementPage();
+ WorkflowDefinitionPage expressProcessPage = page.create();
+ expressProcessPage.fillProcessProperties(false, true, "Test approval",
+ "Test description");
+ FormDefinitionPage formDefinition = configureExpressProcessWhenMultiApproval(
+ expressProcessPage);
+ formDefinition.finishWorkflow();
+ page = new ExpressManagementPage();
+ assertTrue(page.hasExpressProcessWithName("Test approval"));
+ }
+
+ public ExpressResponsible setExpressResponsible(String userName,
+ boolean isGroup) {
+ ExpressResponsible user = new ExpressResponsible();
+ user.setResponsibleName(userName);
+ user.setIsGroup(isGroup);
+ return user;
+ }
+
+ private FormDefinitionPage configureExpressProcessWhenMultiApproval(
+ WorkflowDefinitionPage expressProcessPage) {
+ ExpressResponsible responsible1 = setExpressResponsible(
+ "express", false);
+ ExpressResponsible responsible2 = setExpressResponsible(
+ "testUser", false);
+
+ expressProcessPage.createTask(0, USER_TASK_INDEX, "Task 1",
+ "Task 1 description", Arrays.asList(responsible1, responsible2));
+
+ expressProcessPage.addNewTask(0);
+ expressProcessPage.createTask(1, APPROVAL_INDEX, "Task 2",
+ "Task 2 description", Arrays.asList(responsible2));
+
+ expressProcessPage.addNewTask(1);
+ expressProcessPage.createTask(2, APPROVAL_INDEX, "Task 3",
+ "Task 3 description", Arrays.asList(responsible1, responsible2));
+ FormDefinitionPage formDefinition = expressProcessPage
+ .goToFormDefinition();
+ formDefinition.createTextInputField("Input Text 1", INPUT_TEXT_TYPE_INDEX,
+ false);
+ formDefinition.createTextInputField("Input Number 2",
+ INPUT_NUMBER_TYPE_INDEX, false);
+ formDefinition.countElementPrepareToDrag(2);
+ formDefinition.moveAllElementToDragAndDrogPanel();
+ formDefinition.countElementPrepareToDrag(0);
+ return formDefinition;
+ }
+
+ private void executeExpressProcessWhenMultiApproval() {
+ ExpressTaskPage expressTaskPage = new ExpressTaskPage();
+ expressTaskPage.finish();
+ }
+}