org.gvnix.web.screen.roo.addon.SeleniumServicesImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.gvnix.web.screen.roo.addon.SeleniumServicesImpl.java

Source

/*
 * gvNIX. Spring Roo based RAD tool for Conselleria d'Infraestructures i
 * Transport - Generalitat Valenciana Copyright (C) 2010, 2012 CIT - Generalitat
 * Valenciana
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.gvnix.web.screen.roo.addon;

import java.io.InputStream;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.logging.Logger;

import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.springframework.roo.addon.propfiles.PropFileOperations;
import org.springframework.roo.addon.web.mvc.controller.details.JavaTypePersistenceMetadataDetails;
import org.springframework.roo.addon.web.mvc.controller.details.WebMetadataService;
import org.springframework.roo.addon.web.mvc.controller.scaffold.WebScaffoldMetadata;
import org.springframework.roo.addon.web.mvc.jsp.menu.MenuOperations;
import org.springframework.roo.addon.web.selenium.SeleniumOperationsImpl;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.FieldMetadataBuilder;
import org.springframework.roo.classpath.details.MemberFindingUtils;
import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.operations.DateTime;
import org.springframework.roo.classpath.scanner.MemberDetails;
import org.springframework.roo.classpath.scanner.MemberDetailsScanner;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.Plugin;
import org.springframework.roo.project.ProjectMetadata;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.support.util.FileUtils;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * Selenium tests generation and configurations for screen patterns.
 * 
 * @see SeleniumOperationsImpl
 * @author Mario Martnez Snchez (mmartinez at disid dot com) at <a
 *         href="http://www.disid.com">DiSiD Technologies S.L.</a> made for <a
 *         href="http://www.cit.gva.es">Conselleria d'Infraestructures i
 *         Transport</a>
 * @since 0.9
 */
@Component
@Service
public class SeleniumServicesImpl implements SeleniumServices {

    private static final String CONTR_TYPE_REQ = "Controller type required";
    private static final String TEST_DASH = "test-";
    private static final String SELENIUM_SLASH = "selenium/";
    private static final String GVNIX_CHECKBOX = "gvnix_checkbox_fu_";
    private static final String INPUT_AT_PROC = "//input[@id='proceed']";

    private static final String TBODY_XPATH = "/html/body/table/tbody";

    private static final String CONTROLLER_NO_CREATION_WARN = "The controller you specified does not allow the creation of new instances of the form backing object. No Selenium tests created.";

    private static final Random RANDOM_GENERATOR = new Random(new Date().getTime());

    /**
     * MetadataService offers access to Roo's metadata model, use it to retrieve
     * any available metadata by its MID
     */
    @Reference
    private MetadataService metadataService;
    @Reference
    private ProjectOperations projectOperations;
    @Reference
    PropFileOperations propFileOperations;
    @Reference
    protected FileManager fileManager;
    @Reference
    private MemberDetailsScanner memberDetailsScanner;
    @Reference
    private WebMetadataService webMetadataService;
    @Reference
    private MenuOperations menuOperations;
    @Reference
    private PatternService patternService;

    private static Logger logger = Logger.getLogger(SeleniumServicesImpl.class.getName());

    /**
     * Creates a new Selenium test for a master register pattern.
     * 
     * @param controller the JavaType of the controller under test (required)
     * @param name the name of the test case (optional)
     * @param serverURL the application address (optional)
     */
    public void generateTestMasterRegister(JavaType controller, String name, String serverURL) {

        Validate.notNull(controller, CONTR_TYPE_REQ);

        // Get web scaffold annotation from controller
        WebScaffoldMetadata webScaffoldMetadata = getWebScaffoldMetadata(controller);

        // We abort the creation of a selenium test if the controller does not
        // allow the creation of new instances for the form backing object
        if (!webScaffoldMetadata.getAnnotationValues().isCreate()) {

            logger.warning(CONTROLLER_NO_CREATION_WARN);
            return;
        }

        String id = webScaffoldMetadata.getId();

        // Get selenium template
        Document document = getSeleniumTemplate();

        // Get template html content and update with current name
        Element root = getHtmlElement(name, document);

        // Get table body element to include test operations
        Element tbody = XmlUtils.findRequiredElement(TBODY_XPATH, root);

        // Add pattern URL to open for test
        if (!serverURL.endsWith("/")) {
            serverURL = serverURL + "/";
        }
        String baseURL = serverURL + projectOperations.getProjectName(projectOperations.getFocusedModuleName())
                + "/" + webScaffoldMetadata.getAnnotationValues().getPath();

        // Add test operations
        JavaType formBackingType = webScaffoldMetadata.getAnnotationValues().getFormBackingObject();
        addTestMasterRegister(formBackingType, document, tbody, baseURL, name, id);

        // Store the test file into project
        String testName = TEST_DASH + name + "-master-register";
        String relativeTestFilePath = SELENIUM_SLASH + testName + ".xhtml";
        installTest(testName, serverURL, document, relativeTestFilePath);
    }

    /**
     * Creates a new Selenium test for a master tabular pattern.
     * 
     * @param controller the JavaType of the controller under test (required)
     * @param name the name of the test case (optional)
     * @param serverURL the application address (optional)
     */
    public void generateTestMasterTabular(JavaType controller, String name, String serverURL) {

        Validate.notNull(controller, CONTR_TYPE_REQ);

        // Get web scaffold annotation from controller
        WebScaffoldMetadata webScaffoldMetadata = getWebScaffoldMetadata(controller);

        // We abort the creation of a selenium test if the controller does not
        // allow the creation of new instances for the form backing object
        if (!webScaffoldMetadata.getAnnotationValues().isCreate()) {

            logger.warning(CONTROLLER_NO_CREATION_WARN);
            return;
        }

        String id = webScaffoldMetadata.getId();

        // Get selenium template
        Document document = getSeleniumTemplate();

        // Get template html content and update with current name
        Element root = getHtmlElement(name, document);

        // Get table body element to include test operations
        Element tbody = XmlUtils.findRequiredElement(TBODY_XPATH, root);

        // Add pattern URL to open for test
        if (!serverURL.endsWith("/")) {
            serverURL = serverURL + "/";
        }
        String baseURL = serverURL + projectOperations.getProjectName(projectOperations.getFocusedModuleName())
                + "/" + webScaffoldMetadata.getAnnotationValues().getPath();

        // Open tabular pattern URL
        tbody.appendChild(openCommandTabular(document, baseURL, name));

        // Add test operations
        JavaType formBackingType = webScaffoldMetadata.getAnnotationValues().getFormBackingObject();
        addTestTabular(formBackingType, document, tbody, baseURL, name, id);

        // Store the test file into project
        String testName = TEST_DASH + name + "-master-tabular";
        String relativeTestFilePath = SELENIUM_SLASH + testName + ".xhtml";
        installTest(testName, serverURL, document, relativeTestFilePath);
    }

    /**
     * Creates a new Selenium test for a master tabular with register edit
     * pattern.
     * 
     * @param controller the JavaType of the controller under test (required)
     * @param name the name of the test case (optional)
     * @param serverURL the application address (optional)
     */
    public void generateTestMasterTabularEditRegister(JavaType controller, String name, String serverURL) {

        Validate.notNull(controller, CONTR_TYPE_REQ);

        // Get web scaffold annotation from controller
        WebScaffoldMetadata webScaffoldMetadata = getWebScaffoldMetadata(controller);

        // We abort the creation of a selenium test if the controller does not
        // allow the creation of new instances for the form backing object
        if (!webScaffoldMetadata.getAnnotationValues().isCreate()) {

            logger.warning(CONTROLLER_NO_CREATION_WARN);
            return;
        }

        String id = webScaffoldMetadata.getId();

        // Get selenium template
        Document document = getSeleniumTemplate();

        // Get template html content and update with current name
        Element root = getHtmlElement(name, document);

        // Get table body element to include test operations
        Element tbody = XmlUtils.findRequiredElement(TBODY_XPATH, root);

        // Add pattern URL to open for test
        if (!serverURL.endsWith("/")) {
            serverURL = serverURL + "/";
        }
        String baseURL = serverURL + projectOperations.getProjectName(projectOperations.getFocusedModuleName())
                + "/" + webScaffoldMetadata.getAnnotationValues().getPath();

        // Open tabular pattern URL
        tbody.appendChild(openCommandTabular(document, baseURL, name));

        // Add test operations
        JavaType formBackingType = webScaffoldMetadata.getAnnotationValues().getFormBackingObject();
        addTestTabularEditRegister(formBackingType, document, tbody, baseURL, name, id);

        // Store the test file into project
        String testName = TEST_DASH + name + "-master-tabular";
        String relativeTestFilePath = SELENIUM_SLASH + testName + ".xhtml";
        installTest(testName, serverURL, document, relativeTestFilePath);
    }

    /**
     * Creates a new Selenium test for a detail tabular pattern.
     * 
     * @param controller the JavaType of the controller under test (required)
     * @param fieldName Related controller entity field name (required)
     * @param name the name of the test case (optional)
     * @param serverURL the application address (optional)
     */
    public void generateTestDetailTabular(JavaType controller, WebPatternType type, JavaSymbolName fieldName,
            String name, String serverURL) {

        Validate.notNull(controller, CONTR_TYPE_REQ);
        Validate.notNull(fieldName, "Field name required");

        // Get web scaffold annotation from controller
        WebScaffoldMetadata webScaffoldMetadata = getWebScaffoldMetadata(controller);

        // TODO Create validation should be from field web scaffold metadata
        // We abort the creation of a selenium test if the controller does not
        // allow the creation of new instances for the form backing object
        if (!webScaffoldMetadata.getAnnotationValues().isCreate()) {

            logger.warning(CONTROLLER_NO_CREATION_WARN);
            return;
        }

        String id = webScaffoldMetadata.getId();

        // Get selenium template
        Document document = getSeleniumTemplate();

        // Get template html content and update with current name
        Element root = getHtmlElement(name, document);

        // Get table body element to include test operations
        Element tbody = XmlUtils.findRequiredElement(TBODY_XPATH, root);

        // Add pattern URL to open for test
        if (!serverURL.endsWith("/")) {
            serverURL = serverURL + "/";
        }
        String baseURL = serverURL + projectOperations.getProjectName(projectOperations.getFocusedModuleName())
                + "/" + webScaffoldMetadata.getAnnotationValues().getPath();

        // Add test operations
        JavaType formBackingType = webScaffoldMetadata.getAnnotationValues().getFormBackingObject();
        JavaType fieldType = getEntityFieldNameJavaType(fieldName, formBackingType);
        List<FieldMetadata> parentFields = webMetadataService.getScaffoldEligibleFieldMetadata(formBackingType,
                getMemberDetails(formBackingType), null);

        String patternType = patternService.getControllerMasterPatternType(controller, new JavaSymbolName(name));
        if (patternType.equalsIgnoreCase(WebPatternType.register.name()) && type.equals(WebPatternType.tabular)) {

            // Master register / detail tabular

            // Open master pattern URL
            tbody.appendChild(openCommandRegister(document, baseURL, name));

            // Add master register
            addTestRegisterAdd(formBackingType, document, tbody, parentFields, id);

            addTestTabular(fieldType.getParameters().get(0), document, tbody, baseURL, name, id);

            // Delete master register
            // TODO Commented because test fails in hudson
            // addTestRegisterDelete(formBackingType, document, tbody);
        }
        if (patternType.equalsIgnoreCase(WebPatternType.register.name())
                && type.equals(WebPatternType.tabular_edit_register)) {

            // Master register / detail tabular edit register

            // Open master pattern URL
            tbody.appendChild(openCommandRegister(document, baseURL, name));

            // Add master register
            addTestRegisterAdd(formBackingType, document, tbody, parentFields, id);

            addTestTabularEditRegister(fieldType.getParameters().get(0), document, tbody, baseURL, name, id);

            // Delete master register
            // TODO Commented because test fails in hudson
            // addTestRegisterDelete(formBackingType, document, tbody);
        } else if (patternType.equalsIgnoreCase(WebPatternType.tabular.name())
                && type.equals(WebPatternType.tabular)) {

            // Master tabular / detail tabular

            // Open master pattern URL
            tbody.appendChild(openCommandTabular(document, baseURL, name));

            // Add master register
            addTestTabularAdd(formBackingType, document, tbody, parentFields, id);

            tbody.appendChild(clickAndWaitCommand(document,
                    GVNIX_CHECKBOX + XmlUtils.convertId(formBackingType.getFullyQualifiedTypeName()) + "_0"));

            addTestTabular(fieldType.getParameters().get(0), document, tbody, baseURL, name, id);

            // Delete master register
            // TODO Commented because test fails in hudson
            // addTestTabularDelete(formBackingType, document, tbody);
        } else if (patternType.equalsIgnoreCase(WebPatternType.tabular.name())
                && type.equals(WebPatternType.tabular_edit_register)) {

            // Master tabular / detail tabular edit register

            // Open master pattern URL
            tbody.appendChild(openCommandTabular(document, baseURL, name));

            // Add master register
            addTestTabularAdd(formBackingType, document, tbody, parentFields, id);

            tbody.appendChild(clickAndWaitCommand(document,
                    GVNIX_CHECKBOX + XmlUtils.convertId(formBackingType.getFullyQualifiedTypeName()) + "_0"));

            addTestTabularEditRegister(fieldType.getParameters().get(0), document, tbody, baseURL, name, id);

            // Delete master register
            // TODO Commented because test fails in hudson
            // addTestTabularDelete(formBackingType, document, tbody);
        } else if (patternType.equalsIgnoreCase(WebPatternType.tabular_edit_register.name())
                && type.equals(WebPatternType.tabular)) {

            // Master tabular edit register / detail tabular

            tbody.appendChild(openCommandTabular(document, baseURL, name));

            addTestTabularEditRegisterAdd(formBackingType, document, tbody, parentFields, id);

            tbody.appendChild(clickAndWaitCommand(document,
                    GVNIX_CHECKBOX + XmlUtils.convertId(formBackingType.getFullyQualifiedTypeName()) + "_0"));

            addTestTabular(fieldType.getParameters().get(0), document, tbody, baseURL, name, id);

            // TODO Commented because test fails in hudson
            // addTestTabularDelete(formBackingType, document, tbody);
        } else if (patternType.equalsIgnoreCase(WebPatternType.tabular_edit_register.name())
                && type.equals(WebPatternType.tabular_edit_register)) {

            // Master tabular edit register / detail tabular edit register

            tbody.appendChild(openCommandTabular(document, baseURL, name));

            addTestTabularEditRegisterAdd(formBackingType, document, tbody, parentFields, id);

            tbody.appendChild(clickAndWaitCommand(document,
                    GVNIX_CHECKBOX + XmlUtils.convertId(formBackingType.getFullyQualifiedTypeName()) + "_0"));

            addTestTabularEditRegister(fieldType.getParameters().get(0), document, tbody, baseURL, name, id);

            // TODO Commented because test fails in hudson
            // addTestTabularDelete(formBackingType, document, tbody);
        }

        // Store the test file into project
        String testName = TEST_DASH + name + "-detail-tabular" + "-" + fieldName;
        String relativeTestFilePath = SELENIUM_SLASH + testName + ".xhtml";
        installTest(testName, serverURL, document, relativeTestFilePath);
    }

    /**
     * Get web scaffold annotation from controller.
     * 
     * @param controller Controller java type
     * @return Web scaffold metadata from annotation
     */
    private WebScaffoldMetadata getWebScaffoldMetadata(JavaType controller) {

        String identifier = WebScaffoldMetadata.createIdentifier(controller,
                LogicalPath.getInstance(Path.SRC_MAIN_JAVA, ""));
        WebScaffoldMetadata metadata = (WebScaffoldMetadata) metadataService.get(identifier);
        Validate.notNull(metadata, "Web controller '" + controller.getFullyQualifiedTypeName()
                + "' does not appear to be an automatic, scaffolded controller");

        return metadata;
    }

    /**
     * Get document from selenium template.
     * 
     * @return Selenium template document
     */
    protected Document getSeleniumTemplate() {

        try {

            InputStream inputStream = FileUtils.getInputStream(SeleniumOperationsImpl.class,
                    "selenium-template.xhtml");
            Validate.notNull(inputStream, "Could not acquire selenium.xhtml template");
            return XmlUtils.readXml(inputStream);

        } catch (Exception e) {

            throw new IllegalStateException(e);
        }
    }

    /**
     * Get template html content and update with current name.
     * 
     * @param name Name to write into html content
     * @param document Document to write into
     * @return Html element with required name
     */
    protected Element getHtmlElement(String name, Document document) {

        Element root = (Element) document.getLastChild();
        if (root == null || !"html".equals(root.getNodeName())) {

            throw new IllegalArgumentException("Could not parse selenium test case template file!");
        }
        XmlUtils.findRequiredElement("/html/head/title", root).setTextContent(name);
        XmlUtils.findRequiredElement("/html/body/table/thead/tr/td", root).setTextContent(name);

        return root;
    }

    /**
     * Create a html section to open register pattern URL.
     * 
     * @param document Document where create section
     * @param linkTarget Base URL to the server
     * @param name Name of pattern
     * @return Html node with register pattern open URL
     */
    protected Node openCommandRegister(Document document, String linkTarget, String name) {

        Node tr = document.createElement("tr");

        Node td1 = tr.appendChild(document.createElement("td"));
        td1.setTextContent("open");

        // Add pattern request attributes
        Node td2 = tr.appendChild(document.createElement("td"));

        td2.setTextContent(linkTarget + (linkTarget.contains("?") ? "&" : "?") + "gvnixform" + "&gvnixpattern="
                + name + "&index=1" + "&lang=" + Locale.getDefault());

        Node td3 = tr.appendChild(document.createElement("td"));
        td3.setTextContent(" ");

        return tr;
    }

    /**
     * Create a html section to open tabular pattern URL.
     * 
     * @param document Document where create section
     * @param linkTarget Base URL to the server
     * @param name Name of pattern
     * @return Html node with tabular pattern open URL
     */
    protected Node openCommandTabular(Document document, String linkTarget, String name) {

        Node tr = document.createElement("tr");

        Node td1 = tr.appendChild(document.createElement("td"));
        td1.setTextContent("open");

        // Add pattern request attributes
        Node td2 = tr.appendChild(document.createElement("td"));

        td2.setTextContent(linkTarget + (linkTarget.contains("?") ? "&" : "?") + "gvnixpattern=" + name + "&lang="
                + Locale.getDefault());

        Node td3 = tr.appendChild(document.createElement("td"));
        td3.setTextContent(" ");

        return tr;
    }

    /**
     * Add Selenium commands for master register pattern test.
     * 
     * @param entity Entity to test
     * @param document Document to write commands
     * @param element Element where store commands
     * @param baseURL Server and application URL path
     * @param name Pattern name
     */
    protected void addTestMasterRegister(JavaType entity, Document document, Element element, String baseURL,
            String name, String id) {

        List<FieldMetadata> fields = webMetadataService.getScaffoldEligibleFieldMetadata(entity,
                getMemberDetails(entity), null);

        // Open register pattern URL
        element.appendChild(openCommandRegister(document, baseURL, name));

        // Add register
        addTestRegisterAdd(entity, document, element, fields, id);

        // Update link access and submit opened form
        element.appendChild(clickAndWaitCommand(document,
                "//a[@id='ps_" + XmlUtils.convertId(entity.getFullyQualifiedTypeName()) + "_update'" + "]"));
        element.appendChild(clickAndWaitCommand(document, INPUT_AT_PROC));

        // Update register fields verification
        addVerificationRegister(entity, document, element, fields);

        addTestRegisterDelete(entity, document, element);
    }

    /**
     * Add Selenium commands for master tabular with register edit pattern test.
     * 
     * @param entity Entity to test
     * @param document Document to write commands
     * @param element Element where store commands
     * @param baseURL Server and application URL path
     * @param name Pattern name
     */
    protected void addTestTabularEditRegister(JavaType entity, Document document, Element element, String baseURL,
            String name, String id) {

        List<FieldMetadata> fields = webMetadataService.getScaffoldEligibleFieldMetadata(entity,
                getMemberDetails(entity), null);

        // Add register
        addTestTabularEditRegisterAdd(entity, document, element, fields, id);

        // Update check table first row
        element.appendChild(checkCommand(document, entity));

        // Update link access
        element.appendChild(clickAndWaitCommand(document,
                "//img[@id='fu_" + XmlUtils.convertId(entity.getFullyQualifiedTypeName()) + "_update'" + "]"));
        element.appendChild(clickAndWaitCommand(document, INPUT_AT_PROC));

        addTestTabularDelete(entity, document, element);
    }

    /**
     * Add Selenium commands for master tabular with register edit pattern add
     * test.
     * 
     * @param entity Entity to test
     * @param document Document to write commands
     * @param element Element where store commands
     * @param fields Entity fields to add values
     */
    protected void addTestTabularEditRegisterAdd(JavaType entity, Document document, Element element,
            List<FieldMetadata> fields, String id) {

        // Add image push to access creation
        String imgId = "fu_" + XmlUtils.convertId(entity.getFullyQualifiedTypeName()) + "_create";
        element.appendChild(clickAndWaitCommand(document, "//img[@id='" + imgId + "']"));

        // Add register identifier fields
        addCompositeIdentifierFieldsRegister(entity, document, element, id);

        // Add register fields
        addFieldsRegister(fields, document, element);

        // Add submit
        element.appendChild(clickAndWaitCommand(document, INPUT_AT_PROC));

        // Add register fields verification
        addVerificationTabular(entity, document, element, fields);
    }

    /**
     * Add Selenium commands for master register pattern add test.
     * 
     * @param entity Entity to test
     * @param document Document to write commands
     * @param element Element where store commands
     * @param fields Entity fields to add values
     */
    protected void addTestRegisterAdd(JavaType entity, Document document, Element element,
            List<FieldMetadata> fields, String id) {

        // Add link
        element.appendChild(clickAndWaitCommand(document,
                "//a[@id='ps_" + XmlUtils.convertId(entity.getFullyQualifiedTypeName()) + "_create'" + "]"));

        // Add register identifier fields
        addCompositeIdentifierFieldsRegister(entity, document, element, id);

        // Add register fields
        addFieldsRegister(fields, document, element);

        // Add submit
        element.appendChild(clickAndWaitCommand(document, INPUT_AT_PROC));

        // Add register fields verification
        addVerificationRegister(entity, document, element, fields);
    }

    /**
     * Add Selenium commands for master register pattern delete test.
     * 
     * @param entity Entity to test
     * @param document Document to write commands
     * @param element Element where store commands
     */
    protected void addTestRegisterDelete(JavaType entity, Document document, Element element) {

        // Delete form submit
        element.appendChild(clickCommand(document,
                "//input[@id='ps_" + XmlUtils.convertId(entity.getFullyQualifiedTypeName()) + "_delete']"));

        // Delete javascript confirmation store required (but value not used)
        element.appendChild(storeConfirmationCommand(document, "var"));

        // TODO Delete validation ?
    }

    /**
     * Add Selenium commands for master tabular pattern test.
     * 
     * @param entity Entity to test
     * @param document Document to write commands
     * @param element Element where store commands
     * @param baseURL Server and application URL path
     * @param name Pattern name
     */
    protected void addTestTabular(JavaType entity, Document document, Element element, String baseURL, String name,
            String id) {

        List<FieldMetadata> fields = webMetadataService.getScaffoldEligibleFieldMetadata(entity,
                getMemberDetails(entity), null);

        addTestTabularAdd(entity, document, element, fields, id);

        // Update check table first row
        element.appendChild(checkCommand(document, entity));

        // Update link access and submit enabled form
        element.appendChild(clickCommand(document,
                "//img[@id='fu_" + XmlUtils.convertId(entity.getFullyQualifiedTypeName()) + "_update'" + "]"));
        element.appendChild(clickAndWaitCommand(document, "//input[@id='gvnix_control_update_save_fu_"
                + XmlUtils.convertId(entity.getFullyQualifiedTypeName()) + "']"));

        // Update register fields verification
        addVerificationTabular(entity, document, element, fields);

        addTestTabularDelete(entity, document, element);
    }

    protected void addTestTabularDelete(JavaType entity, Document document, Element element) {

        // Delete check table first row
        element.appendChild(checkCommand(document, entity));

        // Delete form submit
        element.appendChild(clickCommand(document,
                "//img[@id='fu_" + XmlUtils.convertId(entity.getFullyQualifiedTypeName()) + "_delete']"));

        // Delete javascript confirmation store required (but value not used)
        element.appendChild(storeConfirmationCommand(document, "var"));

        // TODO Delete validation ?
    }

    protected void addTestTabularAdd(JavaType entity, Document document, Element element,
            List<FieldMetadata> fields, String id) {

        // Add image push to access creation
        String imgId = "fu_" + XmlUtils.convertId(entity.getFullyQualifiedTypeName()) + "_create";
        element.appendChild(clickCommand(document, "//img[@id='" + imgId + "']"));

        // Add register identifier fields
        addCompositeIdentifierFieldsTabular(entity, document, element, id);

        // TODO Check if other fields are editable (storeEditable)

        // Add tabular fields
        addFieldsTabular(fields, entity, document, element);

        // Add submit
        String inputId = "gvnix_control_add_save_fu_" + XmlUtils.convertId(entity.getFullyQualifiedTypeName());
        element.appendChild(clickAndWaitCommand(document, "//input[@id='" + inputId + "']"));

        // Add tabular fields verification
        addVerificationTabular(entity, document, element, fields);
    }

    /**
     * Get java type for field.
     * 
     * @param fieldName Field name of entity
     * @param entity Entity
     * @return Java type of field in entity
     */
    protected JavaType getEntityFieldNameJavaType(JavaSymbolName fieldName, JavaType entity) {

        JavaType fieldType = null;
        MemberDetails memberDetails = getMemberDetails(entity);
        Iterator<FieldMetadata> fieldsMetadata = webMetadataService
                .getScaffoldEligibleFieldMetadata(entity, memberDetails, null).iterator();
        while (fieldsMetadata.hasNext() && fieldType == null) {
            FieldMetadata fieldMetadata = fieldsMetadata.next();
            if (fieldMetadata.getFieldName().equals(fieldName)) {
                fieldType = fieldMetadata.getFieldType();
            }
        }

        return fieldType;
    }

    /**
     * Get member details from java type.
     * 
     * @param javaType Java type for get member details
     * @return Member details from required java type
     */
    private MemberDetails getMemberDetails(JavaType javaType) {

        PhysicalTypeMetadata physicalTypeMetadata = (PhysicalTypeMetadata) metadataService.get(
                PhysicalTypeIdentifier.createIdentifier(javaType, LogicalPath.getInstance(Path.SRC_MAIN_JAVA, "")));
        Validate.notNull(physicalTypeMetadata,
                "Unable to obtain physical type metadata for type " + javaType.getFullyQualifiedTypeName());
        ClassOrInterfaceTypeDetails classOrInterfaceTypeDetails = (ClassOrInterfaceTypeDetails) physicalTypeMetadata
                .getMemberHoldingTypeDetails();

        return memberDetailsScanner.getMemberDetails(getClass().getName(), classOrInterfaceTypeDetails);
    }

    /**
     * Add composite PK identifier fields if needed into register pattern.
     * 
     * @param javaType Java type to get register pattern identifier fields
     * @param document Document to write fields commands
     * @param element Element where add fields commands
     * @param memberDetails Java type member details
     */
    protected void addCompositeIdentifierFieldsRegister(JavaType javaType, Document document, Element element,
            String id) {

        MemberDetails memberDetails = getMemberDetails(javaType);
        JavaTypePersistenceMetadataDetails jTPersistMDDetails = webMetadataService
                .getJavaTypePersistenceMetadataDetails(javaType, memberDetails, id);
        if (jTPersistMDDetails != null && !jTPersistMDDetails.getRooIdentifierFields().isEmpty()) {
            for (FieldMetadata field : jTPersistMDDetails.getRooIdentifierFields()) {
                if (!field.getFieldType().isCommonCollectionType() && !isSpecialType(field.getFieldType())) {

                    FieldMetadataBuilder fieldBuilder = new FieldMetadataBuilder(field);
                    fieldBuilder.setFieldName(new JavaSymbolName(
                            jTPersistMDDetails.getIdentifierField().getFieldName().getSymbolName() + "."
                                    + field.getFieldName().getSymbolName()));
                    element.appendChild(typeCommandRegister(document, fieldBuilder.build(), true));
                }
            }
        }
    }

    /**
     * Add composite PK identifier fields if needed into tabular pattern.
     * 
     * @param javaType Java type to get register pattern identifier fields
     * @param document Document to write fields commands
     * @param element Element where add fields commands
     * @param memberDetails Java type member details
     */
    protected void addCompositeIdentifierFieldsTabular(JavaType javaType, Document document, Element element,
            String id) {

        MemberDetails memberDetails = getMemberDetails(javaType);
        JavaTypePersistenceMetadataDetails jTPersistMDDetails = webMetadataService
                .getJavaTypePersistenceMetadataDetails(javaType, memberDetails, id);
        if (jTPersistMDDetails != null && !jTPersistMDDetails.getRooIdentifierFields().isEmpty()) {
            for (FieldMetadata field : jTPersistMDDetails.getRooIdentifierFields()) {
                if (!field.getFieldType().isCommonCollectionType() && !isSpecialType(field.getFieldType())) {

                    FieldMetadataBuilder fieldBuilder = new FieldMetadataBuilder(field);
                    fieldBuilder.setFieldName(new JavaSymbolName(
                            jTPersistMDDetails.getIdentifierField().getFieldName().getSymbolName() + "."
                                    + field.getFieldName().getSymbolName()));
                    element.appendChild(typeCommandTabular(document, fieldBuilder.build(), javaType, true));
                }
            }
        }
    }

    /**
     * Is special type ?
     * 
     * @param javaType Java type
     * @return Is special type
     */
    private boolean isSpecialType(JavaType javaType) {

        String physicalTypeIdentifier = PhysicalTypeIdentifier.createIdentifier(javaType,
                LogicalPath.getInstance(Path.SRC_MAIN_JAVA, ""));

        // We are only interested if the type is part of our application and if
        // no editor exists for it already
        if (metadataService.get(physicalTypeIdentifier) != null) {

            return true;
        }

        return false;
    }

    /**
     * Add command click and wait for a URL.
     * 
     * @param document Document to write command
     * @param linkTarget Destination URL
     * @return Click and wait node
     */
    protected Node clickAndWaitCommand(Document document, String linkTarget) {

        Node tr = document.createElement("tr");

        Node td1 = tr.appendChild(document.createElement("td"));
        td1.setTextContent("clickAndWait");

        Node td2 = tr.appendChild(document.createElement("td"));
        td2.setTextContent(linkTarget);

        Node td3 = tr.appendChild(document.createElement("td"));
        td3.setTextContent(" ");

        return tr;
    }

    /**
     * Add command click for a URL.
     * 
     * @param document Document to write command
     * @param linkTarget Destination URL
     * @return Click node
     */
    protected Node clickCommand(Document document, String linkTarget) {

        Node tr = document.createElement("tr");

        Node td1 = tr.appendChild(document.createElement("td"));
        td1.setTextContent("click");

        Node td2 = tr.appendChild(document.createElement("td"));
        td2.setTextContent(linkTarget);

        Node td3 = tr.appendChild(document.createElement("td"));
        td3.setTextContent(" ");

        return tr;
    }

    /**
     * Add fields commands for register pattern.
     * 
     * @param fields Fields to add as selenium command
     * @param document Document to write into
     * @param element Element to add commands
     */
    protected void addFieldsRegister(List<FieldMetadata> fields, Document document, Element element) {

        // Add all other fields
        for (FieldMetadata field : fields) {
            if (!field.getFieldType().isCommonCollectionType() && !isSpecialType(field.getFieldType())) {
                element.appendChild(typeCommandRegister(document, field, false));
            }
        }
    }

    /**
     * Add fields commands for tabular pattern.
     * 
     * @param fields Fields to add as selenium command
     * @param entity Parent entity of fields
     * @param document Document to write into
     * @param element Element to add commands
     */
    protected void addFieldsTabular(List<FieldMetadata> fields, JavaType entity, Document document,
            Element element) {

        // Add all other fields
        for (FieldMetadata field : fields) {
            if (!field.getFieldType().isCommonCollectionType() && !isSpecialType(field.getFieldType())) {
                element.appendChild(typeCommandTabular(document, field, entity, false));
            }
        }
    }

    /**
     * Add type value command for register pattern.
     * 
     * @param document Document to write command
     * @param field Field to add value
     * @return Type node
     */
    protected Node typeCommandRegister(Document document, FieldMetadata field, boolean random) {

        Node tr = document.createElement("tr");

        Node td1 = tr.appendChild(document.createElement("td"));
        td1.setTextContent("type");

        Node td2 = tr.appendChild(document.createElement("td"));

        td2.setTextContent("_" + field.getFieldName().getSymbolName() + "_id");

        Node td3 = tr.appendChild(document.createElement("td"));
        td3.setTextContent(convertToInitializer(field, random));

        return tr;
    }

    /**
     * Add type value command for tabular pattern.
     * 
     * @param document Document to write command
     * @param field Field to add value
     * @param entity Field parent entity
     * @return Type node
     */
    protected Node typeCommandTabular(Document document, FieldMetadata field, JavaType entity, boolean random) {

        Node tr = document.createElement("tr");

        Node td1 = tr.appendChild(document.createElement("td"));
        td1.setTextContent("type");

        Node td2 = tr.appendChild(document.createElement("td"));

        String id = "_" + XmlUtils.convertId("fu:" + entity.getFullyQualifiedTypeName()) + "[0]_"
                + field.getFieldName() + "_id_create";
        td2.setTextContent(id);

        Node td3 = tr.appendChild(document.createElement("td"));
        td3.setTextContent(convertToInitializer(field, random));

        return tr;
    }

    /**
     * Add verification value command for register pattern.
     * 
     * @param entity Field parent entity
     * @param document Document to write command
     * @param element Element to add commands
     * @param fields Fields to verify value
     */
    protected void addVerificationRegister(JavaType entity, Document document, Element element,
            List<FieldMetadata> fields) {

        // Add verifications for all other fields
        for (FieldMetadata field : fields) {
            if (!field.getFieldType().isCommonCollectionType() && !isSpecialType(field.getFieldType())) {
                element.appendChild(verifyCommandRegister(document, entity, field));
            }
        }
    }

    /**
     * Add verification value command for tabular pattern.
     * 
     * @param entity Field parent entity
     * @param document Document to write command
     * @param element Element to add commands
     * @param fields Fields to verify value
     */
    protected void addVerificationTabular(JavaType entity, Document document, Element element,
            List<FieldMetadata> fields) {

        // Add verifications for all other fields
        for (FieldMetadata field : fields) {
            if (!field.getFieldType().isCommonCollectionType() && !isSpecialType(field.getFieldType())) {
                element.appendChild(verifyCommandTabular(document, entity, field));
            }
        }
    }

    /**
     * Add verification value command for register pattern field.
     * 
     * @param document Document to write command
     * @param entity Field parent entity
     * @param field Field to verify value
     * @return Verification node
     */
    protected Node verifyCommandRegister(Document document, JavaType entity, FieldMetadata field) {

        Node tr = document.createElement("tr");

        Node td1 = tr.appendChild(document.createElement("td"));
        td1.setTextContent("verifyText");

        Node td2 = tr.appendChild(document.createElement("td"));
        td2.setTextContent(XmlUtils.convertId("_s_" + entity.getFullyQualifiedTypeName() + "_"
                + field.getFieldName().getSymbolName() + "_" + field.getFieldName().getSymbolName() + "_id"));

        Node td3 = tr.appendChild(document.createElement("td"));
        td3.setTextContent(convertToInitializer(field));

        return tr;
    }

    /**
     * Add verification value command for tabular pattern field.
     * 
     * @param document Document to write command
     * @param entity Field parent entity
     * @param field Field to verify value
     * @return Verification node
     */
    protected Node verifyCommandTabular(Document document, JavaType entity, FieldMetadata field) {

        Node tr = document.createElement("tr");

        Node td1 = tr.appendChild(document.createElement("td"));
        td1.setTextContent("verifyValue");

        Node td2 = tr.appendChild(document.createElement("td"));
        String id = "_" + XmlUtils.convertId("fu:" + entity.getFullyQualifiedTypeName()) + "[0]_"
                + field.getFieldName() + "_id_update";
        td2.setTextContent(XmlUtils.convertId(id));

        Node td3 = tr.appendChild(document.createElement("td"));
        td3.setTextContent(convertToInitializer(field));

        return tr;
    }

    /**
     * Add store confirmation command, required before a javascript
     * confirmation.
     * 
     * @param document Document to write command
     * @param varName Name of variable to store confirmation value
     * @return Confirmation node
     */
    protected Node storeConfirmationCommand(Document document, String varName) {

        Node tr = document.createElement("tr");

        Node td1 = tr.appendChild(document.createElement("td"));
        td1.setTextContent("storeConfirmation");

        Node td2 = tr.appendChild(document.createElement("td"));
        td2.setTextContent(XmlUtils.convertId(varName));

        Node td3 = tr.appendChild(document.createElement("td"));
        td3.setTextContent(" ");

        return tr;
    }

    /**
     * Add check command, to check a checkbox or radio.
     * 
     * @param document Document to write command
     * @param entity Entity owner of check property
     * @return Check node
     */
    protected Node checkCommand(Document document, JavaType entity) {

        Node tr = document.createElement("tr");

        Node td1 = tr.appendChild(document.createElement("td"));
        td1.setTextContent("check");

        Node td2 = tr.appendChild(document.createElement("td"));
        String id = GVNIX_CHECKBOX + XmlUtils.convertId(entity.getFullyQualifiedTypeName()) + "_0";
        td2.setTextContent(id);

        Node td3 = tr.appendChild(document.createElement("td"));
        td3.setTextContent(" ");

        return tr;
    }

    /**
     * Write new test to disk, reference it from Seleniun and install maven
     * plugin.
     * 
     * @param name Test name
     * @param serverURL Application base URL
     * @param document Document to write
     * @param relativePath Destination path where write
     */
    protected void installTest(String name, String serverURL, Document document, String relativePath) {

        // Store new test
        String path = projectOperations.getPathResolver()
                .getIdentifier(LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), relativePath);
        fileManager.createOrUpdateTextFileIfRequired(path, XmlUtils.nodeToString(document), false);

        // Store or update the selenium test suite (all tests list)
        manageTestSuite(relativePath, name, serverURL);

        // Install selenium maven plugin
        installMavenPlugin();
    }

    /**
     * Install central Selenium configuration if not already and reference a new
     * test.
     * 
     * @param testPath New test path
     * @param name Test name
     * @param serverURL Application base URL
     */
    protected void manageTestSuite(String testPath, String name, String serverURL) {

        String relativePath = "selenium/test-suite.xhtml";
        String seleniumPath = projectOperations.getPathResolver()
                .getIdentifier(LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), relativePath);

        Document suite;
        try {

            if (fileManager.exists(seleniumPath)) {

                suite = XmlUtils.readXml(fileManager.getInputStream(seleniumPath));

            } else {

                InputStream templateInputStream = FileUtils.getInputStream(SeleniumOperationsImpl.class,
                        "selenium-test-suite-template.xhtml");
                Validate.notNull(templateInputStream, "Could not acquire selenium test suite template");
                suite = XmlUtils.readXml(templateInputStream);
            }
        } catch (Exception e) {

            throw new IllegalStateException(e);
        }

        ProjectMetadata projectMetadata = projectOperations
                .getProjectMetadata(projectOperations.getFocusedModuleName());
        Validate.notNull(projectMetadata, "Unable to obtain project metadata");

        Element root = (Element) suite.getLastChild();

        XmlUtils.findRequiredElement("/html/head/title", root).setTextContent("Test suite for "
                + projectOperations.getProjectName(projectOperations.getFocusedModuleName()) + "project");

        Element tr = suite.createElement("tr");
        Element td = suite.createElement("td");
        tr.appendChild(td);
        Element a = suite.createElement("a");
        a.setAttribute("href",
                serverURL + projectOperations.getProjectName(projectOperations.getFocusedModuleName())
                        + "/resources/" + testPath);
        a.setTextContent(name);
        td.appendChild(a);

        XmlUtils.findRequiredElement("/html/body/table", root).appendChild(tr);

        fileManager.createOrUpdateTextFileIfRequired(seleniumPath, XmlUtils.nodeToString(suite), false);

        menuOperations.addMenuItem(new JavaSymbolName("SeleniumTests"), new JavaSymbolName("Test"), "Test",
                "selenium_menu_test_suite", "/resources/" + relativePath, "si_",
                LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""));
    }

    /**
     * Install into Maven if not already the Selenium plugin.
     */
    protected void installMavenPlugin() {

        PathResolver pathResolver = projectOperations.getPathResolver();
        String pom = pathResolver.getIdentifier(LogicalPath.getInstance(Path.ROOT, ""), "/pom.xml");
        Document document = XmlUtils.readXml(fileManager.getInputStream(pom));
        Element root = (Element) document.getLastChild();

        // Stop if the plugin is already installed
        if (XmlUtils.findFirstElement("/project/build/plugins/plugin[artifactId = 'selenium-maven-plugin']",
                root) != null) {
            return;
        }

        Element configuration = XmlUtils.getConfiguration(SeleniumOperationsImpl.class);
        Element plugin = XmlUtils.findFirstElement("/configuration/selenium/plugin", configuration);

        // Now install the plugin itself
        if (plugin != null) {
            projectOperations.addBuildPlugin(projectOperations.getFocusedModuleName(), new Plugin(plugin));
        }
    }

    /**
     * Get test value for a field.
     * 
     * @param field Field to auto generate a value
     * @return Field test value
     */
    private String convertToInitializer(FieldMetadata field) {
        return convertToInitializer(field, false);
    }

    /**
     * Get test value for a field.
     * 
     * @param field Field to auto generate a value
     * @param random Should be the initialized value generated randomly ?
     * @return Field test value
     */
    private String convertToInitializer(FieldMetadata field, boolean random) {

        String initializer = " ";

        short index = 1;
        if (random) {
            index = (short) RANDOM_GENERATOR.nextInt();
        }

        AnnotationMetadata min = MemberFindingUtils.getAnnotationOfType(field.getAnnotations(),
                new JavaType("javax.validation.constraints.Min"));
        if (min != null) {
            AnnotationAttributeValue<?> value = min.getAttribute(new JavaSymbolName("value"));
            if (value != null) {
                index = Short.valueOf(value.getValue().toString());
            }
        }
        if (field.getFieldName().getSymbolName().contains("email")
                || field.getFieldName().getSymbolName().contains("Email")) {
            initializer = "some@email.com";
        } else if (field.getFieldType().equals(JavaType.STRING)) {
            initializer = "some" + field.getFieldName().getSymbolNameCapitalisedFirstLetter() + index;
        } else if (field.getFieldType().equals(new JavaType(Date.class.getName()))
                || field.getFieldType().equals(new JavaType(Calendar.class.getName()))) {
            Calendar cal = Calendar.getInstance();
            AnnotationMetadata dateTimeFormat = null;
            String style = null;
            if (null != (dateTimeFormat = MemberFindingUtils.getAnnotationOfType(field.getAnnotations(),
                    new JavaType("org.springframework.format.annotation.DateTimeFormat")))) {
                AnnotationAttributeValue<?> value = dateTimeFormat.getAttribute(new JavaSymbolName("style"));
                if (value != null) {
                    style = value.getValue().toString();
                }
            }
            if (null != MemberFindingUtils.getAnnotationOfType(field.getAnnotations(),
                    new JavaType("javax.validation.constraints.Past"))) {
                cal.add(Calendar.YEAR, -1);
                cal.add(Calendar.MONTH, -1);
                cal.add(Calendar.DAY_OF_MONTH, -1);
            } else if (null != MemberFindingUtils.getAnnotationOfType(field.getAnnotations(),
                    new JavaType("javax.validation.constraints.Future"))) {
                cal.add(Calendar.YEAR, +1);
                cal.add(Calendar.MONTH, +1);
                cal.add(Calendar.DAY_OF_MONTH, +1);
            }
            if (style != null) {
                if (style.startsWith("-")) {
                    initializer = ((SimpleDateFormat) DateFormat
                            .getTimeInstance(DateTime.parseDateFormat(style.charAt(1)), Locale.getDefault()))
                                    .format(cal.getTime());
                } else if (style.endsWith("-")) {
                    initializer = ((SimpleDateFormat) DateFormat
                            .getDateInstance(DateTime.parseDateFormat(style.charAt(0)), Locale.getDefault()))
                                    .format(cal.getTime());
                } else {
                    initializer = ((SimpleDateFormat) DateFormat.getDateTimeInstance(
                            DateTime.parseDateFormat(style.charAt(0)), DateTime.parseDateFormat(style.charAt(1)),
                            Locale.getDefault())).format(cal.getTime());
                }
            } else {
                initializer = ((SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()))
                        .format(cal.getTime());
            }

        } else if (field.getFieldType().equals(JavaType.BOOLEAN_OBJECT)
                || field.getFieldType().equals(JavaType.BOOLEAN_PRIMITIVE)) {
            initializer = Boolean.FALSE.toString();
        } else if (field.getFieldType().equals(JavaType.INT_OBJECT)
                || field.getFieldType().equals(JavaType.INT_PRIMITIVE)) {
            initializer = Integer.valueOf(index).toString();
        } else if (field.getFieldType().equals(JavaType.DOUBLE_OBJECT)
                || field.getFieldType().equals(JavaType.DOUBLE_PRIMITIVE)) {
            initializer = Double.valueOf(index).toString();
        } else if (field.getFieldType().equals(JavaType.FLOAT_OBJECT)
                || field.getFieldType().equals(JavaType.FLOAT_PRIMITIVE)) {
            initializer = Float.valueOf(index).toString();
        } else if (field.getFieldType().equals(JavaType.LONG_OBJECT)
                || field.getFieldType().equals(JavaType.LONG_PRIMITIVE)) {
            initializer = Long.valueOf(index).toString();
        } else if (field.getFieldType().equals(JavaType.SHORT_OBJECT)
                || field.getFieldType().equals(JavaType.SHORT_PRIMITIVE)) {
            initializer = Short.valueOf(index).toString();
        } else if (field.getFieldType().equals(new JavaType("java.math.BigDecimal"))) {
            initializer = BigDecimal.valueOf(index).toString();
        }
        return initializer;
    }

}