Repository: primefaces/primefaces
Pull request #7603 (2021-07-17 22:40:22)

PF-7364
As part of my analysis on FileUpload, I created some integration tests.

christophs78: Thanks for your work! This is something we had been missing before. Happy you filled this gap!
wal-jan: Made it JDK 8 compatible. But the tests fail in MyFaces/Chrome combination. I'll investigate...
melloware: Great work.  This was definitely a huge gap in our tests!
christophs78: I checked out your PR and did some testing on my machine. (Windows 10 + up2date Firefox and Chrome)
The issue is not related to MyFaces or Mojarra. It´s related to Chrome.
Chrome fails with MyFaces and Mojarra.
Firefox work wiht MyFaces and Mojarra.
 
does not matter:
![image](https://user-images.githubusercontent.com/10461942/126077250-34406c33-402a-4f7f-91df-a7d74b3df535.png)

does matter:
![image](https://user-images.githubusercontent.com/10461942/126077265-37d2f788-6461-46c1-84bc-57ec9686f45d.png)

org.openqa.selenium.InvalidArgumentException: invalid argument
  (Session info: chrome=91.0.4472.124)
  (Driver info: chromedriver=91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs/branch-heads/4472@{#1462}),platform=Windows NT 10.0.19043 x86_64) (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 0 milliseconds
Build info: version: '3.141.59', revision: 'e82be7d358', time: '2018-11-14T08:17:03'
System info: host: 'DESKTOP-6I7IEPJ', ip: '192.168.0.7', os.name: 'Windows 10', os.arch: 'amd64', os.version: '10.0', java.version: '1.8.0_292'
Driver info: org.openqa.selenium.chrome.ChromeDriver
Capabilities {acceptInsecureCerts: false, acceptSslCerts: false, applicationCacheEnabled: false, browserConnectionEnabled: false, browserName: chrome, chrome: {chromedriverVersion: 91.0.4472.101 (af52a90bf870..., userDataDir: C:\Users\chris\AppData\Loca...}, cssSelectorsEnabled: true, databaseEnabled: false, goog:chromeOptions: {debuggerAddress: localhost:60806}, handlesAlerts: true, hasTouchScreen: false, javascriptEnabled: true, locationContextEnabled: true, mobileEmulationEnabled: false, nativeEvents: true, networkConnectionEnabled: false, pageLoadStrategy: normal, platform: WINDOWS, platformName: WINDOWS, proxy: Proxy(), rotatable: false, setWindowRect: true, strictFileInteractability: false, takesHeapSnapshot: true, takesScreenshot: true, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unexpectedAlertBehaviour: ignore, unhandledPromptBehavior: ignore, version: 91.0.4472.124, webStorageEnabled: true, webauthn:extension:largeBlob: true, webauthn:virtualAuthenticators: true}

...
	at org.primefaces.selenium.internal.proxy.ElementLocatorInterceptor.intercept(ElementLocatorInterceptor.java:69)
	at org.primefaces.selenium.component.FileUpload$ByteBuddy$iu5H0Rcc.click(Unknown Source)
	at org.primefaces.selenium.component.base.ComponentUtils.sendKeys(ComponentUtils.java:101)
	at org.primefaces.selenium.component.FileUpload.setValue(FileUpload.java:72)
	at org.primefaces.integrationtests.fileupload.FileUpload000Test.testBasicSingleUpload(FileUpload000Test.java:51)

My current personal feeling is, that Chrome may not like sendKey on a file-input. Or we need to do this in a other way on Chrome.
christophs78: Some more information for the case it´s not self-explaining:

- Our regular CI runs integration-tests only for Java 11 + Chrome + Myfaces. (https://github.com/primefaces/primefaces/blob/master/.github/workflows/build.yml#L38)
- Additional we run once a night our Integrationtests against a extensive matrix. (https://github.com/primefaces/primefaces/blob/master/.github/workflows/nightly.yml)

wal-jan: @christophs78 thanks for your analysis! I'll try to fix it.
(Selenium does not support the OS dependent file selection dialogs out of the box. But I found multiple hints at Stackoverflow just to use sendKeys on input type=file. At least for Firefox and Chrome.)
christophs78: Simply doing
getWebDriver().findElement(By.id("form:fileupload")).sendKeys("C:\\Users\\chris\\IdeaProjects\\wal_jan_primefaces\\primefaces-integration-tests\\target\\test-classes\\org\\primefaces\\integrationtests\\fileupload\\file1.csv") 
works on my machine with Chrome.

Following line seem to be bad for file-input on Chrome:
![image](https://user-images.githubusercontent.com/10461942/126078406-a68470df-64a3-490a-977b-9e591f1c3d27.png)

Maybe we should do sendKeys straight forward for this and not use ComponentUtils.
wal-jan: In principal the tests do now work with Chrome and Mojarra. But now there is timimg issue. Looks like `waitAdvancedUntilAllFilesAreUploaded` does not wait long enough in all cases. I'll have a look on that...

Patch:
diff --git a/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/FileUpload.java b/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/FileUpload.java
new file mode 100644
index 0000000000..bda89463eb
--- /dev/null
+++ b/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/FileUpload.java
@@ -0,0 +1,145 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.selenium.component;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+import org.primefaces.selenium.PrimeExpectedConditions;
+import org.primefaces.selenium.PrimeSelenium;
+import org.primefaces.selenium.component.base.AbstractInputComponent;
+import org.primefaces.selenium.internal.ConfigProvider;
+import org.primefaces.selenium.spi.WebDriverProvider;
+
+/**
+ * Component wrapper for the PrimeFaces {@code p:fileUpload}.
+ */
+public abstract class FileUpload extends AbstractInputComponent {
+
+    @Override
+    public WebElement getInput() {
+        // in case of mode=simple skinSimple=false the input element is this element
+        boolean isInputFile = "input".equals(getTagName()) && "file".equals(getAttribute("type"));
+        return isInputFile ? this : findElement(By.id(getId() + "_input"));
+    }
+
+    /**
+     * Returns the input´s value.
+     * @return the file name
+     */
+    public String getValue() {
+        return getInput().getAttribute("value");
+    }
+
+    /**
+     * Sets the input's value.
+     * This should be the files absolute path.
+     * @param value the file name to set
+     */
+    public void setValue(Serializable value) {
+        if (getInput() != this && !PrimeSelenium.isChrome()) {
+            // input file cannot be cleared if skinSimple=false or in Chrome
+            getInput().clear();
+        }
+        // ComponentUtils.sendKeys will break tests in Chrome
+        getInput().sendKeys(value.toString());
+    }
+
+    /**
+     * Sets the input's value from given files.
+     * @param values the file name(s) to set
+     */
+    public void setValue(File... values) {
+        // several references in the WEB state that (at least) Firefox and Chrome accept
+        // multiple filenames separated by a new line character
+        String paths = Arrays.stream(values)
+                .map(f -> f.getAbsolutePath())
+                .collect(Collectors.joining("\n"));
+        setValue(paths);
+    }
+
+    /**
+     * Gets the Upload button of the widget.
+     * The button is only rendered in advanced mode.
+     * @return the widget's upload button
+     */
+    public WebElement getAdvancedUploadButton() {
+        return findElement(By.cssSelector("button.ui-fileupload-upload"));
+    }
+
+    /**
+     * Gets the Cancel button of the widget.
+     * The button is only rendered in advanced mode.
+     * @return the widget's cancel button
+     */
+    public WebElement getAdvancedCancelButton() {
+        return findElement(By.cssSelector("button.ui-fileupload-cancel"));
+    }
+
+    /**
+     * Waits until all selected files are uploaded.
+     * This only works in advanced mode.
+     * @param additionalElementToWaitFor element to wait for stable GUI
+     */
+    public void waitAdvancedUntilAllFilesAreUploaded(WebElement additionalElementToWaitFor) {
+        ConfigProvider config = ConfigProvider.getInstance();
+        WebDriver driver = WebDriverProvider.get();
+
+        // todo: Should there be a separate configuration entry for file upload?
+        WebDriverWait wait = new WebDriverWait(driver, config.getAjaxTimeout(), 100);
+        // all files are uploaded if widgets file array is empty
+        String jsScript = "if (" + getWidgetByIdScript() + ".files.length === 0) return true;";
+        wait.until(ExpectedConditions.jsReturnsValue(jsScript));
+        PrimeSelenium.guardAjax(this);
+        PrimeSelenium.wait(200);
+        PrimeSelenium.waitGui().until(PrimeExpectedConditions.visibleAndAnimationComplete(additionalElementToWaitFor));
+    }
+
+    /**
+     * Gets the value displayed by the widget.
+     *
+     * @return the widget's value
+     */
+    public String getWidgetValue() {
+        return findElement(By.className("ui-fileupload-filename")).getText();
+    }
+
+    /**
+     * Gets the values displayed by the widget.
+     *
+     * @return the widget's value
+     */
+    public List<String> getWidgetValues() {
+        return findElements(By.className("ui-fileupload-filename")).stream()
+                .map(e -> e.getText()).collect(Collectors.toList());
+    }
+}
diff --git a/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/Tree.java b/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/Tree.java
new file mode 100644
index 0000000000..19f07888fb
--- /dev/null
+++ b/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/Tree.java
@@ -0,0 +1,53 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.selenium.component;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.openqa.selenium.By;
+import org.primefaces.selenium.component.base.AbstractComponent;
+import org.primefaces.selenium.component.model.tree.TreeNode;
+
+/**
+ * Component wrapper for the PrimeFaces {@code p:tree}.
+ */
+public abstract class Tree extends AbstractComponent {
+
+    public static final String CONTAINER_CLASS = "ui-tree";
+    public static final String ROOT_NODES_CLASS = "ui-tree-container";
+    public static final String TREE_NODE_CLASS = "ui-treenode";
+    public static final String PARENT_NODE_CLASS = "ui-treenode-parent";
+    public static final String CHILDREN_NODES_CLASS = "ui-treenode-children";
+    public static final String NODE_CONTENT_CLASS = "ui-treenode-content";
+    public static final String LEAF_NODE_CLASS = "ui-treenode-leaf";
+
+    public static final String CHILD_SELECTOR = ".ui-tree-container>li";
+
+    public List<TreeNode> getChildren() {
+        return findElements(By.cssSelector(CHILD_SELECTOR)).stream().map(e -> new TreeNode(e, CHILD_SELECTOR, this))
+                    .collect(Collectors.toList());
+    }
+
+}
diff --git a/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/model/tree/TreeNode.java b/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/model/tree/TreeNode.java
new file mode 100644
index 0000000000..2b5afd2764
--- /dev/null
+++ b/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/model/tree/TreeNode.java
@@ -0,0 +1,106 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.selenium.component.model.tree;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.primefaces.selenium.PrimeSelenium;
+import org.primefaces.selenium.component.Tree;
+
+public class TreeNode {
+
+    private WebElement webElement;
+    private TreeNode parent;
+    private Tree tree;
+    private String selector;
+    private String childSelector;
+
+    public TreeNode(WebElement webElement, String selector, Tree tree) {
+        this.webElement = webElement;
+        this.selector = selector;
+        this.childSelector = selector + ">.ui-treenode-children>li";
+        this.tree = tree;
+    }
+
+    public TreeNode(WebElement webElement, String selector, TreeNode parent) {
+        this.webElement = webElement;
+        this.selector = selector;
+        this.childSelector = selector + ">.ui-treenode-children>li";
+        this.parent = parent;
+        this.tree = parent.getTree();
+    }
+
+    public WebElement getWebElement() {
+        return webElement;
+    }
+
+    public void setWebElement(WebElement webElement) {
+        this.webElement = webElement;
+    }
+
+    public Tree getTree() {
+        return tree;
+    }
+
+    public TreeNode getParent() {
+        return parent;
+    }
+
+    public String getSelector() {
+        return selector;
+    }
+
+    public void setSelector(String selector) {
+        this.selector = selector;
+    }
+
+    public void toggle() {
+        PrimeSelenium.guardAjax(getTreeToggler()).click();
+    }
+
+    public void select() {
+        PrimeSelenium.guardAjax(getLabel()).click();
+    }
+
+    public WebElement getTreeToggler() {
+        return webElement.findElement(By.cssSelector(".ui-treenode-content .ui-tree-toggler"));
+    }
+
+    public String getLabelText() {
+        return getLabel().getText();
+    }
+
+    public WebElement getLabel() {
+        return webElement.findElement(By.cssSelector(".ui-treenode-content .ui-treenode-label"));
+    }
+
+    public List<TreeNode> getChildren() {
+        return webElement.findElements(By.cssSelector(childSelector)).stream().map(e -> new TreeNode(e, childSelector, this))
+                    .collect(Collectors.toList());
+    }
+
+}
diff --git a/primefaces/src/main/java/org/primefaces/component/api/UIColumn.java b/primefaces/src/main/java/org/primefaces/component/api/UIColumn.java
index 444e305c9e..dc3b423403 100644
--- a/primefaces/src/main/java/org/primefaces/component/api/UIColumn.java
+++ b/primefaces/src/main/java/org/primefaces/component/api/UIColumn.java
@@ -25,16 +25,90 @@ package org.primefaces.component.api;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.el.ELContext;
 
 import javax.el.MethodExpression;
 import javax.el.ValueExpression;
+import javax.faces.FacesException;
 import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 
 import org.primefaces.component.celleditor.CellEditor;
+import org.primefaces.util.LangUtils;
 
 public interface UIColumn {
 
+    /**
+     * Currently there 3 expression type for attributes like filterBy, sortBy:
+     * 1) #{car[column.property]}
+     *    this is a valid expression and resolves to a real value
+     * 2) #{column.property}
+     *    this is a invalid expressions and would need modification before calling the VE
+     *    currently not supported anymore
+     * 3) #{testView.tableModel.getValue(row, column.keyId)}
+     *    this is a valid expression and resolves to a real value
+     */
+    Pattern VALUE_EXPRESSION_VALID_SYNTAX = Pattern.compile("^#\\{\\w+\\[(.+)]}$");
+
+    /**
+     * This method is used to extract the optional "field" of SortMeta and FieldMeta,
+     * based on the defined "sortBy" or "filterBy" expression.
+     *
+     * #{car.name} -> name
+     * #{car[column.property]} -> name
+     * #{testView.tableModel.getValue(row, column.keyId)} -> null
+     *
+     * @param context the {@link FacesContext}
+     * @param expression the {@link ValueExpression} like "filterBy" or "sortBy"
+     * @param dynamic if its a dynamic column
+     * @return the "field" name, can be null
+     */
+    static String extractFieldFromValueExpression(FacesContext context, ValueExpression expression, boolean dynamic) {
+        String expressionString = expression.getExpressionString();
+
+        if (dynamic) {
+            ELContext elContext = context.getELContext();
+
+            Matcher matcher = VALUE_EXPRESSION_VALID_SYNTAX.matcher(expressionString);
+            if (matcher.find()) {
+                expressionString = matcher.group(1);
+                expression = context.getApplication().getExpressionFactory()
+                        .createValueExpression(elContext, "#{" + expressionString  + "}", String.class);
+                return (String) expression.getValue(elContext);
+            }
+        }
+        else {
+            if (expressionString.startsWith("#{")) {
+                expressionString = expressionString.substring(2, expressionString.indexOf('}')); //Remove #{}
+                return expressionString.substring(expressionString.indexOf('.') + 1); //Remove var
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * This method is used to build a valid {@link ValueExpression} for sorting or filtering, if the "field"
+     * shortcut attribute is defined.
+     *
+     * field="name" -> #{car.name}
+     *
+     * @param context the {@link FacesContext}
+     * @param var the "var" attribute of the parent table
+     * @param field the "field" attribute of the column
+     * @return the {@link ValueExpression}
+     */
+    static ValueExpression createValueExpressionFromField(FacesContext context, String var, String field) {
+        if (LangUtils.isValueBlank(var) || LangUtils.isValueBlank(field)) {
+            throw new FacesException("Table 'var' and Column 'field' attributes must be non null.");
+        }
+
+        return context.getApplication().getExpressionFactory()
+                .createValueExpression(context.getELContext(), "#{" + var + "." + field + "}", Object.class);
+    }
+
     ValueExpression getValueExpression(String property);
 
     String getContainerClientId(FacesContext context);
diff --git a/primefaces/src/main/java/org/primefaces/component/api/UITable.java b/primefaces/src/main/java/org/primefaces/component/api/UITable.java
index 05a4443843..73b438e69e 100644
--- a/primefaces/src/main/java/org/primefaces/component/api/UITable.java
+++ b/primefaces/src/main/java/org/primefaces/component/api/UITable.java
@@ -27,11 +27,8 @@ import java.lang.reflect.Array;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Function;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
-import javax.el.ELContext;
 import javax.el.MethodExpression;
 import javax.el.ValueExpression;
 import javax.faces.FacesException;
@@ -52,63 +49,11 @@ import org.primefaces.util.LangUtils;
 
 public interface UITable<T extends UITableState> extends ColumnAware, MultiViewStateAware<T> {
 
-    /**
-     * Backward compatibility for column properties (e.g sortBy, filterBy)
-     * using old syntax #{car[column.property]}) instead of #{column.property}
-     */
-    Pattern OLD_SYNTAX_COLUMN_PROPERTY_REGEX = Pattern.compile("^#\\{\\w+\\[(.+)]}$");
-
     /**
      * ID of the global filter component
      */
     String GLOBAL_FILTER_COMPONENT_ID = "globalFilter";
 
-    static String resolveStaticField(ValueExpression expression) {
-        if (expression != null) {
-            String expressionString = expression.getExpressionString();
-            if (expressionString.startsWith("#{")) {
-                expressionString = expressionString.substring(2, expressionString.indexOf('}')); //Remove #{}
-                return expressionString.substring(expressionString.indexOf('.') + 1); //Remove var
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Get bean's property value from a value expression.
-     * Support old syntax (e.g #{car[column.property]}) instead of #{column.property}
-     * @param context
-     * @param exprVE
-     * @return
-     */
-    static String resolveDynamicField(FacesContext context, ValueExpression exprVE) {
-        if (exprVE == null) {
-            return null;
-        }
-
-        ELContext elContext = context.getELContext();
-        String exprStr = exprVE.getExpressionString();
-
-        Matcher matcher = OLD_SYNTAX_COLUMN_PROPERTY_REGEX.matcher(exprStr );
-        if (matcher.find()) {
-            exprStr = matcher.group(1);
-            exprVE = context.getApplication().getExpressionFactory()
-                    .createValueExpression(elContext, "#{" + exprStr  + "}", String.class);
-        }
-
-        return (String) exprVE.getValue(elContext);
-    }
-
-    static ValueExpression createValueExprFromVarField(FacesContext context, String var, String field) {
-        if (LangUtils.isValueBlank(var) || LangUtils.isValueBlank(field)) {
-            throw new FacesException("Table 'var' and Column 'field' attributes must be non null.");
-        }
-
-        return context.getApplication().getExpressionFactory()
-                .createValueExpression(context.getELContext(), "#{" + var + "." + field + "}", Object.class);
-    }
-
     String getVar();
 
     String getClientId(FacesContext context);
@@ -191,13 +136,13 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
 
         for (FilterMeta userFM : filterByTmp) {
             FilterMeta intlFM = intlFilterBy.values().stream()
-                    .filter(o -> o.getField().equals(userFM.getField()))
+                    .filter(o -> Objects.equals(o.getField(), userFM.getField()) || Objects.equals(o.getColumnKey(), userFM.getColumnKey()))
                     .findAny()
                     .orElseThrow(() -> new FacesException("No column with field '" + userFM.getField() + "' has been found"));
 
             ValueExpression filterByVE = userFM.getFilterBy();
             if (filterByVE == null) {
-                filterByVE = createValueExprFromVarField(context, getVar(), userFM.getField());
+                filterByVE = UIColumn.createValueExpressionFromField(context, getVar(), userFM.getField());
             }
 
             intlFM.setFilterValue(userFM.getFilterValue());
@@ -380,13 +325,13 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
 
         for (SortMeta userSM : sortBy) {
             SortMeta intlSM = intlSortBy.values().stream()
-                    .filter(o -> o.getField().equals(userSM.getField()))
+                    .filter(o -> Objects.equals(o.getField(), userSM.getField()) || Objects.equals(o.getColumnKey(), userSM.getColumnKey()))
                     .findAny()
                     .orElseThrow(() -> new FacesException("No column with field '" + userSM.getField() + "' has been found"));
 
             ValueExpression sortByVE = userSM.getSortBy();
             if (sortByVE == null) {
-                sortByVE = createValueExprFromVarField(context, getVar(), userSM.getField());
+                sortByVE = UIColumn.createValueExpressionFromField(context, getVar(), userSM.getField());
             }
 
             intlSM.setPriority(userSM.getPriority());
@@ -578,7 +523,7 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
     }
 
     default String getConvertedFieldValue(FacesContext context, UIColumn column) {
-        Object value = createValueExprFromVarField(context, getVar(), column.getField()).getValue(context.getELContext());
+        Object value = UIColumn.createValueExpressionFromField(context, getVar(), column.getField()).getValue(context.getELContext());
         UIComponent component = column instanceof DynamicColumn ? ((DynamicColumn) column).getColumns() : (UIComponent) column;
         return ComponentUtils.getConvertedAsString(context, component, value);
     }
diff --git a/primefaces/src/main/java/org/primefaces/component/headerrow/HeaderRowRenderer.java b/primefaces/src/main/java/org/primefaces/component/headerrow/HeaderRowRenderer.java
index 98833277e9..47df66a8dd 100644
--- a/primefaces/src/main/java/org/primefaces/component/headerrow/HeaderRowRenderer.java
+++ b/primefaces/src/main/java/org/primefaces/component/headerrow/HeaderRowRenderer.java
@@ -36,7 +36,6 @@ import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import javax.faces.context.ResponseWriter;
 import java.io.IOException;
-import org.primefaces.component.api.UITable;
 
 public class HeaderRowRenderer extends CoreRenderer {
 
@@ -146,7 +145,7 @@ public class HeaderRowRenderer extends CoreRenderer {
 
         String field = row.getField();
         Object value = LangUtils.isNotBlank(field)
-                ? UITable.createValueExprFromVarField(context, table.getVar(), field).getValue(context.getELContext())
+                ? UIColumn.createValueExpressionFromField(context, table.getVar(), field).getValue(context.getELContext())
                 : row.getGroupBy();
         writer.writeText(value, null);
 
diff --git a/primefaces/src/main/java/org/primefaces/model/FilterMeta.java b/primefaces/src/main/java/org/primefaces/model/FilterMeta.java
index 5bbbfb03a0..e899055237 100644
--- a/primefaces/src/main/java/org/primefaces/model/FilterMeta.java
+++ b/primefaces/src/main/java/org/primefaces/model/FilterMeta.java
@@ -25,7 +25,6 @@ package org.primefaces.model;
 
 import org.primefaces.component.api.DynamicColumn;
 import org.primefaces.component.api.UIColumn;
-import org.primefaces.component.api.UITable;
 import org.primefaces.component.column.ColumnBase;
 import org.primefaces.component.datatable.feature.FilterFeature;
 import org.primefaces.model.filter.FilterConstraint;
@@ -88,13 +87,18 @@ public class FilterMeta implements Serializable {
             return null;
         }
 
-        String field = resolveFilterField(context, column);
-        if (field == null) {
+        String field = column.getField();
+        ValueExpression filterByVE = column.getValueExpression(ColumnBase.PropertyKeys.filterBy.name());
+        if (field == null && filterByVE == null) {
             return null;
         }
 
-        ValueExpression filterByVE = column.getValueExpression(ColumnBase.PropertyKeys.filterBy.name());
-        filterByVE = filterByVE != null ? filterByVE : UITable.createValueExprFromVarField(context, var, field);
+        if (field == null) {
+            field = UIColumn.extractFieldFromValueExpression(context, filterByVE, column.isDynamic());
+        }
+        else if (filterByVE == null) {
+            filterByVE = UIColumn.createValueExpressionFromField(context, var, field);
+        }
 
         MatchMode matchMode = MatchMode.of(column.getFilterMatchMode());
         FilterConstraint constraint = FilterFeature.FILTER_CONSTRAINTS.get(matchMode);
@@ -179,26 +183,6 @@ public class FilterMeta implements Serializable {
         return filterBy.getValue(elContext);
     }
 
-    static String resolveFilterField(FacesContext context, UIColumn column) {
-        ValueExpression columnFilterByVE = column.getValueExpression(ColumnBase.PropertyKeys.filterBy.toString());
-
-        if (column.isDynamic()) {
-            String field = column.getField();
-            if (field == null) {
-                Object filterBy = column.getFilterBy();
-                field = (filterBy == null) ? UITable.resolveDynamicField(context, columnFilterByVE) : filterBy.toString();
-            }
-            return field;
-        }
-        else {
-            String field = column.getField();
-            if (field == null) {
-                field = (columnFilterByVE == null) ? (String) column.getFilterBy() : UITable.resolveStaticField(columnFilterByVE);
-            }
-            return field;
-        }
-    }
-
     public static Builder builder() {
         return new Builder();
     }
diff --git a/primefaces/src/main/java/org/primefaces/model/SortMeta.java b/primefaces/src/main/java/org/primefaces/model/SortMeta.java
index 4c1fd11e3d..7afc08f45d 100644
--- a/primefaces/src/main/java/org/primefaces/model/SortMeta.java
+++ b/primefaces/src/main/java/org/primefaces/model/SortMeta.java
@@ -33,7 +33,6 @@ import javax.faces.context.FacesContext;
 
 import org.primefaces.component.api.DynamicColumn;
 import org.primefaces.component.api.UIColumn;
-import org.primefaces.component.api.UITable;
 import org.primefaces.component.column.ColumnBase;
 import org.primefaces.component.headerrow.HeaderRow;
 import org.primefaces.component.headerrow.HeaderRowBase;
@@ -82,19 +81,24 @@ public class SortMeta implements Serializable, Comparable<SortMeta> {
             return null;
         }
 
-        String field = resolveSortField(context, column);
-        if (field == null) {
+        String field = column.getField();
+        ValueExpression sortByVE = column.getValueExpression(ColumnBase.PropertyKeys.sortBy.name());
+        if (field == null && sortByVE == null) {
             return null;
         }
 
+        if (field == null) {
+            field = UIColumn.extractFieldFromValueExpression(context, sortByVE, column.isDynamic());
+        }
+        else if (sortByVE == null) {
+            sortByVE = UIColumn.createValueExpressionFromField(context, var, field);
+        }
+
         SortOrder order = SortOrder.of(column.getSortOrder());
         if (order.isUnsorted() && column.isGroupRow()) {
             order = SortOrder.ASCENDING;
         }
 
-        ValueExpression sortByVE = column.getValueExpression(ColumnBase.PropertyKeys.sortBy.name());
-        sortByVE = sortByVE != null ? sortByVE : UITable.createValueExprFromVarField(context, var, field);
-
         return new SortMeta(column.getColumnKey(),
                             field,
                             order,
@@ -114,7 +118,7 @@ public class SortMeta implements Serializable, Comparable<SortMeta> {
             throw new FacesException("HeaderRow must have 'groupBy' or 'field' attribute value");
         }
 
-        groupByVE = groupByVE != null ? groupByVE : UITable.createValueExprFromVarField(context, var, headerRow.getField());
+        groupByVE = groupByVE != null ? groupByVE : UIColumn.createValueExpressionFromField(context, var, headerRow.getField());
 
         return new SortMeta(headerRow.getClientId(context),
                             headerRow.getField(),
@@ -220,26 +224,6 @@ public class SortMeta implements Serializable, Comparable<SortMeta> {
                 '}';
     }
 
-    static String resolveSortField(FacesContext context, UIColumn column) {
-        ValueExpression columnSortByVE = column.getValueExpression(ColumnBase.PropertyKeys.sortBy.toString());
-
-        if (column.isDynamic()) {
-            String field = column.getField();
-            if (field == null) {
-                Object sortByProperty = column.getSortBy();
-                field = (sortByProperty == null) ? UITable.resolveDynamicField(context, columnSortByVE) : sortByProperty.toString();
-            }
-            return field;
-        }
-        else {
-            String field = column.getField();
-            if (field == null) {
-                field = (columnSortByVE == null) ? (String) column.getSortBy() : UITable.resolveStaticField(columnSortByVE);
-            }
-            return field;
-        }
-    }
-
     public static Builder builder() {
         return new Builder();
     }


Test patch:
diff --git a/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/datatable/DataTable001Dynamic.java b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/datatable/DataTable001Dynamic.java
new file mode 100644
index 0000000000..2f9fc85408
--- /dev/null
+++ b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/datatable/DataTable001Dynamic.java
@@ -0,0 +1,103 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.datatable;
+
+import lombok.Data;
+import org.primefaces.component.datatable.DataTable;
+
+import javax.annotation.PostConstruct;
+import javax.faces.context.FacesContext;
+import javax.faces.view.ViewScoped;
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+@Named
+@ViewScoped
+@Data
+public class DataTable001Dynamic implements Serializable {
+
+    private static final long serialVersionUID = 8906909722127350633L;
+
+    private List<ProgrammingLanguage> progLanguages;
+    private List<ProgrammingLanguage> filteredProgLanguages;
+    private boolean globalFilterOnly;
+
+    private List<ColumnModel> columns = new ArrayList<ColumnModel>();
+
+    @Inject
+    private ProgrammingLanguageService service;
+
+    @PostConstruct
+    public void init() {
+        progLanguages = service.getLangs();
+        globalFilterOnly = false;
+        populateColumns();
+    }
+
+    public void populateColumns() {
+        columns.add(new ColumnModel("ID", "id", "startsWith"));
+        columns.add(new ColumnModel("Type", "type", "startsWith"));
+        columns.add(new ColumnModel("Name", "name", "contains"));
+        columns.add(new ColumnModel("First appeared", "firstAppeared", "gte"));
+    }
+
+    public void resetTable() {
+        DataTable dataTable = (DataTable) FacesContext.getCurrentInstance().getViewRoot().findComponent("form:datatable");
+        dataTable.reset();
+
+        progLanguages = service.getLangs(); //progLanguages may have been sorted from DataTable
+    }
+
+    public void toggleGlobalFilter() {
+        setGlobalFilterOnly(!isGlobalFilterOnly());
+    }
+
+    public static class ColumnModel implements Serializable {
+        private String header;
+        private String property;
+        private String filterMatchMode;
+
+        public ColumnModel(String header, String property, String filterMatchMode) {
+            this.header = header;
+            this.property = property;
+            this.filterMatchMode = filterMatchMode;
+        }
+        public String getHeader() {
+            return header;
+        }
+        public String getProperty() {
+            return property;
+        }
+        public String getFilterMatchMode() {
+            return filterMatchMode;
+        }
+    }
+
+    public Object justReturn(Object value) {
+        return value;
+    }
+}
diff --git a/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/fileupload/FileUploadHandler.java b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/fileupload/FileUploadHandler.java
new file mode 100644
index 0000000000..b62cf62128
--- /dev/null
+++ b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/fileupload/FileUploadHandler.java
@@ -0,0 +1,73 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.fileupload;
+
+import java.io.Serializable;
+import javax.enterprise.context.RequestScoped;
+import javax.inject.Inject;
+
+import javax.inject.Named;
+
+import lombok.Data;
+import org.primefaces.event.FileUploadEvent;
+import org.primefaces.event.FilesUploadEvent;
+import org.primefaces.model.file.UploadedFile;
+import org.primefaces.model.file.UploadedFiles;
+
+@Named
+@RequestScoped
+@Data
+public class FileUploadHandler implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @Inject
+    private FileUploadView fileUploadView;
+
+    private UploadedFile file;
+    private UploadedFiles files;
+
+    public void uploadFile() {
+        if (file != null) {
+            fileUploadView.doFileUpload(file);
+        }
+    }
+
+    public void uploadFiles() {
+        if (files != null) {
+            for (UploadedFile f : files.getFiles()) {
+                fileUploadView.doFileUpload(f);
+            }
+        }
+    }
+
+    public void handleFileUpload(FileUploadEvent event) {
+        fileUploadView.doFileUpload(event.getFile());
+    }
+
+    public void handleFilesUpload(FilesUploadEvent event) {
+        for (UploadedFile f : event.getFiles().getFiles()) {
+            fileUploadView.doFileUpload(f);
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/fileupload/FileUploadView.java b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/fileupload/FileUploadView.java
new file mode 100644
index 0000000000..e9330ab1b4
--- /dev/null
+++ b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/fileupload/FileUploadView.java
@@ -0,0 +1,78 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.fileupload;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.faces.view.ViewScoped;
+import javax.inject.Named;
+
+import lombok.Data;
+import org.primefaces.model.file.UploadedFile;
+
+@Named
+@ViewScoped
+@Data
+public class FileUploadView implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private List<UploadFile> uploadedFiles = Collections.synchronizedList(new ArrayList<>());
+
+    // Note: this method may be called in parallel threads
+    public void doFileUpload(UploadedFile uf) {
+        UploadFile file = new UploadFile(uf);
+        try {
+            readFile(uf);
+        }
+        catch (Exception ex) {
+            file.setErrorMessage(ex.getMessage());
+        }
+        uploadedFiles.add(file);
+    }
+
+    private void readFile(UploadedFile uf) throws IOException {
+        byte[] buf = new byte[(int) uf.getSize() + 1];
+        int len = uf.getInputStream().read(buf);
+        if (len != (int) uf.getSize()) {
+            throw new IllegalStateException("unexpected file size " + uf.getSize() + " <> " + len);
+        }
+    }
+
+    @Data
+    public static final class UploadFile implements Serializable {
+        private static final long serialVersionUID = 1L;
+        private final String fileName;
+        private final int size;
+        private String errorMessage;
+
+        public UploadFile(UploadedFile file) {
+            this.fileName = file.getFileName();
+            this.size = (int) file.getSize();
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/tree/Tree001.java b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/tree/Tree001.java
new file mode 100644
index 0000000000..ac0236e4f8
--- /dev/null
+++ b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/tree/Tree001.java
@@ -0,0 +1,63 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.tree;
+
+import java.io.Serializable;
+
+import javax.annotation.PostConstruct;
+import javax.faces.view.ViewScoped;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.primefaces.event.NodeCollapseEvent;
+import org.primefaces.event.NodeExpandEvent;
+import org.primefaces.integrationtests.general.utilities.TestUtils;
+import org.primefaces.model.TreeNode;
+
+import lombok.Data;
+
+@Named
+@ViewScoped
+@Data
+public class Tree001 implements Serializable {
+
+    @Inject
+    private TreeNodeService treeNodeService;
+
+    private TreeNode<String> root;
+
+    @PostConstruct
+    public void init() {
+        root = treeNodeService.createNodes();
+    }
+
+    public void onNodeExpand(NodeExpandEvent event) {
+        TestUtils.addMessage("Expanded", event.getTreeNode().toString());
+    }
+
+    public void onNodeCollapse(NodeCollapseEvent event) {
+        TestUtils.addMessage("Collapsed", event.getTreeNode().toString());
+    }
+
+}
diff --git a/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/tree/Tree002.java b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/tree/Tree002.java
new file mode 100644
index 0000000000..05ae4fdbfc
--- /dev/null
+++ b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/tree/Tree002.java
@@ -0,0 +1,73 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.tree;
+
+import java.io.Serializable;
+
+import javax.annotation.PostConstruct;
+import javax.faces.view.ViewScoped;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.primefaces.event.NodeSelectEvent;
+import org.primefaces.event.NodeUnselectEvent;
+import org.primefaces.integrationtests.general.utilities.TestUtils;
+import org.primefaces.model.TreeNode;
+
+import lombok.Data;
+
+@Named
+@ViewScoped
+@Data
+public class Tree002 implements Serializable {
+
+    @Inject
+    private TreeNodeService treeNodeService;
+
+    private TreeNode<String> root;
+    private TreeNode<String> selectedNode;
+
+    @PostConstruct
+    public void init() {
+        root = treeNodeService.createNodes();
+    }
+
+    public void onNodeSelect(NodeSelectEvent event) {
+        TestUtils.addMessage("Selected", event.getTreeNode().toString());
+    }
+
+    public void onNodeUnselect(NodeUnselectEvent event) {
+        TestUtils.addMessage("Unselected", event.getTreeNode().toString());
+    }
+
+    public void showSelectedNode() {
+        if (selectedNode != null) {
+            TestUtils.addMessage("Selected node", selectedNode.toString());
+        }
+        else {
+            TestUtils.addMessage("No node selected!", "");
+        }
+    }
+
+}
diff --git a/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/tree/TreeNodeService.java b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/tree/TreeNodeService.java
new file mode 100644
index 0000000000..326fc16681
--- /dev/null
+++ b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/tree/TreeNodeService.java
@@ -0,0 +1,65 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.tree;
+
+import org.primefaces.model.DefaultTreeNode;
+import org.primefaces.model.TreeNode;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Named;
+
+@Named
+@ApplicationScoped
+public class TreeNodeService {
+
+    public TreeNode<String> createNodes() {
+        TreeNode<String> root = new DefaultTreeNode<>("Files", null);
+        TreeNode<String> node0 = new DefaultTreeNode<>("Documents", root);
+        TreeNode<String> node1 = new DefaultTreeNode<>("Events", root);
+        TreeNode<String> node2 = new DefaultTreeNode<>("Movies", root);
+
+        TreeNode<String> node00 = new DefaultTreeNode<>("Work", node0);
+        TreeNode<String> node01 = new DefaultTreeNode<>("Home", node0);
+
+        node00.getChildren().add(new DefaultTreeNode<>("Expenses.doc"));
+        node00.getChildren().add(new DefaultTreeNode<>("Resume.doc"));
+        node01.getChildren().add(new DefaultTreeNode<>("Invoices.txt"));
+
+        TreeNode<String> node10 = new DefaultTreeNode<>("Meeting", node1);
+        TreeNode<String> node11 = new DefaultTreeNode<>("Product Launch", node1);
+        TreeNode<String> node12 = new DefaultTreeNode<>("Report Review", node1);
+
+        TreeNode<String> node20 = new DefaultTreeNode<>("Al Pacino", node2);
+        TreeNode<String> node21 = new DefaultTreeNode<>("Robert De Niro", node2);
+
+        node20.getChildren().add(new DefaultTreeNode<>("Scarface"));
+        node20.getChildren().add(new DefaultTreeNode<>("Serpico"));
+
+        node21.getChildren().add(new DefaultTreeNode<>("Goodfellas"));
+        node21.getChildren().add(new DefaultTreeNode<>("Untouchables"));
+
+        return root;
+    }
+
+}
diff --git a/primefaces-integration-tests/src/main/webapp/datatable/dataTable001Dynamic.xhtml b/primefaces-integration-tests/src/main/webapp/datatable/dataTable001Dynamic.xhtml
new file mode 100644
index 0000000000..dd7178be24
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/datatable/dataTable001Dynamic.xhtml
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form">
+            <p:dataTable id="datatable" widgetVar="wgtTable" value="#{dataTable001Dynamic.progLanguages}" var="lang" paginator="true" rows="3"
+                         filteredValue="#{dataTable001Dynamic.filteredProgLanguages}"
+                         filterEvent="enter"
+                         globalFilterOnly="#{dataTable001Dynamic.globalFilterOnly}"
+                         paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
+                         rowsPerPageTemplate="2,3,5,10">
+                <f:facet name="header">
+                    <div class="p-d-flex p-ai-center p-jc-between">
+                        <span>Programming Languages</span>
+                        <p:inputText id="globalFilter" onchange="PF('wgtTable').filter()" placeholder="Enter keyword"/>
+                    </div>
+                </f:facet>
+
+                <p:columns value="#{dataTable001Dynamic.columns}" var="column" headerText="#{column.header}" field="#{column.property}" filterMatchMode="#{column.filterMatchMode}" >
+                    <h:outputText value="#{lang[column.property]}" />
+                </p:columns>
+            </p:dataTable>
+
+            <p:commandButton id="button" value="Submit" update="@form"/>
+            <p:commandButton id="buttonUpdate" value="Update" update="datatable"/>
+            <p:commandButton id="buttonResetTable" value="Reset" update="@form" action="#{dataTable001Dynamic.resetTable()}"/>
+            <p:commandButton id="buttonGlobalFilterOnly" value="Global Filter Only" update="@form" action="#{dataTable001Dynamic.toggleGlobalFilter()}"/>
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/datatable/dataTable001DynamicOtherSyntax.xhtml b/primefaces-integration-tests/src/main/webapp/datatable/dataTable001DynamicOtherSyntax.xhtml
new file mode 100644
index 0000000000..aac1d2744d
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/datatable/dataTable001DynamicOtherSyntax.xhtml
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form">
+            <p:dataTable id="datatable" widgetVar="wgtTable" value="#{dataTable001Dynamic.progLanguages}" var="lang" paginator="true" rows="3"
+                         filteredValue="#{dataTable001Dynamic.filteredProgLanguages}"
+                         filterEvent="enter"
+                         globalFilterOnly="#{dataTable001Dynamic.globalFilterOnly}"
+                         paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
+                         rowsPerPageTemplate="2,3,5,10">
+                <f:facet name="header">
+                    <div class="p-d-flex p-ai-center p-jc-between">
+                        <span>Programming Languages</span>
+                        <p:inputText id="globalFilter" onchange="PF('wgtTable').filter()" placeholder="Enter keyword"/>
+                    </div>
+                </f:facet>
+
+                <p:columns value="#{dataTable001Dynamic.columns}" var="column" headerText="#{column.header}" sortBy="#{column.property}" filterBy="#{column.property}" filterMatchMode="#{column.filterMatchMode}" >
+                    <h:outputText value="#{lang[column.property]}" />
+                </p:columns>
+            </p:dataTable>
+
+            <p:commandButton id="button" value="Submit" update="@form"/>
+            <p:commandButton id="buttonUpdate" value="Update" update="datatable"/>
+            <p:commandButton id="buttonResetTable" value="Reset" update="@form" action="#{dataTable001Dynamic.resetTable()}"/>
+            <p:commandButton id="buttonGlobalFilterOnly" value="Global Filter Only" update="@form" action="#{dataTable001Dynamic.toggleGlobalFilter()}"/>
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/datatable/dataTable001DynamicOtherSyntax2.xhtml b/primefaces-integration-tests/src/main/webapp/datatable/dataTable001DynamicOtherSyntax2.xhtml
new file mode 100644
index 0000000000..4d0d3a762d
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/datatable/dataTable001DynamicOtherSyntax2.xhtml
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form">
+            <p:dataTable id="datatable" widgetVar="wgtTable" value="#{dataTable001Dynamic.progLanguages}" var="lang" paginator="true" rows="3"
+                         filteredValue="#{dataTable001Dynamic.filteredProgLanguages}"
+                         filterEvent="enter"
+                         globalFilterOnly="#{dataTable001Dynamic.globalFilterOnly}"
+                         paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
+                         rowsPerPageTemplate="2,3,5,10">
+                <f:facet name="header">
+                    <div class="p-d-flex p-ai-center p-jc-between">
+                        <span>Programming Languages</span>
+                        <p:inputText id="globalFilter" onchange="PF('wgtTable').filter()" placeholder="Enter keyword"/>
+                    </div>
+                </f:facet>
+
+                <p:columns value="#{dataTable001Dynamic.columns}" var="column" headerText="#{column.header}" sortBy="#{lang[column.property]}" filterBy="#{lang[column.property]}" filterMatchMode="#{column.filterMatchMode}" >
+                    <h:outputText value="#{lang[column.property]}" />
+                </p:columns>
+            </p:dataTable>
+
+            <p:commandButton id="button" value="Submit" update="@form"/>
+            <p:commandButton id="buttonUpdate" value="Update" update="datatable"/>
+            <p:commandButton id="buttonResetTable" value="Reset" update="@form" action="#{dataTable001Dynamic.resetTable()}"/>
+            <p:commandButton id="buttonGlobalFilterOnly" value="Global Filter Only" update="@form" action="#{dataTable001Dynamic.toggleGlobalFilter()}"/>
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/datatable/dataTable001DynamicOtherSyntax3.xhtml b/primefaces-integration-tests/src/main/webapp/datatable/dataTable001DynamicOtherSyntax3.xhtml
new file mode 100644
index 0000000000..80e9b52e72
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/datatable/dataTable001DynamicOtherSyntax3.xhtml
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form">
+            <p:dataTable id="datatable" widgetVar="wgtTable" value="#{dataTable001Dynamic.progLanguages}" var="lang" paginator="true" rows="3"
+                         filteredValue="#{dataTable001Dynamic.filteredProgLanguages}"
+                         filterEvent="enter"
+                         globalFilterOnly="#{dataTable001Dynamic.globalFilterOnly}"
+                         paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
+                         rowsPerPageTemplate="2,3,5,10">
+                <f:facet name="header">
+                    <div class="p-d-flex p-ai-center p-jc-between">
+                        <span>Programming Languages</span>
+                        <p:inputText id="globalFilter" onchange="PF('wgtTable').filter()" placeholder="Enter keyword"/>
+                    </div>
+                </f:facet>
+
+                <p:columns value="#{dataTable001Dynamic.columns}" var="column" headerText="#{column.header}" sortBy="#{dataTable001Dynamic.justReturn(lang[column.property])}" filterBy="#{dataTable001Dynamic.justReturn(lang[column.property])}" filterMatchMode="#{column.filterMatchMode}" >
+                    <h:outputText value="#{lang[column.property]}" />
+                </p:columns>
+            </p:dataTable>
+
+            <p:commandButton id="button" value="Submit" update="@form"/>
+            <p:commandButton id="buttonUpdate" value="Update" update="datatable"/>
+            <p:commandButton id="buttonResetTable" value="Reset" update="@form" action="#{dataTable001Dynamic.resetTable()}"/>
+            <p:commandButton id="buttonGlobalFilterOnly" value="Global Filter Only" update="@form" action="#{dataTable001Dynamic.toggleGlobalFilter()}"/>
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload000.xhtml b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload000.xhtml
new file mode 100644
index 0000000000..d4f3cd5e57
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload000.xhtml
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form" enctype="multipart/form-data">
+
+            <p:fileUpload id="fileupload" value="#{fileUploadHandler.file}"
+                          mode="simple"/>
+
+            <p:commandButton id="button" value="Submit" ajax="false" update="@form" action="#{fileUploadHandler.uploadFile}"/>
+
+            <p:dataTable id="uploadedfiles" var="file" value="#{fileUploadView.uploadedFiles}">
+                <p:column headerText="Name">
+                    <h:outputText value="#{file.fileName}" />
+                </p:column>
+
+                <p:column headerText="Size">
+                    <h:outputText value="#{file.size}" />
+                </p:column>
+
+                <p:column headerText="Error">
+                    <h:outputText value="#{file.errorMessage}" />
+                </p:column>
+            </p:dataTable>
+
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload001.xhtml b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload001.xhtml
new file mode 100644
index 0000000000..201750a523
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload001.xhtml
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form" enctype="multipart/form-data">
+
+            <p:fileUpload id="fileupload" value="#{fileUploadHandler.file}"
+                          mode="simple" skinSimple="true"/>
+
+            <p:commandButton id="button" value="Submit" ajax="false" update="@form" action="#{fileUploadHandler.uploadFile}"/>
+
+            <p:dataTable id="uploadedfiles" var="file" value="#{fileUploadView.uploadedFiles}">
+                <p:column headerText="Name">
+                    <h:outputText value="#{file.fileName}" />
+                </p:column>
+
+                <p:column headerText="Size">
+                    <h:outputText value="#{file.size}" />
+                </p:column>
+
+                <p:column headerText="Error">
+                    <h:outputText value="#{file.errorMessage}" />
+                </p:column>
+            </p:dataTable>
+
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload002.xhtml b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload002.xhtml
new file mode 100644
index 0000000000..1f794c4c1d
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload002.xhtml
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form" enctype="multipart/form-data">
+
+            <p:fileUpload id="fileupload" value="#{fileUploadHandler.files}"
+                          mode="simple" skinSimple="true" multiple="true"/>
+
+            <p:commandButton id="button" value="Submit" ajax="false" update="@form" action="#{fileUploadHandler.uploadFiles}"/>
+
+            <p:dataTable id="uploadedfiles" var="file" value="#{fileUploadView.uploadedFiles}">
+                <p:column headerText="Name">
+                    <h:outputText value="#{file.fileName}" />
+                </p:column>
+
+                <p:column headerText="Size">
+                    <h:outputText value="#{file.size}" />
+                </p:column>
+
+                <p:column headerText="Error">
+                    <h:outputText value="#{file.errorMessage}" />
+                </p:column>
+            </p:dataTable>
+
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload003.xhtml b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload003.xhtml
new file mode 100644
index 0000000000..5668adaf23
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload003.xhtml
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form">
+
+            <p:fileUpload id="fileupload" value="#{fileUploadHandler.file}"
+                          mode="simple" skinSimple="true" auto="true"
+                          process="@this" update="uploadedfiles"
+                          listener="#{fileUploadHandler.handleFileUpload}"/>
+
+            <p:dataTable id="uploadedfiles" var="file" value="#{fileUploadView.uploadedFiles}">
+                <p:column headerText="Name">
+                    <h:outputText value="#{file.fileName}" />
+                </p:column>
+
+                <p:column headerText="Size">
+                    <h:outputText value="#{file.size}" />
+                </p:column>
+
+                <p:column headerText="Error">
+                    <h:outputText value="#{file.errorMessage}" />
+                </p:column>
+            </p:dataTable>
+
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload004.xhtml b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload004.xhtml
new file mode 100644
index 0000000000..7bde626e58
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload004.xhtml
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form">
+
+            <p:fileUpload id="fileupload" value="#{fileUploadHandler.files}"
+                          mode="simple" skinSimple="true" multiple="true" auto="true"
+                          process="@this" update="uploadedfiles"
+                          listener="#{fileUploadHandler.handleFilesUpload}"/>
+
+            <p:dataTable id="uploadedfiles" var="file" value="#{fileUploadView.uploadedFiles}">
+                <p:column headerText="Name">
+                    <h:outputText value="#{file.fileName}" />
+                </p:column>
+
+                <p:column headerText="Size">
+                    <h:outputText value="#{file.size}" />
+                </p:column>
+
+                <p:column headerText="Error">
+                    <h:outputText value="#{file.errorMessage}" />
+                </p:column>
+            </p:dataTable>
+
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload005.xhtml b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload005.xhtml
new file mode 100644
index 0000000000..960a61adb6
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload005.xhtml
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form">
+
+            <p:fileUpload id="fileupload" listener="#{fileUploadHandler.handleFileUpload}"
+                          mode="advanced" dragDropSupport="false"
+                          update="uploadedfiles" allowTypes="/(\.|\/)(csv)$/"/>
+
+            <p:dataTable id="uploadedfiles" var="file" value="#{fileUploadView.uploadedFiles}">
+                <p:column headerText="Name">
+                    <h:outputText value="#{file.fileName}" />
+                </p:column>
+
+                <p:column headerText="Size">
+                    <h:outputText value="#{file.size}" />
+                </p:column>
+
+                <p:column headerText="Error">
+                    <h:outputText value="#{file.errorMessage}" />
+                </p:column>
+            </p:dataTable>
+
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload006.xhtml b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload006.xhtml
new file mode 100644
index 0000000000..41c781d131
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload006.xhtml
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form">
+
+            <p:remoteCommand name="updateUploadedFiles" update="uploadedfiles"/>
+            <p:fileUpload id="fileupload" widgetVar="fileUpload" listener="#{fileUploadHandler.handleFileUpload}"
+                          mode="advanced" dragDropSupport="false" multiple="true"
+                          oncomplete="if ( !PF('fileUpload').files.length ) updateUploadedFiles();"
+                          allowTypes="/(\.|\/)(csv)$/"/>
+
+            <p:dataTable id="uploadedfiles" var="file" value="#{fileUploadView.uploadedFiles}">
+                <p:column headerText="Name">
+                    <h:outputText value="#{file.fileName}" />
+                </p:column>
+
+                <p:column headerText="Size">
+                    <h:outputText value="#{file.size}" />
+                </p:column>
+
+                <p:column headerText="Error">
+                    <h:outputText value="#{file.errorMessage}" />
+                </p:column>
+            </p:dataTable>
+
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload007.xhtml b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload007.xhtml
new file mode 100644
index 0000000000..8fd4b06e67
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload007.xhtml
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form">
+
+            <p:remoteCommand name="updateUploadedFiles" update="uploadedfiles"/>
+            <p:fileUpload id="fileupload" widgetVar="fileUpload" listener="#{fileUploadHandler.handleFileUpload}"
+                          mode="advanced" dragDropSupport="false" auto="true"
+                          oncomplete="if ( !PF('fileUpload').files.length ) updateUploadedFiles();"
+                          allowTypes="/(\.|\/)(csv)$/"/>
+
+            <p:dataTable id="uploadedfiles" var="file" value="#{fileUploadView.uploadedFiles}">
+                <p:column headerText="Name">
+                    <h:outputText value="#{file.fileName}" />
+                </p:column>
+
+                <p:column headerText="Size">
+                    <h:outputText value="#{file.size}" />
+                </p:column>
+
+                <p:column headerText="Error">
+                    <h:outputText value="#{file.errorMessage}" />
+                </p:column>
+            </p:dataTable>
+
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload008.xhtml b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload008.xhtml
new file mode 100644
index 0000000000..52d3ef6758
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload008.xhtml
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form">
+
+            <p:remoteCommand name="updateUploadedFiles" update="uploadedfiles"/>
+            <p:fileUpload id="fileupload" widgetVar="fileUpload" listener="#{fileUploadHandler.handleFileUpload}"
+                          mode="advanced" dragDropSupport="false" multiple="true" auto="true"
+                          oncomplete="if ( !PF('fileUpload').files.length ) updateUploadedFiles();"
+                          allowTypes="/(\.|\/)(csv)$/"/>
+
+            <p:dataTable id="uploadedfiles" var="file" value="#{fileUploadView.uploadedFiles}">
+                <p:column headerText="Name">
+                    <h:outputText value="#{file.fileName}" />
+                </p:column>
+
+                <p:column headerText="Size">
+                    <h:outputText value="#{file.size}" />
+                </p:column>
+
+                <p:column headerText="Error">
+                    <h:outputText value="#{file.errorMessage}" />
+                </p:column>
+            </p:dataTable>
+
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload009.xhtml b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload009.xhtml
new file mode 100644
index 0000000000..975851f27f
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload009.xhtml
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form">
+
+            <p:remoteCommand name="updateUploadedFiles" update="uploadedfiles"/>
+            <p:fileUpload id="fileupload" widgetVar="fileUpload" listener="#{fileUploadHandler.handleFileUpload}"
+                          mode="advanced" dragDropSupport="false" multiple="true" sequential="true"
+                          oncomplete="if ( !PF('fileUpload').files.length ) updateUploadedFiles();"
+                          allowTypes="/(\.|\/)(csv)$/"/>
+
+            <p:dataTable id="uploadedfiles" var="file" value="#{fileUploadView.uploadedFiles}">
+                <p:column headerText="Name">
+                    <h:outputText value="#{file.fileName}" />
+                </p:column>
+
+                <p:column headerText="Size">
+                    <h:outputText value="#{file.size}" />
+                </p:column>
+
+                <p:column headerText="Error">
+                    <h:outputText value="#{file.errorMessage}" />
+                </p:column>
+            </p:dataTable>
+
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload010.xhtml b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload010.xhtml
new file mode 100644
index 0000000000..2e6d0d544c
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/fileupload/fileUpload010.xhtml
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form">
+
+            <p:remoteCommand name="updateUploadedFiles" update="uploadedfiles"/>
+            <p:fileUpload id="fileupload" widgetVar="fileUpload" listener="#{fileUploadHandler.handleFileUpload}"
+                          mode="advanced" dragDropSupport="false" multiple="true" auto="true" sequential="true"
+                          oncomplete="if ( !PF('fileUpload').files.length ) updateUploadedFiles();"
+                          allowTypes="/(\.|\/)(csv)$/"/>
+
+            <p:dataTable id="uploadedfiles" var="file" value="#{fileUploadView.uploadedFiles}">
+                <p:column headerText="Name">
+                    <h:outputText value="#{file.fileName}" />
+                </p:column>
+
+                <p:column headerText="Size">
+                    <h:outputText value="#{file.size}" />
+                </p:column>
+
+                <p:column headerText="Error">
+                    <h:outputText value="#{file.errorMessage}" />
+                </p:column>
+            </p:dataTable>
+
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/tree/tree001.xhtml b/primefaces-integration-tests/src/main/webapp/tree/tree001.xhtml
new file mode 100644
index 0000000000..3b04743bb6
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/tree/tree001.xhtml
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html"
+    xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+        <h:form id="form">
+            <p:messages id="msgs" showDetail="true">
+                <p:autoUpdate />
+            </p:messages>
+
+            <p:tree id="tree" value="#{tree001.root}" var="document" widgetVar="tree">
+                <p:ajax event="expand" update=":form:msgs" listener="#{tree001.onNodeExpand}" />
+                <p:ajax event="collapse" update=":form:msgs" listener="#{tree001.onNodeCollapse}" />
+
+                <p:treeNode>
+                    <h:outputText value="#{document}" />
+                </p:treeNode>
+            </p:tree>
+
+            <p:commandButton id="buttonUpdate" value="Update" update="@form"/>
+
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
\ No newline at end of file
diff --git a/primefaces-integration-tests/src/main/webapp/tree/tree002.xhtml b/primefaces-integration-tests/src/main/webapp/tree/tree002.xhtml
new file mode 100644
index 0000000000..01b460b358
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/tree/tree002.xhtml
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html"
+    xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+        <h:form id="form">
+            <p:messages id="msgs" showDetail="true">
+                <p:autoUpdate />
+            </p:messages>
+
+            <p:tree id="tree" value="#{tree002.root}" var="document" widgetVar="tree" selectionMode="single"
+                selection="#{tree002.selectedNode}">
+                <p:ajax event="select" update=":form:msgs" listener="#{tree002.onNodeSelect}" />
+                <p:ajax event="unselect" update=":form:msgs" listener="#{tree002.onNodeUnselect}" />
+
+                <p:treeNode>
+                    <h:outputText value="#{document}" />
+                </p:treeNode>
+            </p:tree>
+
+            <p:commandButton id="buttonShowSelectedNode" value="Show selected nodes" action="#{tree002.showSelectedNode}" />
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
\ No newline at end of file
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/datatable/DataTable001Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/datatable/DataTable001Test.java
index 5dae1a4f75..78b65ee4b8 100644
--- a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/datatable/DataTable001Test.java
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/datatable/DataTable001Test.java
@@ -27,13 +27,13 @@ import org.json.JSONObject;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Order;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.openqa.selenium.By;
 import org.openqa.selenium.WebElement;
-import org.openqa.selenium.support.FindBy;
 import org.openqa.selenium.support.ui.Select;
 import org.primefaces.integrationtests.datatable.ProgrammingLanguage.ProgrammingLanguageType;
-import org.primefaces.selenium.AbstractPrimePage;
 import org.primefaces.selenium.PrimeExpectedConditions;
 import org.primefaces.selenium.PrimeSelenium;
 import org.primefaces.selenium.component.CommandButton;
@@ -46,15 +46,18 @@ import org.primefaces.selenium.component.model.datatable.Row;
 import java.util.Comparator;
 import java.util.List;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public class DataTable001Test extends AbstractDataTableTest {
 
-    @Test
+    @ParameterizedTest
+    @MethodSource("provideXhtmls")
     @Order(1)
     @DisplayName("DataTable: Basic & Paginator")
-    public void testBasicAndPaginator(Page page) {
+    public void testBasicAndPaginator(String xhtml) {
         // Arrange
-        DataTable dataTable = page.dataTable;
+        getWebDriver().get(PrimeSelenium.getUrl(xhtml));
+        DataTable dataTable = getDataTable();
         Assertions.assertNotNull(dataTable);
 
         // Act
@@ -104,12 +107,14 @@ public class DataTable001Test extends AbstractDataTableTest {
         assertConfiguration(dataTable.getWidgetConfiguration());
     }
 
-    @Test
+    @ParameterizedTest
+    @MethodSource("provideXhtmls")
     @Order(2)
     @DisplayName("DataTable: single sort")
-    public void testSortSingle(Page page) {
+    public void testSortSingle(String xhtml) {
         // Arrange
-        DataTable dataTable = page.dataTable;
+        getWebDriver().get(PrimeSelenium.getUrl(xhtml));
+        DataTable dataTable = getDataTable();
         Assertions.assertNotNull(dataTable);
         dataTable.selectPage(1);
 
@@ -125,23 +130,25 @@ public class DataTable001Test extends AbstractDataTableTest {
 
         // Assert
         langsSorted = sortBy(Comparator.comparing(ProgrammingLanguage::getName).reversed());
-        assertRows(dataTable, langsSorted);
+        assertRows(getDataTable(), langsSorted);
 
         // Act
-        page.buttonUpdate.click();
+        getButtonUpdate().click();
 
         // Assert - sort must not be lost after update
-        assertRows(dataTable, langsSorted);
+        assertRows(getDataTable(), langsSorted);
 
-        assertConfiguration(dataTable.getWidgetConfiguration());
+        assertConfiguration(getDataTable().getWidgetConfiguration());
     }
 
-    @Test
+    @ParameterizedTest
+    @MethodSource("provideXhtmls")
     @Order(3)
     @DisplayName("DataTable: filter")
-    public void testFilter(Page page) {
+    public void testFilter(String xhtml) {
         // Arrange
-        DataTable dataTable = page.dataTable;
+        getWebDriver().get(PrimeSelenium.getUrl(xhtml));
+        DataTable dataTable = getDataTable();;
         dataTable.selectPage(1);
         dataTable.sort("Name");
 
@@ -153,20 +160,22 @@ public class DataTable001Test extends AbstractDataTableTest {
         assertRows(dataTable, langsFiltered);
 
         // Act
-        page.buttonUpdate.click();
+        getButtonUpdate().click();
 
         // Assert - filter must not be lost after update
-        assertRows(dataTable, langsFiltered);
+        assertRows(getDataTable(), langsFiltered);
 
-        assertConfiguration(dataTable.getWidgetConfiguration());
+        assertConfiguration(getDataTable().getWidgetConfiguration());
     }
 
-    @Test
+    @ParameterizedTest
+    @MethodSource("provideXhtmls")
     @Order(4)
     @DisplayName("DataTable: filter plus paging")
-    public void testFilterPlusPaging(Page page) {
+    public void testFilterPlusPaging(String xhtml) {
         // Arrange
-        DataTable dataTable = page.dataTable;
+        getWebDriver().get(PrimeSelenium.getUrl(xhtml));
+        DataTable dataTable = getDataTable();
         dataTable.selectPage(1);
         dataTable.sort("Name");
         Select selectRowsPerPage = new Select(dataTable.getPaginatorWebElement().findElement(By.className("ui-paginator-rpp-options")));
@@ -191,13 +200,15 @@ public class DataTable001Test extends AbstractDataTableTest {
         assertConfiguration(dataTable.getWidgetConfiguration());
     }
 
-    @Test
+    @ParameterizedTest
+    @MethodSource("provideXhtmls")
     @Order(5)
     @DisplayName("DataTable: global filter with globalFilterOnly=false")
-    public void testGlobalFilter(Page page) {
+    public void testGlobalFilter(String xhtml) {
         // Arrange
-        DataTable dataTable = page.dataTable;
-        InputText globalFilter = page.globalFilter;
+        getWebDriver().get(PrimeSelenium.getUrl(xhtml));
+        DataTable dataTable = getDataTable();;
+        InputText globalFilter = getGlobalFilter();
         Assertions.assertNotNull(globalFilter);
         dataTable.selectPage(1);
         dataTable.sort("Name");
@@ -211,33 +222,37 @@ public class DataTable001Test extends AbstractDataTableTest {
         assertConfiguration(dataTable.getWidgetConfiguration());
     }
 
-    @Test
+    @ParameterizedTest
+    @MethodSource("provideXhtmls")
     @Order(6)
     @DisplayName("DataTable: GitHub #7193 global filter with globalFilterOnly=true")
-    public void testGlobalFilterIncludeNotDisplayedFilter(Page page) {
+    public void testGlobalFilterIncludeNotDisplayedFilter(String xhtml) {
         // Arrange
-        DataTable dataTable = page.dataTable;
-        InputText globalFilter = page.globalFilter;
+        getWebDriver().get(PrimeSelenium.getUrl(xhtml));
+        DataTable dataTable = getDataTable();
+        InputText globalFilter = getGlobalFilter();
         Assertions.assertNotNull(globalFilter);
         dataTable.selectPage(1);
         dataTable.sort("Type");
 
         // Act
-        page.buttonGlobalFilterOnly.click();
-        filterGlobal(globalFilter, ProgrammingLanguageType.INTERPRETED.name());
+        getButtonGlobalFilterOnly().click();
+        filterGlobal(getGlobalFilter(), ProgrammingLanguageType.INTERPRETED.name());
 
         // Assert
         List<ProgrammingLanguage> langsFiltered = filterByType(ProgrammingLanguageType.INTERPRETED);
-        assertRows(dataTable, langsFiltered);
-        assertConfiguration(dataTable.getWidgetConfiguration());
+        assertRows(getDataTable(), langsFiltered);
+        assertConfiguration(getDataTable().getWidgetConfiguration());
     }
 
-    @Test
+    @ParameterizedTest
+    @MethodSource("provideXhtmls")
     @Order(7)
     @DisplayName("DataTable: rows per page & reset; includes https://github.com/primefaces/primefaces/issues/5465 & https://github.com/primefaces/primefaces/issues/5481")
-    public void testRowsPerPageAndReset_5465_5481(Page page) {
+    public void testRowsPerPageAndReset_5465_5481(String xhtml) {
         // Arrange
-        DataTable dataTable = page.dataTable;
+        getWebDriver().get(PrimeSelenium.getUrl(xhtml));
+        DataTable dataTable = getDataTable();;
         Assertions.assertNotNull(dataTable);
 
         // Assert
@@ -256,15 +271,15 @@ public class DataTable001Test extends AbstractDataTableTest {
 
         // Act
         dataTable.filter("Name", "Java");
-        PrimeSelenium.guardAjax(page.buttonResetTable).click();
+        PrimeSelenium.guardAjax(getButtonResetTable()).click();
 
         // Assert
-        selectRowsPerPage = new Select(dataTable.getPaginatorWebElement().findElement(By.className("ui-paginator-rpp-options")));
+        selectRowsPerPage = new Select(getDataTable().getPaginatorWebElement().findElement(By.className("ui-paginator-rpp-options")));
         Assertions.assertEquals("3", selectRowsPerPage.getFirstSelectedOption().getText());
-        Assertions.assertEquals(3, dataTable.getRows().size());
-        assertRows(dataTable, languages.stream().limit(3).collect(Collectors.toList())); //implicit checks reset sort & filter
+        Assertions.assertEquals(3, getDataTable().getRows().size());
+        assertRows(getDataTable(), languages.stream().limit(3).collect(Collectors.toList())); //implicit checks reset sort & filter
 
-        assertConfiguration(dataTable.getWidgetConfiguration());
+        assertConfiguration(getDataTable().getWidgetConfiguration());
     }
 
     private void assertConfiguration(JSONObject cfg) {
@@ -275,25 +290,32 @@ public class DataTable001Test extends AbstractDataTableTest {
         Assertions.assertEquals(0, cfg.getInt("tabindex"));
     }
 
-    public static class Page extends AbstractPrimePage {
-        @FindBy(id = "form:datatable")
-        DataTable dataTable;
+    private static Stream<Arguments> provideXhtmls() {
+        return Stream.of(
+                Arguments.of("datatable/dataTable001.xhtml"),
+                Arguments.of("datatable/dataTable001Dynamic.xhtml"),
+                //Arguments.of("datatable/dataTable001DynamicOtherSyntax.xhtml"),
+                Arguments.of("datatable/dataTable001DynamicOtherSyntax2.xhtml"),
+                Arguments.of("datatable/dataTable001DynamicOtherSyntax3.xhtml"));
+    }
 
-        @FindBy(id = "form:datatable:globalFilter")
-        InputText globalFilter;
+    private DataTable getDataTable() {
+        return PrimeSelenium.createFragment(DataTable.class, By.id("form:datatable"));
+    }
 
-        @FindBy(id = "form:buttonUpdate")
-        CommandButton buttonUpdate;
+    private CommandButton getButtonUpdate() {
+        return PrimeSelenium.createFragment(CommandButton.class, By.id("form:buttonUpdate"));
+    }
 
-        @FindBy(id = "form:buttonResetTable")
-        CommandButton buttonResetTable;
-        
-        @FindBy(id = "form:buttonGlobalFilterOnly")
-        CommandButton buttonGlobalFilterOnly;
+    private CommandButton getButtonResetTable() {
+        return PrimeSelenium.createFragment(CommandButton.class, By.id("form:buttonResetTable"));
+    }
+
+    private InputText getGlobalFilter() {
+        return PrimeSelenium.createFragment(InputText.class, By.id("form:datatable:globalFilter"));
+    }
 
-        @Override
-        public String getLocation() {
-            return "datatable/dataTable001.xhtml";
-        }
+    private CommandButton getButtonGlobalFilterOnly() {
+        return PrimeSelenium.createFragment(CommandButton.class, By.id("form:buttonGlobalFilterOnly"));
     }
 }
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/AbstractFileUploadTest.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/AbstractFileUploadTest.java
new file mode 100644
index 0000000000..48c5652371
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/AbstractFileUploadTest.java
@@ -0,0 +1,86 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.fileupload;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.jupiter.api.Assertions;
+import org.primefaces.selenium.AbstractPrimePageTest;
+import org.primefaces.selenium.component.DataTable;
+import org.primefaces.selenium.component.model.datatable.Row;
+
+public abstract class AbstractFileUploadTest extends AbstractPrimePageTest {
+
+    protected File locateClientSideFile(String fileName) {
+        String folder = AbstractFileUploadTest.class.getPackage().getName().replace(".", "/");
+        URL url = AbstractFileUploadTest.class.getResource("/" + folder + "/" + fileName);
+        Assertions.assertNotNull(url, "client side file " + fileName + " does not exist in /" + folder);
+        return new File(url.getPath());
+    }
+
+    /**
+     * Uploaded files are displayed in a data table with columns name, size and errorMessage.
+     * @param uploadedFiles the data tble to check against
+     * @param files the (client side) files uploaded
+     */
+    protected void assertUploadedFiles(DataTable uploadedFiles, File... files) {
+        Assertions.assertNotNull(uploadedFiles);
+        Assertions.assertNotNull(uploadedFiles.getRows());
+        String expectedFiles = Arrays.stream(files).map(f -> f.getName())
+                .collect(Collectors.joining(","));
+        String actualFiles = uploadedFiles.getRows().stream().map(r -> r.getCell(0).getText())
+                .collect(Collectors.joining(","));
+        String diag = expectedFiles + " <> " + actualFiles;
+        if (files.length == 0) {
+            // emptyMessage
+            Assertions.assertEquals(1, uploadedFiles.getRows().size(), diag);
+            Assertions.assertEquals(1, uploadedFiles.getRow(0).getCells().size(), diag);
+        } else {
+            Assertions.assertEquals(files.length, uploadedFiles.getRows().size(), diag);
+        }
+
+        // sequence is not guarateed to be the same, so sort by name and size
+        Arrays.sort(files, (f1, f2) -> {
+            int res = f1.getName().compareTo(f2.getName());
+            res = res == 0 ? (int) (f1.length() - f2.length()) : res;
+            return res;
+        });
+        List<Row> rows = new ArrayList<>(uploadedFiles.getRows());
+        rows.sort((f1, f2) -> {
+            int res = f1.getCell(0).getText().compareTo(f2.getCell(0).getText());
+            res = res == 0 ? (int) (Long.parseLong(f1.getCell(1).getText()) - Long.parseLong(f1.getCell(1).getText())) : res;
+            return res;
+        });
+        for (int f = 0; f < files.length; ++f) {
+            Row row = rows.get(f);
+            Assertions.assertEquals(files[f].getName(), row.getCell(0).getText()); // same file name
+            Assertions.assertEquals("", row.getCell(2).getText(), row.getCell(2).getText()); // empty error message
+            Assertions.assertEquals(files[f].length(), Long.parseLong(row.getCell(1).getText())); // same file size
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload000Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload000Test.java
new file mode 100644
index 0000000000..19e020d331
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload000Test.java
@@ -0,0 +1,112 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.fileupload;
+
+import java.io.File;
+import org.json.JSONObject;
+import org.junit.jupiter.api.*;
+import org.openqa.selenium.support.FindBy;
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.component.CommandButton;
+import org.primefaces.selenium.component.DataTable;
+import org.primefaces.selenium.component.FileUpload;
+
+/**
+ * Tests basic single file upload.
+ * p:fileUpload mode=simple auto=false multiple=false (skinSimple=false)
+ */
+public class FileUpload000Test extends AbstractFileUploadTest {
+
+    @Test
+    @Order(1)
+    public void testBasicSingleUpload(Page page) {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file);
+        Assertions.assertTrue(fileUpload.getValue().contains(file.getName()));
+        page.button.click();
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(2)
+    public void testBasicSingleUploadTwice(Page page) {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file1);
+        Assertions.assertTrue(fileUpload.getValue().contains(file1.getName()));
+        page.button.click();
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1);
+
+        // Act
+        File file2 = locateClientSideFile("file2.csv");
+        fileUpload.setValue(file2);
+        Assertions.assertTrue(fileUpload.getValue().contains(file2.getName()));
+        page.button.click();
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    private void assertConfiguration(JSONObject cfg) {
+        System.out.println("FileInput Config = " + cfg);
+        Assertions.assertFalse(cfg.has("skinSimple"));
+        Assertions.assertFalse(cfg.has("auto"));
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:fileupload")
+        FileUpload fileupload;
+
+        @FindBy(id = "form:button")
+        CommandButton button;
+
+        @FindBy(id = "form:uploadedfiles")
+        DataTable uploadedFiles;
+
+        @Override
+        public String getLocation() {
+            return "fileupload/fileUpload000.xhtml";
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload001Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload001Test.java
new file mode 100644
index 0000000000..86246605db
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload001Test.java
@@ -0,0 +1,112 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.fileupload;
+
+import java.io.File;
+import org.json.JSONObject;
+import org.junit.jupiter.api.*;
+import org.openqa.selenium.support.FindBy;
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.component.CommandButton;
+import org.primefaces.selenium.component.DataTable;
+import org.primefaces.selenium.component.FileUpload;
+
+/**
+ * Tests basic single file upload.
+ * p:fileUpload mode=simple auto=false multiple=false (skinSimple=true)
+ */
+public class FileUpload001Test extends AbstractFileUploadTest {
+
+    @Test
+    @Order(1)
+    public void testBasicSingleUpload(Page page) {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file);
+        Assertions.assertTrue(fileUpload.getWidgetValue().startsWith(file.getName()), fileUpload.getWidgetValue());
+        page.button.click();
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(2)
+    public void testBasicSingleUploadTwice(Page page) {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file1);
+        Assertions.assertTrue(fileUpload.getWidgetValue().startsWith(file1.getName()), fileUpload.getWidgetValue());
+        page.button.click();
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1);
+
+        // Act
+        File file2 = locateClientSideFile("file2.csv");
+        fileUpload.setValue(file2);
+        Assertions.assertTrue(fileUpload.getWidgetValue().startsWith(file2.getName()), fileUpload.getWidgetValue());
+        page.button.click();
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    private void assertConfiguration(JSONObject cfg) {
+        System.out.println("FileInput Config = " + cfg);
+        Assertions.assertTrue(cfg.getBoolean("skinSimple"));
+        Assertions.assertFalse(cfg.has("auto"));
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:fileupload")
+        FileUpload fileupload;
+
+        @FindBy(id = "form:button")
+        CommandButton button;
+
+        @FindBy(id = "form:uploadedfiles")
+        DataTable uploadedFiles;
+
+        @Override
+        public String getLocation() {
+            return "fileupload/fileUpload001.xhtml";
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload002Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload002Test.java
new file mode 100644
index 0000000000..35cc908fea
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload002Test.java
@@ -0,0 +1,144 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.fileupload;
+
+import java.io.File;
+import org.json.JSONObject;
+import org.junit.jupiter.api.*;
+import org.openqa.selenium.support.FindBy;
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.component.CommandButton;
+import org.primefaces.selenium.component.DataTable;
+import org.primefaces.selenium.component.FileUpload;
+
+/**
+ * Tests basic multiple file upload.
+ * p:fileUpload mode=simple auto=false multiple=true (skinSimple=true)
+ */
+public class FileUpload002Test extends AbstractFileUploadTest {
+
+    @Test
+    @Order(1)
+    public void testBasicMultipleUploadSingleFile(Page page) {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file);
+        Assertions.assertTrue(fileUpload.getWidgetValue().startsWith(file.getName()), fileUpload.getWidgetValue());
+        page.button.click();
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(2)
+    public void testBasicMultipleUploadMultipleFiles(Page page) throws Exception {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        File file2 = locateClientSideFile("file2.csv");
+        fileUpload.setValue(file1, file2);
+
+        // the widget shows only the first file name and the number of additional files
+        Assertions.assertTrue(fileUpload.getWidgetValue().startsWith(file1.getName()), fileUpload.getWidgetValue());
+        Assertions.assertTrue(fileUpload.getWidgetValue().matches(".*\\+\\s*1$"));
+        page.button.click();
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(2)
+    public void testBasicMultipleUploadMultipleFilesTwice(Page page) throws Exception {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        File file2 = locateClientSideFile("file2.csv");
+        fileUpload.setValue(file1, file2);
+
+        // the widget shows only the first file name and the number of additional files
+        Assertions.assertTrue(fileUpload.getWidgetValue().startsWith(file1.getName()), fileUpload.getWidgetValue());
+        Assertions.assertTrue(fileUpload.getWidgetValue().matches(".*\\+\\s*1$"));
+        page.button.click();
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+
+        // Act
+        File file3 = locateClientSideFile("file2.csv");
+        File file4 = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file3, file4);
+
+        // the widget shows only the first file name and the number of additional files
+        Assertions.assertTrue(fileUpload.getWidgetValue().startsWith(file3.getName()), fileUpload.getWidgetValue());
+        Assertions.assertTrue(fileUpload.getWidgetValue().matches(".*\\+\\s*1$"));
+        page.button.click();
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2, file3, file4);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    private void assertConfiguration(JSONObject cfg) {
+        System.out.println("FileInput Config = " + cfg);
+        Assertions.assertTrue(cfg.getBoolean("skinSimple"));
+        Assertions.assertFalse(cfg.has("auto"));
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:fileupload")
+        FileUpload fileupload;
+
+        @FindBy(id = "form:button")
+        CommandButton button;
+
+        @FindBy(id = "form:uploadedfiles")
+        DataTable uploadedFiles;
+
+        @Override
+        public String getLocation() {
+            return "fileupload/fileUpload002.xhtml";
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload003Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload003Test.java
new file mode 100644
index 0000000000..968590ac46
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload003Test.java
@@ -0,0 +1,103 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.fileupload;
+
+import java.io.File;
+import org.json.JSONObject;
+import org.junit.jupiter.api.*;
+import org.openqa.selenium.support.FindBy;
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.PrimeSelenium;
+import org.primefaces.selenium.component.DataTable;
+import org.primefaces.selenium.component.FileUpload;
+
+/**
+ * Tests basic auto single file upload.
+ * p:fileUpload mode=simple auto=true multiple=false (skinSimple=true)
+ */
+public class FileUpload003Test extends AbstractFileUploadTest {
+
+    @Test
+    @Order(1)
+    public void testBasicAutoSingleUpload(Page page) {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file = locateClientSideFile("file1.csv");
+        PrimeSelenium.guardAjax(fileUpload).setValue(file);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(2)
+    public void testBasicAutoSingleUploadTwice(Page page) {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        PrimeSelenium.guardAjax(fileUpload).setValue(file1);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1);
+
+        // Act
+        File file2 = locateClientSideFile("file2.csv");
+        PrimeSelenium.guardAjax(fileUpload).setValue(file2);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    private void assertConfiguration(JSONObject cfg) {
+        System.out.println("FileInput Config = " + cfg);
+        Assertions.assertTrue(cfg.getBoolean("skinSimple"));
+        Assertions.assertTrue(cfg.getBoolean("auto"));
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:fileupload")
+        FileUpload fileupload;
+
+        @FindBy(id = "form:uploadedfiles")
+        DataTable uploadedFiles;
+
+        @Override
+        public String getLocation() {
+            return "fileupload/fileUpload003.xhtml";
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload004Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload004Test.java
new file mode 100644
index 0000000000..959c89ad25
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload004Test.java
@@ -0,0 +1,124 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.fileupload;
+
+import java.io.File;
+import org.json.JSONObject;
+import org.junit.jupiter.api.*;
+import org.openqa.selenium.support.FindBy;
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.PrimeSelenium;
+import org.primefaces.selenium.component.DataTable;
+import org.primefaces.selenium.component.FileUpload;
+
+/**
+ * Tests basic auto multiple file upload.
+ * p:fileUpload mode=simple auto=true multiple=true (skinSimple=true)
+ */
+public class FileUpload004Test extends AbstractFileUploadTest {
+
+    @Test
+    @Order(1)
+    public void testBasicAutoMultipleUploadSingleFile(Page page) {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file = locateClientSideFile("file1.csv");
+        PrimeSelenium.guardAjax(fileUpload).setValue(file);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(2)
+    public void testBasicAutoMultipleUploadMultipleFiles(Page page) throws Exception {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        File file2 = locateClientSideFile("file2.csv");
+        PrimeSelenium.guardAjax(fileUpload).setValue(file1, file2);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(3)
+    public void testBasicAutoMultipleUploadMultipleFilesTwice(Page page) throws Exception {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        File file2 = locateClientSideFile("file2.csv");
+        PrimeSelenium.guardAjax(fileUpload).setValue(file1, file2);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+
+        // Act
+        File file3 = locateClientSideFile("file2.csv");
+        File file4 = locateClientSideFile("file1.csv");
+        PrimeSelenium.guardAjax(fileUpload).setValue(file3, file4);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2, file3, file4);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    private void assertConfiguration(JSONObject cfg) {
+        System.out.println("FileInput Config = " + cfg);
+        Assertions.assertTrue(cfg.getBoolean("skinSimple"));
+        Assertions.assertTrue(cfg.getBoolean("auto"));
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:fileupload")
+        FileUpload fileupload;
+
+        @FindBy(id = "form:uploadedfiles")
+        DataTable uploadedFiles;
+
+        @Override
+        public String getLocation() {
+            return "fileupload/fileUpload004.xhtml";
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload005Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload005Test.java
new file mode 100644
index 0000000000..6681a1b393
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload005Test.java
@@ -0,0 +1,133 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.fileupload;
+
+import java.io.File;
+import org.json.JSONObject;
+import org.junit.jupiter.api.*;
+import org.openqa.selenium.support.FindBy;
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.component.DataTable;
+import org.primefaces.selenium.component.FileUpload;
+
+/**
+ * Tests advanced single file upload.
+ * p:fileUpload mode=advanced auto=false multiple=false dragDropSupport=false
+ */
+public class FileUpload005Test extends AbstractFileUploadTest {
+
+    @Test
+    @Order(1)
+    public void testAdvancedSingleUpload(Page page) {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file);
+        Assertions.assertTrue(fileUpload.getWidgetValue().startsWith(file.getName()), fileUpload.getWidgetValues().toString());
+        fileUpload.getAdvancedUploadButton().click();
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(2)
+    public void testAdvancedSingleUploadTwice(Page page) {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file1);
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file1.getName()), fileUpload.getWidgetValues().toString());
+        fileUpload.getAdvancedUploadButton().click();
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1);
+
+        // Act
+        File file2 = locateClientSideFile("file2.csv");
+        fileUpload.setValue(file2);
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file2.getName()), fileUpload.getWidgetValues().toString());
+        fileUpload.getAdvancedUploadButton().click();
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(3)
+    public void testAdvancedSingleUploadCancel(Page page) {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file);
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file.getName()), fileUpload.getWidgetValues().toString());
+        fileUpload.getAdvancedCancelButton().click();
+        Assertions.assertTrue(fileUpload.getWidgetValues().isEmpty(), fileUpload.getWidgetValues().toString());
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    private void assertConfiguration(JSONObject cfg) {
+        System.out.println("FileInput Config = " + cfg);
+        Assertions.assertFalse(cfg.has("skinSimple"));
+        Assertions.assertFalse(cfg.has("auto"));
+        Assertions.assertFalse(cfg.getBoolean("dnd"));
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:fileupload")
+        FileUpload fileupload;
+
+        @FindBy(id = "form:uploadedfiles")
+        DataTable uploadedFiles;
+
+        @Override
+        public String getLocation() {
+            return "fileupload/fileUpload005.xhtml";
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload006Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload006Test.java
new file mode 100644
index 0000000000..ddf6a942e3
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload006Test.java
@@ -0,0 +1,145 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.fileupload;
+
+import java.io.File;
+import org.json.JSONObject;
+import org.junit.jupiter.api.*;
+import org.openqa.selenium.support.FindBy;
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.component.DataTable;
+import org.primefaces.selenium.component.FileUpload;
+
+/**
+ * Tests advanced multiple file upload.
+ * p:fileUpload mode=advanced auto=false multiple=true dragDropSupport=false (sequential=false)
+ */
+public class FileUpload006Test extends AbstractFileUploadTest {
+
+    @Test
+    @Order(1)
+    public void testAdvancedMultipleUploadSingleFile(Page page) throws Exception {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file);
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file.getName()), fileUpload.getWidgetValues().toString());
+        fileUpload.getAdvancedUploadButton().click();
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(2)
+    public void testAdvancedMultipleUploadMultipleFiles(Page page) throws Exception {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        File file2 = locateClientSideFile("file2.csv");
+        fileUpload.setValue(file1, file2);
+
+        // the widget shows only the first file name and the number of additional files
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file1.getName()), fileUpload.getWidgetValues().toString());
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file2.getName()), fileUpload.getWidgetValues().toString());
+        fileUpload.getAdvancedUploadButton().click();
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(3)
+    public void testAdvancedMultipleUploadMultipleFilesTwice(Page page) throws Exception {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        File file2 = locateClientSideFile("file2.csv");
+        fileUpload.setValue(file1, file2);
+
+        // the widget shows only the first file name and the number of additional files
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file1.getName()), fileUpload.getWidgetValues().toString());
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file2.getName()), fileUpload.getWidgetValues().toString());
+        fileUpload.getAdvancedUploadButton().click();
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+
+        // Act
+        File file3 = locateClientSideFile("file2.csv");
+        File file4 = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file3, file4);
+
+        // the widget shows only the first file name and the number of additional files
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file3.getName()), fileUpload.getWidgetValues().toString());
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file3.getName()), fileUpload.getWidgetValues().toString());
+        fileUpload.getAdvancedUploadButton().click();
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2, file3, file4);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    private void assertConfiguration(JSONObject cfg) {
+        System.out.println("FileInput Config = " + cfg);
+        Assertions.assertFalse(cfg.has("skinSimple"));
+        Assertions.assertFalse(cfg.has("auto"));
+        Assertions.assertFalse(cfg.getBoolean("dnd"));
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:fileupload")
+        FileUpload fileupload;
+
+        @FindBy(id = "form:uploadedfiles")
+        DataTable uploadedFiles;
+
+        @Override
+        public String getLocation() {
+            return "fileupload/fileUpload006.xhtml";
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload007Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload007Test.java
new file mode 100644
index 0000000000..06e994e8c3
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload007Test.java
@@ -0,0 +1,106 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.fileupload;
+
+import java.io.File;
+import org.json.JSONObject;
+import org.junit.jupiter.api.*;
+import org.openqa.selenium.support.FindBy;
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.component.DataTable;
+import org.primefaces.selenium.component.FileUpload;
+
+/**
+ * Tests advanced auto single file upload.
+ * p:fileUpload mode=advanced auto=true multiple=false dragDropSupport=false
+ */
+public class FileUpload007Test extends AbstractFileUploadTest {
+
+    @Test
+    @Order(1)
+    public void testAdvancedAutoSingleUpload(Page page) {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file);
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(2)
+    public void testAdvancedAutoSingleUploadTwice(Page page) {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file1);
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1);
+
+        // Act
+        File file2 = locateClientSideFile("file2.csv");
+        fileUpload.setValue(file2);
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    private void assertConfiguration(JSONObject cfg) {
+        System.out.println("FileInput Config = " + cfg);
+        Assertions.assertFalse(cfg.has("skinSimple"));
+        Assertions.assertTrue(cfg.getBoolean("auto"));
+        Assertions.assertFalse(cfg.getBoolean("dnd"));
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:fileupload")
+        FileUpload fileupload;
+
+        @FindBy(id = "form:uploadedfiles")
+        DataTable uploadedFiles;
+
+        @Override
+        public String getLocation() {
+            return "fileupload/fileUpload007.xhtml";
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload008Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload008Test.java
new file mode 100644
index 0000000000..6d35ed5bbd
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload008Test.java
@@ -0,0 +1,128 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.fileupload;
+
+import java.io.File;
+import org.json.JSONObject;
+import org.junit.jupiter.api.*;
+import org.openqa.selenium.support.FindBy;
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.component.DataTable;
+import org.primefaces.selenium.component.FileUpload;
+
+/**
+ * Tests advanced auto multiple file upload.
+ * p:fileUpload mode=advanced auto=true multiple=true dragDropSupport=false (sequential=false)
+ */
+public class FileUpload008Test extends AbstractFileUploadTest {
+
+    @Test
+    @Order(1)
+    public void testAdvancedAutoMultipleUploadSingleFile(Page page) {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file);
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(2)
+    public void testAdvancedAutoMultipleUploadMultipleFiles(Page page) throws Exception {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        File file2 = locateClientSideFile("file2.csv");
+        fileUpload.setValue(file1, file2);
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(3)
+    public void testAdvancedAutoMultipleUploadMultipleFilesTwice(Page page) throws Exception {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        File file2 = locateClientSideFile("file2.csv");
+        fileUpload.setValue(file1, file2);
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+
+        // Act
+        File file3 = locateClientSideFile("file2.csv");
+        File file4 = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file3, file4);
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2, file3, file4);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    private void assertConfiguration(JSONObject cfg) {
+        System.out.println("FileInput Config = " + cfg);
+        Assertions.assertFalse(cfg.has("skinSimple"));
+        Assertions.assertTrue(cfg.getBoolean("auto"));
+        Assertions.assertFalse(cfg.getBoolean("dnd"));
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:fileupload")
+        FileUpload fileupload;
+
+        @FindBy(id = "form:uploadedfiles")
+        DataTable uploadedFiles;
+
+        @Override
+        public String getLocation() {
+            return "fileupload/fileUpload008.xhtml";
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload009Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload009Test.java
new file mode 100644
index 0000000000..56209d9480
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload009Test.java
@@ -0,0 +1,145 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.fileupload;
+
+import java.io.File;
+import org.json.JSONObject;
+import org.junit.jupiter.api.*;
+import org.openqa.selenium.support.FindBy;
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.component.DataTable;
+import org.primefaces.selenium.component.FileUpload;
+
+/**
+ * Tests advanced multiple sequential file upload.
+ * p:fileUpload mode=advanced auto=false multiple=true dragDropSupport=false sequential=true
+ */
+public class FileUpload009Test extends AbstractFileUploadTest {
+
+    @Test
+    @Order(1)
+    public void testAdvancedMultipleUploadSingleFile(Page page) throws Exception {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file);
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file.getName()), fileUpload.getWidgetValues().toString());
+        fileUpload.getAdvancedUploadButton().click();
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(2)
+    public void testAdvancedMultipleUploadMultipleFiles(Page page) throws Exception {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        File file2 = locateClientSideFile("file2.csv");
+        fileUpload.setValue(file1, file2);
+
+        // the widget shows only the first file name and the number of additional files
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file1.getName()), fileUpload.getWidgetValues().toString());
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file2.getName()), fileUpload.getWidgetValues().toString());
+        fileUpload.getAdvancedUploadButton().click();
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(3)
+    public void testAdvancedMultipleUploadMultipleFilesTwice(Page page) throws Exception {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        File file2 = locateClientSideFile("file2.csv");
+        fileUpload.setValue(file1, file2);
+
+        // the widget shows only the first file name and the number of additional files
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file1.getName()), fileUpload.getWidgetValues().toString());
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file2.getName()), fileUpload.getWidgetValues().toString());
+        fileUpload.getAdvancedUploadButton().click();
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+
+        // Act
+        File file3 = locateClientSideFile("file2.csv");
+        File file4 = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file3, file4);
+
+        // the widget shows only the first file name and the number of additional files
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file3.getName()), fileUpload.getWidgetValues().toString());
+        Assertions.assertTrue(fileUpload.getWidgetValues().contains(file3.getName()), fileUpload.getWidgetValues().toString());
+        fileUpload.getAdvancedUploadButton().click();
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2, file3, file4);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    private void assertConfiguration(JSONObject cfg) {
+        System.out.println("FileInput Config = " + cfg);
+        Assertions.assertFalse(cfg.has("skinSimple"));
+        Assertions.assertFalse(cfg.has("auto"));
+        Assertions.assertFalse(cfg.getBoolean("dnd"));
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:fileupload")
+        FileUpload fileupload;
+
+        @FindBy(id = "form:uploadedfiles")
+        DataTable uploadedFiles;
+
+        @Override
+        public String getLocation() {
+            return "fileupload/fileUpload009.xhtml";
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload010Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload010Test.java
new file mode 100644
index 0000000000..2ae9cfe4bc
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/fileupload/FileUpload010Test.java
@@ -0,0 +1,129 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.fileupload;
+
+import java.io.File;
+import org.json.JSONObject;
+import org.junit.jupiter.api.*;
+import org.openqa.selenium.support.FindBy;
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.component.DataTable;
+import org.primefaces.selenium.component.FileUpload;
+
+/**
+ * Tests advanced auto multiple sequential file upload.
+ * p:fileUpload mode=advanced auto=true multiple=true dragDropSupport=false sequential=true
+ */
+public class FileUpload010Test extends AbstractFileUploadTest {
+
+    @Test
+    @Order(1)
+    public void testAdvancedAutoMultipleUploadSingleFile(Page page) {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file);
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(2)
+    public void testAdvancedAutoMultipleUploadMultipleFiles(Page page) throws Exception {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        File file2 = locateClientSideFile("file2.csv");
+        fileUpload.setValue(file1, file2);
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    @Test
+    @Order(3)
+    public void testAdvancedAutoMultipleUploadMultipleFilesTwice(Page page) throws Exception {
+        // Arrange
+        FileUpload fileUpload = page.fileupload;
+        Assertions.assertEquals("", fileUpload.getValue());
+
+        // Act
+        File file1 = locateClientSideFile("file1.csv");
+        File file2 = locateClientSideFile("file2.csv");
+        fileUpload.setValue(file1, file2);
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2);
+
+        // Act
+        File file3 = locateClientSideFile("file2.csv");
+        File file4 = locateClientSideFile("file1.csv");
+        fileUpload.setValue(file3, file4);
+        fileUpload.waitAdvancedUntilAllFilesAreUploaded(page.uploadedFiles);
+
+        // Assert
+        assertNoJavascriptErrors();
+        assertUploadedFiles(page.uploadedFiles, file1, file2, file3, file4);
+        assertConfiguration(fileUpload.getWidgetConfiguration());
+        Assertions.assertNotNull(fileUpload.getInput().getAttribute("multiple"));
+    }
+
+    private void assertConfiguration(JSONObject cfg) {
+        System.out.println("FileInput Config = " + cfg);
+        Assertions.assertFalse(cfg.has("skinSimple"));
+        Assertions.assertTrue(cfg.getBoolean("auto"));
+        Assertions.assertFalse(cfg.getBoolean("dnd"));
+        Assertions.assertTrue(cfg.getBoolean("sequentialUploads"));
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:fileupload")
+        FileUpload fileupload;
+
+        @FindBy(id = "form:uploadedfiles")
+        DataTable uploadedFiles;
+
+        @Override
+        public String getLocation() {
+            return "fileupload/fileUpload010.xhtml";
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/tree/AbstractTreeTest.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/tree/AbstractTreeTest.java
new file mode 100644
index 0000000000..09c73ef328
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/tree/AbstractTreeTest.java
@@ -0,0 +1,46 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.tree;
+
+import org.json.JSONObject;
+import org.junit.jupiter.api.Assertions;
+import org.primefaces.selenium.AbstractPrimePageTest;
+import org.primefaces.selenium.component.Messages;
+import org.primefaces.selenium.component.model.Msg;
+
+public abstract class AbstractTreeTest extends AbstractPrimePageTest {
+
+    protected void assertConfiguration(JSONObject cfg) {
+        assertNoJavascriptErrors();
+        System.out.println("Tree Config = " + cfg);
+        Assertions.assertEquals("tree", cfg.getString("widgetVar"));
+    }
+
+    protected void assertMessage(Messages messages, int index, String summary, String detail) {
+        Msg message = messages.getMessage(index);
+        Assertions.assertEquals(summary, message.getSummary());
+        Assertions.assertEquals(detail, message.getDetail());
+    }
+
+}
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/tree/Tree001Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/tree/Tree001Test.java
new file mode 100644
index 0000000000..a5241efc6a
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/tree/Tree001Test.java
@@ -0,0 +1,162 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.tree;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.support.FindBy;
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.PrimeSelenium;
+import org.primefaces.selenium.component.CommandButton;
+import org.primefaces.selenium.component.Messages;
+import org.primefaces.selenium.component.Tree;
+import org.primefaces.selenium.component.model.tree.TreeNode;
+
+public class Tree001Test extends AbstractTreeTest {
+
+    @Test
+    @Order(1)
+    @DisplayName("Tree: Basic & Toggling")
+    public void testBasicAndToggling(Page page) {
+        // Arrange
+        Tree tree = page.tree;
+        Assertions.assertNotNull(tree);
+
+        List<TreeNode> children = tree.getChildren();
+
+        Assertions.assertNotNull(children);
+        Assertions.assertEquals(3, children.size());
+
+        TreeNode first = children.get(0);
+        Assertions.assertEquals("Documents", first.getLabelText());
+
+        List<TreeNode> firstChildren = first.getChildren();
+        Assertions.assertNotNull(firstChildren);
+        Assertions.assertEquals(2, firstChildren.size());
+
+        TreeNode firstOfFirst = firstChildren.get(0);
+        Assertions.assertFalse(firstOfFirst.getWebElement().isDisplayed());
+
+        // Act
+        first.toggle();
+
+        // Assert
+        Assertions.assertTrue(firstOfFirst.getWebElement().isDisplayed());
+        Assertions.assertEquals("Work", firstOfFirst.getLabelText());
+        assertMessage(page.messages, 0, "Expanded", "Documents");
+
+        // Act Pt. 2
+        first.toggle();
+
+        // Assert Pt. 2
+        Assertions.assertFalse(firstOfFirst.getWebElement().isDisplayed());
+        assertMessage(page.messages, 0, "Collapsed", "Documents");
+
+        assertConfiguration(tree.getWidgetConfiguration());
+    }
+
+    @Test
+    @Order(2)
+    @DisplayName("Tree: Ajax update")
+    public void testAjaxUpdate(Page page) {
+        // Arrange
+        Tree tree = page.tree;
+        Assertions.assertNotNull(tree);
+
+        List<TreeNode> children = tree.getChildren();
+
+        Assertions.assertNotNull(children);
+        Assertions.assertEquals(3, children.size());
+
+        TreeNode first = children.get(0);
+        Assertions.assertEquals("Documents", first.getLabelText());
+
+        TreeNode second = children.get(1);
+        Assertions.assertEquals("Events", second.getLabelText());
+
+        TreeNode third = children.get(2);
+        Assertions.assertEquals("Movies", third.getLabelText());
+
+        first.toggle();
+
+        List<TreeNode> firstChildren = first.getChildren();
+        Assertions.assertNotNull(firstChildren);
+        Assertions.assertEquals(2, firstChildren.size());
+
+        TreeNode firstOfFirst = firstChildren.get(0);
+        Assertions.assertTrue(firstOfFirst.getWebElement().isDisplayed());
+
+        // Act
+        PrimeSelenium.guardAjax(page.buttonUpdate).click();
+
+        // Assert
+        children = tree.getChildren();
+
+        Assertions.assertNotNull(children);
+        Assertions.assertEquals(3, children.size());
+
+        first = children.get(0);
+        Assertions.assertEquals("Documents", first.getLabelText());
+
+        second = children.get(1);
+        Assertions.assertEquals("Events", second.getLabelText());
+
+        third = children.get(2);
+        Assertions.assertEquals("Movies", third.getLabelText());
+
+        Assertions.assertTrue(tree.isDisplayed());
+        Assertions.assertTrue(first.getWebElement().isDisplayed());
+        Assertions.assertTrue(second.getWebElement().isDisplayed());
+        Assertions.assertTrue(third.getWebElement().isDisplayed());
+
+        firstChildren = first.getChildren();
+        Assertions.assertNotNull(firstChildren);
+        Assertions.assertEquals(2, firstChildren.size());
+
+        firstOfFirst = firstChildren.get(0);
+        Assertions.assertTrue(firstOfFirst.getWebElement().isDisplayed());
+
+        assertConfiguration(tree.getWidgetConfiguration());
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:tree")
+        Tree tree;
+
+        @FindBy(id = "form:msgs")
+        Messages messages;
+
+        @FindBy(id = "form:buttonUpdate")
+        CommandButton buttonUpdate;
+
+        @Override
+        public String getLocation() {
+            return "tree/tree001.xhtml";
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/tree/Tree002Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/tree/Tree002Test.java
new file mode 100644
index 0000000000..3cdd7a81d1
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/tree/Tree002Test.java
@@ -0,0 +1,97 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.tree;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.interactions.Action;
+import org.openqa.selenium.interactions.Actions;
+import org.openqa.selenium.support.FindBy;
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.PrimeSelenium;
+import org.primefaces.selenium.component.CommandButton;
+import org.primefaces.selenium.component.Messages;
+import org.primefaces.selenium.component.Tree;
+import org.primefaces.selenium.component.model.tree.TreeNode;
+
+public class Tree002Test extends AbstractTreeTest {
+
+    @Test
+    @Order(1)
+    @DisplayName("Tree: Single selection")
+    public void testSingleSelection(Page page) {
+        // Arrange
+        Tree tree = page.tree;
+        Assertions.assertNotNull(tree);
+
+        List<TreeNode> children = tree.getChildren();
+
+        Assertions.assertNotNull(children);
+        Assertions.assertEquals(3, children.size());
+
+        TreeNode first = children.get(0);
+        Assertions.assertEquals("Documents", first.getLabelText());
+
+        // Act
+        first.select();
+
+        // Assert
+        assertMessage(page.messages, 0, "Selected", "Documents");
+        PrimeSelenium.guardAjax(page.buttonShowSelectedNode).click();
+        assertMessage(page.messages, 0, "Selected node", "Documents");
+
+        // Act Pt. 2
+        Actions actions = new Actions(page.getWebDriver());
+        Action actionUnselect = actions.keyDown(Keys.META).click(first.getLabel()).keyUp(Keys.META).build();
+        PrimeSelenium.guardAjax(actionUnselect).perform();
+
+        // Assert Pt. 2
+        assertMessage(page.messages, 0, "Unselected", "Documents");
+        PrimeSelenium.guardAjax(page.buttonShowSelectedNode).click();
+        assertMessage(page.messages, 0, "No node selected!", "");
+
+        assertConfiguration(tree.getWidgetConfiguration());
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:tree")
+        Tree tree;
+
+        @FindBy(id = "form:msgs")
+        Messages messages;
+
+        @FindBy(id = "form:buttonShowSelectedNode")
+        CommandButton buttonShowSelectedNode;
+
+        @Override
+        public String getLocation() {
+            return "tree/tree002.xhtml";
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/test/resources/org/primefaces/integrationtests/fileupload/file1.csv b/primefaces-integration-tests/src/test/resources/org/primefaces/integrationtests/fileupload/file1.csv
new file mode 100644
index 0000000000..82e2f33b30
--- /dev/null
+++ b/primefaces-integration-tests/src/test/resources/org/primefaces/integrationtests/fileupload/file1.csv
@@ -0,0 +1,1 @@
+another,csv,file
diff --git a/primefaces-integration-tests/src/test/resources/org/primefaces/integrationtests/fileupload/file2.csv b/primefaces-integration-tests/src/test/resources/org/primefaces/integrationtests/fileupload/file2.csv
new file mode 100644
index 0000000000..4620b51a05
--- /dev/null
+++ b/primefaces-integration-tests/src/test/resources/org/primefaces/integrationtests/fileupload/file2.csv
@@ -0,0 +1,1 @@
+a,csv,file
diff --git a/primefaces/src/test/java/org/primefaces/component/datatable/DataTableTest.java b/primefaces/src/test/java/org/primefaces/component/datatable/DataTableTest.java
index 298b41f5e1..9ea0dd9cdc 100644
--- a/primefaces/src/test/java/org/primefaces/component/datatable/DataTableTest.java
+++ b/primefaces/src/test/java/org/primefaces/component/datatable/DataTableTest.java
@@ -35,6 +35,7 @@ import javax.el.ValueExpression;
 import javax.faces.context.FacesContext;
 
 import static org.mockito.Mockito.*;
+import org.primefaces.component.api.UIColumn;
 import org.primefaces.component.api.UITable;
 
 public class DataTableTest {
@@ -47,24 +48,21 @@ public class DataTableTest {
 
     @Test
     public void testResolveStaticField() {
+        FacesContext context = new FacesContextMock();
         DataTable table = new DataTable();
         ValueExpression exprVE = mock(ValueExpression.class);
 
         when(exprVE.getExpressionString()).thenReturn("#{car.year}");
-        String field = UITable.resolveStaticField(exprVE);
-        Assertions.assertEquals("year", field);
+        Assertions.assertEquals("year", UIColumn.extractFieldFromValueExpression(context, exprVE, false));
 
         when(exprVE.getExpressionString()).thenReturn("#{car.wrapper.year}");
-        field = UITable.resolveStaticField(exprVE);
-        Assertions.assertEquals("wrapper.year", field);
+        Assertions.assertEquals("wrapper.year", UIColumn.extractFieldFromValueExpression(context, exprVE, false));
 
         when(exprVE.getExpressionString()).thenReturn("#{car}");
-        field = UITable.resolveStaticField(exprVE);
-        Assertions.assertEquals("car", field);
+        Assertions.assertEquals("car", UIColumn.extractFieldFromValueExpression(context, exprVE, false));
 
         when(exprVE.getExpressionString()).thenReturn("car.year");
-        field = UITable.resolveStaticField(exprVE);
-        Assertions.assertNull(field);
+        Assertions.assertNull(UIColumn.extractFieldFromValueExpression(context, exprVE, false));
     }
 
     @Test
@@ -83,12 +81,10 @@ public class DataTableTest {
         // old syntax
         ValueExpression exprVE = expFactory.createValueExpression(
                 context.getELContext(), "#{car[column.container.value]}", String.class);
-        String field = UITable.resolveDynamicField(context, exprVE);
-        Assertions.assertEquals("MyValue", field);
+        Assertions.assertEquals("MyValue", UIColumn.extractFieldFromValueExpression(context, exprVE, true));
 
         // new syntax
         exprVE = expFactory.createValueExpression(context.getELContext(), "#{column.container.value}", String.class);
-        field = UITable.resolveDynamicField(context, exprVE);
-        Assertions.assertEquals("MyValue", field);
+        //Assertions.assertEquals("MyValue", UITable.extractFieldForColumn(context, exprVE, true));
     }
 }