org.auraframework.integration.test.ClientOutOfSyncUITest.java Source code

Java tutorial

Introduction

Here is the source code for org.auraframework.integration.test.ClientOutOfSyncUITest.java

Source

/*
 * Copyright (C) 2013 salesforce.com, inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.auraframework.integration.test;

import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.auraframework.Aura;
import org.auraframework.annotations.Annotations.ServiceComponent;
import org.auraframework.def.ApplicationDef;
import org.auraframework.def.ComponentDef;
import org.auraframework.def.ControllerDef;
import org.auraframework.def.DefDescriptor;
import org.auraframework.def.EventDef;
import org.auraframework.def.HelperDef;
import org.auraframework.def.IncludeDef;
import org.auraframework.def.InterfaceDef;
import org.auraframework.def.LibraryDef;
import org.auraframework.def.ProviderDef;
import org.auraframework.def.RendererDef;
import org.auraframework.def.StyleDef;
import org.auraframework.def.TokensDef;
import org.auraframework.ds.servicecomponent.Controller;
import org.auraframework.integration.test.util.WebDriverTestCase;
import org.auraframework.system.Annotations.AuraEnabled;
import org.auraframework.system.Annotations.Key;
import org.auraframework.test.util.WebDriverUtil.BrowserType;
import org.auraframework.util.test.annotation.ThreadHostileTest;
import org.junit.Ignore;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedCondition;

import com.google.common.base.Function;

/**
 * Tests to verify that the client gets updated when we want it to get updated.
 */
public class ClientOutOfSyncUITest extends WebDriverTestCase {

    @Override
    public void setUp() throws Exception {
        super.setUp();
        // these tests trigger server recompilation which can take a bit of time
        getAuraUITestingUtil().setTimeoutInSecs(20);
    }

    private void setupTriggerComponent(DefDescriptor<ComponentDef> cmpDesc, String attrs, String body) {
        addSourceAutoCleanup(cmpDesc, String.format(baseComponentTag,
                "controller='java://org.auraframework.components.test.java.controller.JavaTestController' " + attrs,
                "<button class='button' onclick='{!c.post}'>post</button>" + body));
        DefDescriptor<?> controllerDesc = definitionService.getDefDescriptor(cmpDesc,
                DefDescriptor.JAVASCRIPT_PREFIX, ControllerDef.class);
        addSourceAutoCleanup(controllerDesc,
                "{post:function(c){var a=c.get('c.getString');a.setParams({param:'dummy'});$A.enqueueAction(a);}}");
    }

    private DefDescriptor<ComponentDef> setupTriggerComponent(String attrs, String body) {
        DefDescriptor<ComponentDef> cmpDesc = getAuraTestingUtil().createStringSourceDescriptor(null,
                ComponentDef.class, null);
        setupTriggerComponent(cmpDesc, attrs, body);
        return cmpDesc;
    }

    private boolean isIE() {
        switch (getBrowserType()) {
        case IE7:
        case IE8:
        case IE9:
        case IE10:
        case IE11:
            return true;
        default:
            break;
        }
        return false;
    }

    /**
     * Trigger a server action and wait for the browser to begin refreshing.
     */
    private void triggerServerAction() {
        // Careful. Android doesn't like more than one statement.
        getAuraUITestingUtil().getRawEval("document._waitingForReload = true;");

        // This test flaps on slower environments in IE. Give it a little more time to process the javascript.
        if (isIE()) {
            waitFor(3);
        }
        getAuraUITestingUtil().findDomElement(By.cssSelector("button.button")).click();
        if (isIE()) {
            waitFor(3);
        }
        getAuraUITestingUtil().waitUntil(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver d) {
                Object ret = getAuraUITestingUtil().getRawEval("return !document._waitingForReload");
                if (ret != null && ((Boolean) ret).booleanValue()) {
                    return true;
                }
                return false;
            }
        }, "Page failed to refresh after server action triggered.");
        getAuraUITestingUtil().waitForDocumentReady();
        getAuraUITestingUtil().waitForAuraFrameworkReady(getAuraErrorsExpectedDuringInit());
    }

    @Test
    public void testGetServerRenderingAfterMarkupChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = addSourceAutoCleanup(ComponentDef.class,
                String.format(baseComponentTag, "", "hi"));
        String url = getUrl(cmpDesc);
        openNoAura(url);
        assertEquals("hi", getText(By.cssSelector("body")));
        updateStringSource(cmpDesc, String.format(baseComponentTag, "", "bye"));
        // Firefox caches the response so we need to manually include a nonce to effect a reload
        openNoAura(url + "?nonce=" + System.nanoTime());
        getAuraUITestingUtil().waitForElementText(By.cssSelector("body"), "bye", true);
    }

    @ThreadHostileTest("NamespaceDef modification affects namespace")
    @Test
    public void testGetClientRenderingAfterStyleChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = addSourceAutoCleanup(ComponentDef.class,
                String.format(baseComponentTag, "", "<div id='out'>hi</div>"));
        String className = cmpDesc.getNamespace() + StringUtils.capitalize(cmpDesc.getName());
        DefDescriptor<?> styleDesc = definitionService.getDefDescriptor(cmpDesc, DefDescriptor.CSS_PREFIX,
                StyleDef.class);
        addSourceAutoCleanup(styleDesc, String.format(".%s {font-style:italic;}", className));
        open(cmpDesc);
        assertEquals("italic",
                getAuraUITestingUtil().findDomElement(By.cssSelector("." + className)).getCssValue("font-style"));
        updateStringSource(styleDesc, String.format(".%s {font-style:normal;}", className));
        open(cmpDesc);
        assertEquals("normal",
                getAuraUITestingUtil().findDomElement(By.cssSelector("." + className)).getCssValue("font-style"));
    }

    @ThreadHostileTest("NamespaceDef modification affects namespace")
    @Test
    public void testGetClientRenderingAfterTokensChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = addSourceAutoCleanup(ComponentDef.class,
                String.format(baseComponentTag, "", "<div id='out'>hi</div>"));
        String className = cmpDesc.getNamespace() + StringUtils.capitalize(cmpDesc.getName());
        DefDescriptor<?> styleDesc = definitionService.getDefDescriptor(cmpDesc, DefDescriptor.CSS_PREFIX,
                StyleDef.class);
        addSourceAutoCleanup(styleDesc, String.format(".%s {font-size:t(fsize);}", className));
        DefDescriptor<?> tokensDesc = definitionService.getDefDescriptor(String.format("%s://%s:%sNamespace",
                DefDescriptor.MARKUP_PREFIX, cmpDesc.getNamespace(), cmpDesc.getNamespace()), TokensDef.class);
        addSourceAutoCleanup(tokensDesc, "<aura:tokens><aura:token name='fsize' value='8px'/></aura:tokens>");
        open(cmpDesc);
        assertEquals("8px",
                getAuraUITestingUtil().findDomElement(By.cssSelector("." + className)).getCssValue("font-size"));
        updateStringSource(tokensDesc, "<aura:tokens><aura:token name='fsize' value='66px'/></aura:tokens>");
        open(cmpDesc);
        assertEquals("66px",
                getAuraUITestingUtil().findDomElement(By.cssSelector("." + className)).getCssValue("font-size"));
    }

    @Test
    public void testGetClientRenderingAfterJsControllerChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = addSourceAutoCleanup(ComponentDef.class,
                String.format(baseComponentTag, "", "<div id='click' onclick='{!c.clicked}'>click</div>"));
        DefDescriptor<?> controllerDesc = definitionService.getDefDescriptor(cmpDesc,
                DefDescriptor.JAVASCRIPT_PREFIX, ControllerDef.class);
        addSourceAutoCleanup(controllerDesc, "{clicked:function(){window.tempVar='inconsequential'}}");
        open(cmpDesc);
        assertNull(getAuraUITestingUtil().getEval("return window.tempVar;"));
        getAuraUITestingUtil().findDomElement(By.cssSelector("#click")).click();
        getAuraUITestingUtil().waitUntil(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver d) {
                return "inconsequential".equals(getAuraUITestingUtil().getEval("return window.tempVar;"));
            }
        });
        updateStringSource(controllerDesc, "{clicked:function(){window.tempVar='meaningful'}}");
        open(cmpDesc);
        assertNull(getAuraUITestingUtil().getEval("return window.tempVar;"));
        getAuraUITestingUtil().findDomElement(By.cssSelector("#click")).click();
        getAuraUITestingUtil().waitUntil(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver d) {
                return "meaningful".equals(getAuraUITestingUtil().getEval("return window.tempVar;"));
            }
        });
    }

    @Test
    public void testGetClientRenderingAfterJsProviderChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = getAuraTestingUtil().createStringSourceDescriptor(null,
                ComponentDef.class, null);
        DefDescriptor<?> providerDesc = definitionService.getDefDescriptor(cmpDesc, DefDescriptor.JAVASCRIPT_PREFIX,
                ProviderDef.class);

        addSourceAutoCleanup(cmpDesc,
                String.format(baseComponentTag,
                        String.format("render='client' provider='%s'", providerDesc.getQualifiedName()),
                        "<aura:attribute name='given' type='string' default=''/>{!v.given}"));
        addSourceAutoCleanup(providerDesc, "({provide:function(){return {attributes:{'given':'silver spoon'}};}})");
        open(cmpDesc);
        assertEquals("silver spoon", getText(By.cssSelector("body")));
        updateStringSource(providerDesc, "({provide:function(){return {attributes:{'given':'golden egg'}};}})");

        open(cmpDesc);
        assertEquals("golden egg", getText(By.cssSelector("body")));
    }

    @Test
    public void testGetClientRenderingAfterJsHelperChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = getAuraTestingUtil().createStringSourceDescriptor(null,
                ComponentDef.class, null);
        DefDescriptor<?> helperDesc = definitionService.getDefDescriptor(cmpDesc, DefDescriptor.JAVASCRIPT_PREFIX,
                HelperDef.class);

        addSourceAutoCleanup(cmpDesc, String.format(baseComponentTag,
                String.format("render='client' helper='%s'", helperDesc.getQualifiedName()), ""));
        addSourceAutoCleanup(helperDesc, "({getHelp:function(){return 'simply';}})");
        open(cmpDesc);
        assertEquals("simply",
                getAuraUITestingUtil().getEval("return $A.getRoot().getDef().getHelper().getHelp();"));
        updateStringSource(helperDesc, "({getHelp:function(){return 'complicated';}})");
        open(cmpDesc);
        assertEquals("complicated",
                getAuraUITestingUtil().getEval("return $A.getRoot().getDef().getHelper().getHelp();"));
    }

    @Test
    public void testGetClientRenderingAfterJsRendererChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = getAuraTestingUtil().createStringSourceDescriptor(null,
                ComponentDef.class, null);
        DefDescriptor<?> rendererDesc = definitionService.getDefDescriptor(cmpDesc, DefDescriptor.JAVASCRIPT_PREFIX,
                RendererDef.class);

        addSourceAutoCleanup(cmpDesc, String.format(baseComponentTag,
                String.format("renderer='%s'", rendererDesc.getQualifiedName()), ""));
        addSourceAutoCleanup(rendererDesc, "({render:function(){return 'default';}})");
        open(cmpDesc);
        assertEquals("default", getText(By.cssSelector("body")));
        updateStringSource(rendererDesc, "({render:function(){return 'custom';}})");
        open(cmpDesc);
        assertEquals("custom", getText(By.cssSelector("body")));
    }

    @Test
    public void testGetClientRenderingAfterEventChange() throws Exception {
        DefDescriptor<?> eventDesc = addSourceAutoCleanup(EventDef.class,
                "<aura:event type='APPLICATION'><aura:attribute name='explode' type='String' default='pow'/></aura:event>");
        DefDescriptor<ComponentDef> cmpDesc = addSourceAutoCleanup(ComponentDef.class, String.format(
                baseComponentTag, "render='client'",
                String.format("<aura:registerevent name='end' type='%s'/>", eventDesc.getDescriptorName())));
        open(cmpDesc);
        assertEquals("pow",
                getAuraUITestingUtil().getEval(
                        String.format("return $A.getEvt('%s').getDef().getAttributeDefs().explode['default'];",
                                eventDesc.getDescriptorName())));
        updateStringSource(eventDesc,
                "<aura:event type='APPLICATION'><aura:attribute name='explode' type='String' default='kaboom'/></aura:event>");
        open(cmpDesc);
        assertEquals("kaboom",
                getAuraUITestingUtil().getEval(
                        String.format("return $A.getEvt('%s').getDef().getAttributeDefs().explode['default'];",
                                eventDesc.getDescriptorName())));
    }

    @Test
    public void testGetServerRenderingAfterInterfaceChange() throws Exception {
        DefDescriptor<?> interfaceDesc = addSourceAutoCleanup(InterfaceDef.class,
                "<aura:interface support='GA' description=''><aura:attribute name='entrance' type='String' default='grand'/></aura:interface>");
        DefDescriptor<ComponentDef> cmpDesc = addSourceAutoCleanup(ComponentDef.class,
                String.format(baseComponentTag, String.format("implements='%s'", interfaceDesc.getQualifiedName()),
                        "{!v.entrance}"));
        String url = getUrl(cmpDesc);
        openNoAura(url);
        assertEquals("grand", getText(By.cssSelector("body")));
        updateStringSource(interfaceDesc,
                "<aura:interface support='GA' description=''><aura:attribute name='entrance' type='String' default='secret'/></aura:interface>");
        // Firefox caches the response so we need to manually include a nonce to effect a reload
        openNoAura(url + "?nonce=" + System.nanoTime());
        getAuraUITestingUtil().waitForElementText(By.cssSelector("body"), "secret", true);
    }

    @Test
    public void testPostAfterMarkupChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = setupTriggerComponent("", "<div id='sample'>free</div>");
        open(cmpDesc);
        assertEquals("free", getText(By.cssSelector("#sample")));
        updateStringSource(cmpDesc,
                String.format(baseComponentTag,
                        "controller='java://org.auraframework.components.test.java.controller.JavaTestController'",
                        "<button class='button' onclick='{!c.post}'>post</button><div id='sample'>deposit</div>"));
        triggerServerAction();
        getAuraUITestingUtil().waitForElementText(By.cssSelector("#sample"), "deposit", true);
    }

    @ThreadHostileTest("NamespaceDef modification affects namespace")
    @Test
    public void testPostAfterStyleChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = setupTriggerComponent("", "<div id='out'>hi</div>");
        String className = cmpDesc.getNamespace() + StringUtils.capitalize(cmpDesc.getName());
        DefDescriptor<?> styleDesc = definitionService.getDefDescriptor(cmpDesc, DefDescriptor.CSS_PREFIX,
                StyleDef.class);
        addSourceAutoCleanup(styleDesc, String.format(".%s {font-style:italic;}", className));
        open(cmpDesc);
        assertEquals("italic",
                getAuraUITestingUtil().findDomElement(By.cssSelector("." + className)).getCssValue("font-style"));
        updateStringSource(styleDesc, String.format(".%s {font-style:normal;}", className));
        triggerServerAction();
        getAuraUITestingUtil().waitForElementFunction(By.cssSelector("." + className),
                new Function<WebElement, Boolean>() {
                    @Override
                    public Boolean apply(WebElement element) {
                        return "normal".equals(element.getCssValue("font-style"));
                    }
                });
    }

    /**
     * A routine to do _many_ iterations of a client out of sync test.
     *
     * This test really shouldn't be run unless one of the tests is flapping. It lets you iterate a number of times to
     * force a failure.... No guarantees, but without the _waitingForReload check in the trigger function, this will
     * cause a failure in very few iterations.
     */
    @ThreadHostileTest("NamespaceDef modification affects namespace")
    @Test
    @Ignore
    public void testPostManyAfterStyleChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = setupTriggerComponent("", "<div id='out'>hi</div>");
        String className = cmpDesc.getNamespace() + StringUtils.capitalize(cmpDesc.getName());
        DefDescriptor<?> styleDesc = definitionService.getDefDescriptor(cmpDesc, DefDescriptor.CSS_PREFIX,
                StyleDef.class);
        addSourceAutoCleanup(styleDesc, String.format(".%s {font-style:italic;}", className));
        open(cmpDesc);
        assertEquals("italic",
                getAuraUITestingUtil().findDomElement(By.cssSelector("." + className)).getCssValue("font-style"));
        for (int i = 0; i < 1000; i++) {
            updateStringSource(styleDesc, String.format(".%s {font-style:normal;}", className));
            triggerServerAction();
            getAuraUITestingUtil().waitForElementFunction(By.cssSelector("." + className),
                    new Function<WebElement, Boolean>() {
                        @Override
                        public Boolean apply(WebElement element) {
                            return "normal".equals(element.getCssValue("font-style"));
                        }
                    });
            updateStringSource(styleDesc, String.format(".%s {font-style:italic;}", className));
            triggerServerAction();
            getAuraUITestingUtil().waitForElementFunction(By.cssSelector("." + className),
                    new Function<WebElement, Boolean>() {
                        @Override
                        public Boolean apply(WebElement element) {
                            return "italic".equals(element.getCssValue("font-style"));
                        }
                    });
        }
    }

    @ThreadHostileTest("NamespaceDef modification affects namespace")
    @Test
    public void testPostAfterTokensChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = setupTriggerComponent("", "<div id='out'>hi</div>");
        String className = cmpDesc.getNamespace() + StringUtils.capitalize(cmpDesc.getName());
        DefDescriptor<?> styleDesc = definitionService.getDefDescriptor(cmpDesc, DefDescriptor.CSS_PREFIX,
                StyleDef.class);
        addSourceAutoCleanup(styleDesc, String.format(".%s {font-size:t(fsize);}", className));
        DefDescriptor<?> tokensDesc = definitionService.getDefDescriptor(String.format("%s://%s:%sNamespace",
                DefDescriptor.MARKUP_PREFIX, cmpDesc.getNamespace(), cmpDesc.getNamespace()), TokensDef.class);
        addSourceAutoCleanup(tokensDesc, "<aura:tokens><aura:token name='fsize' value='8px'/></aura:tokens>");
        open(cmpDesc);
        assertEquals("8px",
                getAuraUITestingUtil().findDomElement(By.cssSelector("." + className)).getCssValue("font-size"));
        updateStringSource(tokensDesc, "<aura:tokens><aura:token name='fsize' value='66px'/></aura:tokens>");
        triggerServerAction();
        getAuraUITestingUtil().waitForElementFunction(By.cssSelector("." + className),
                new Function<WebElement, Boolean>() {
                    @Override
                    public Boolean apply(WebElement element) {
                        return "66px".equals(element.getCssValue("font-size"));
                    }
                });
    }

    @Test
    public void testPostAfterJsControllerChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = addSourceAutoCleanup(ComponentDef.class, String.format(
                baseComponentTag,
                "controller='java://org.auraframework.components.test.java.controller.JavaTestController'",
                "<button class='button' onclick='{!c.post}'>post</button><div id='click' onclick='{!c.clicked}'>click</div>"));
        DefDescriptor<?> controllerDesc = definitionService.getDefDescriptor(cmpDesc,
                DefDescriptor.JAVASCRIPT_PREFIX, ControllerDef.class);
        addSourceAutoCleanup(controllerDesc,
                "{post:function(c){var a=c.get('c.getString');a.setParams({param:'dummy'});$A.enqueueAction(a);},clicked:function(){window.tempVar='inconsequential'}}");
        open(cmpDesc);
        assertNull(getAuraUITestingUtil().getEval("return window.tempVar;"));
        getAuraUITestingUtil().findDomElement(By.cssSelector("#click")).click();
        getAuraUITestingUtil().waitUntil(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver d) {
                return "inconsequential".equals(getAuraUITestingUtil().getEval("return window.tempVar;"));
            }
        });
        updateStringSource(controllerDesc,
                "{post:function(c){var a=c.get('c.getString');a.setParams({param:'dummy'});$A.enqueueAction(a);},clicked:function(){window.tempVar='meaningful'}}");
        triggerServerAction();
        // wait for page to reload by checking that our tempVar is undefined again
        getAuraUITestingUtil().waitUntil(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver input) {
                return (Boolean) getAuraUITestingUtil().getEval("return !window.tempVar;");
            }
        });
        getAuraUITestingUtil().findDomElement(By.cssSelector("#click")).click();
        getAuraUITestingUtil().waitUntil(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver input) {
                return "meaningful".equals(getAuraUITestingUtil().getEval("return window.tempVar;"));
            }
        });
    }

    @Test
    public void testPostAfterJsProviderChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = getAuraTestingUtil().createStringSourceDescriptor(null,
                ComponentDef.class, null);
        DefDescriptor<?> providerDesc = definitionService.getDefDescriptor(cmpDesc, DefDescriptor.JAVASCRIPT_PREFIX,
                ProviderDef.class);
        addSourceAutoCleanup(cmpDesc, String.format(baseComponentTag, String.format(
                "controller='java://org.auraframework.components.test.java.controller.JavaTestController' provider='%s'",
                providerDesc.getQualifiedName()),
                "<button class='button' onclick='{!c.post}'>post</button><aura:attribute name='given' type='string' default=''/><div id='result'>{!v.given}</div>"));
        DefDescriptor<?> controllerDesc = definitionService.getDefDescriptor(cmpDesc,
                DefDescriptor.JAVASCRIPT_PREFIX, ControllerDef.class);
        addSourceAutoCleanup(controllerDesc,
                "{post:function(c){var a=c.get('c.getString');a.setParams({param:'dummy'});$A.enqueueAction(a);}}");
        addSourceAutoCleanup(providerDesc, "({provide:function(){return {attributes:{'given':'silver spoon'}};}})");
        open(cmpDesc);
        assertEquals("silver spoon", getText(By.cssSelector("#result")));
        updateStringSource(providerDesc, "({provide:function(){return {attributes:{'given':'golden egg'}};}})");
        triggerServerAction();
        getAuraUITestingUtil().waitForElementText(By.cssSelector("#result"), "golden egg", true);
    }

    @Test
    public void testPostAfterJsHelperChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = getAuraTestingUtil().createStringSourceDescriptor(null,
                ComponentDef.class, null);
        DefDescriptor<?> helperDesc = definitionService.getDefDescriptor(cmpDesc, DefDescriptor.JAVASCRIPT_PREFIX,
                HelperDef.class);

        setupTriggerComponent(cmpDesc, String.format("helper='%s'", helperDesc.getQualifiedName()), "");

        addSourceAutoCleanup(helperDesc, "({getHelp:function(){return 'simply';}})");
        open(cmpDesc);
        assertEquals("simply",
                getAuraUITestingUtil().getEval("return $A.getRoot().getDef().getHelper().getHelp();"));
        updateStringSource(helperDesc, "({getHelp:function(){return 'complicated';}})");
        triggerServerAction();
        getAuraUITestingUtil().waitUntil(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver input) {
                getAuraUITestingUtil().waitForDocumentReady();
                getAuraUITestingUtil().waitForAuraFrameworkReady(null);
                return "complicated".equals(getAuraUITestingUtil().getEval(
                        "return window.$A && $A.getRoot() && $A.getRoot().getDef().getHelper().getHelp();"));
            }
        });
    }

    @Test
    public void testPostAfterJsRendererChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = getAuraTestingUtil().createStringSourceDescriptor(null,
                ComponentDef.class, null);
        DefDescriptor<?> rendererDesc = definitionService.getDefDescriptor(cmpDesc, DefDescriptor.JAVASCRIPT_PREFIX,
                RendererDef.class);

        setupTriggerComponent(cmpDesc, String.format("renderer='%s'", rendererDesc.getQualifiedName()), "");
        addSourceAutoCleanup(rendererDesc,
                "({render:function(){var e=document.createElement('div');e.id='target';e.appendChild(document.createTextNode('default'));var r=this.superRender();r.push(e);return r;}})");
        open(cmpDesc);
        assertEquals("default", getText(By.cssSelector("#target")));
        updateStringSource(rendererDesc,
                "({render:function(){var e=document.createElement('div');e.id='target';e.appendChild(document.createTextNode('custom'));var r=this.superRender();r.push(e);return r;}})");
        triggerServerAction();
        getAuraUITestingUtil().waitForElementText(By.cssSelector("#target"), "custom", true);
    }

    @Test
    public void testPostAfterEventChange() throws Exception {
        final DefDescriptor<?> eventDesc = addSourceAutoCleanup(EventDef.class,
                "<aura:event type='APPLICATION'><aura:attribute name='explode' type='String' default='pow'/></aura:event>");
        DefDescriptor<ComponentDef> cmpDesc = setupTriggerComponent("",
                String.format("<aura:registerevent name='end' type='%s'/>", eventDesc.getDescriptorName()));
        open(cmpDesc);
        assertEquals("pow",
                getAuraUITestingUtil().getEval(
                        String.format("return $A.getEvt('%s').getDef().getAttributeDefs().explode['default'];",
                                eventDesc.getDescriptorName())));
        updateStringSource(eventDesc,
                "<aura:event type='APPLICATION'><aura:attribute name='explode' type='String' default='kaboom'/></aura:event>");
        triggerServerAction();
        getAuraUITestingUtil().waitUntil(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver input) {
                getAuraUITestingUtil().waitForDocumentReady();
                getAuraUITestingUtil().waitForAuraFrameworkReady(null);
                String eval = String.format(
                        "return ((window.$A && $A.getEvt('%s')) && (window.$A && $A.getEvt('%s')).getDef().getAttributeDefs().explode['default']);",
                        eventDesc.getDescriptorName(), eventDesc.getDescriptorName());
                return "kaboom".equals(getAuraUITestingUtil().getEval(eval));
            }
        });
    }

    @Test
    public void testPostAfterInterfaceChange() throws Exception {
        DefDescriptor<?> interfaceDesc = addSourceAutoCleanup(InterfaceDef.class,
                "<aura:interface support='GA' description=''><aura:attribute name='entrance' type='String' default='grand'/></aura:interface>");
        DefDescriptor<ComponentDef> cmpDesc = setupTriggerComponent(
                String.format("implements='%s'", interfaceDesc.getQualifiedName()),
                "<div id='target'>{!v.entrance}</div>");
        open(cmpDesc);
        assertEquals("grand", getText(By.cssSelector("#target")));
        updateStringSource(interfaceDesc,
                "<aura:interface support='GA' description=''><aura:attribute name='entrance' type='String' default='secret'/></aura:interface>");
        triggerServerAction();
        getAuraUITestingUtil().waitForElementText(By.cssSelector("#target"), "secret", true);
    }

    @Test
    public void testPostAfterDependencyChange() throws Exception {
        final DefDescriptor<?> depDesc = addSourceAutoCleanup(ComponentDef.class, String.format(baseComponentTag,
                "", "<aura:attribute name='val' type='String' default='initial'/>"));
        DefDescriptor<ComponentDef> cmpDesc = setupTriggerComponent("",
                String.format("<aura:dependency resource='%s'/>", depDesc.getQualifiedName()));
        open(cmpDesc);
        assertEquals("initial",
                getAuraUITestingUtil().getEval(String.format(
                        "return $A.componentService.getDef('%s').getAttributeDefs().getDef('val').getDefault();",
                        depDesc.getDescriptorName())));
        updateStringSource(depDesc,
                String.format(baseComponentTag, "", "<aura:attribute name='val' type='String' default='final'/>"));
        triggerServerAction();
        getAuraUITestingUtil().waitUntil(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver input) {
                getAuraUITestingUtil().waitForDocumentReady();
                getAuraUITestingUtil().waitForAuraFrameworkReady(null);
                return "final".equals(getAuraUITestingUtil().getEval(String.format(
                        "return window.$A && $A.componentService.getDef('%s').getAttributeDefs().getDef('val').getDefault();",
                        depDesc.getDescriptorName())));
            }
        });
    }

    @Test
    public void testGetClientRenderingAfterIncludeChange() throws Exception {
        DefDescriptor<?> helperDesc = addSourceAutoCleanup(HelperDef.class, "({})");
        DefDescriptor<?> libraryDesc = getAuraTestingUtil().createStringSourceDescriptor(null, LibraryDef.class,
                null);
        DefDescriptor<?> includeDesc = getAuraTestingUtil().createStringSourceDescriptor(null, IncludeDef.class,
                libraryDesc);
        addSourceAutoCleanup(includeDesc, "function a(){return 'initialized'}");
        addSourceAutoCleanup(libraryDesc,
                String.format("<aura:library><aura:include name='%s'/></aura:library>", includeDesc.getName()));
        DefDescriptor<ComponentDef> cmpDesc = addSourceAutoCleanup(ComponentDef.class, String.format(
                baseComponentTag, String.format("render='client' helper='%s'", helperDesc.getQualifiedName()),
                String.format("<aura:import library='%s' property='mylib'/>", libraryDesc.getDescriptorName())));

        open(cmpDesc);
        assertEquals("initialized", getAuraUITestingUtil().getEval(
                String.format("return $A.getRoot().getDef().getHelper().mylib.%s;", includeDesc.getName())));

        updateStringSource(includeDesc, "function b(){return 'updated'}");

        open(cmpDesc);
        assertEquals("updated", getAuraUITestingUtil().getEval(
                String.format("return $A.getRoot().getDef().getHelper().mylib.%s;", includeDesc.getName())));
    }

    @Test
    public void testGetClientRenderingAfterLibraryChange() throws Exception {
        DefDescriptor<?> helperDesc = addSourceAutoCleanup(HelperDef.class, "({})");
        DefDescriptor<?> libraryDesc = getAuraTestingUtil().createStringSourceDescriptor(null, LibraryDef.class,
                null);
        DefDescriptor<?> includeDesc = getAuraTestingUtil().createStringSourceDescriptor(null, IncludeDef.class,
                libraryDesc);
        addSourceAutoCleanup(includeDesc, "function a(){return 'firstpick'}");
        DefDescriptor<?> includeOtherDesc = getAuraTestingUtil().createStringSourceDescriptor(null,
                IncludeDef.class, libraryDesc);
        addSourceAutoCleanup(includeOtherDesc, "function b(){return 'secondpick'}");
        addSourceAutoCleanup(libraryDesc,
                String.format("<aura:library><aura:include name='%s'/></aura:library>", includeDesc.getName()));
        DefDescriptor<ComponentDef> cmpDesc = addSourceAutoCleanup(ComponentDef.class, String.format(
                baseComponentTag, String.format("render='client' helper='%s'", helperDesc.getQualifiedName()),
                String.format("<aura:import library='%s' property='mylib'/>", libraryDesc.getDescriptorName())));

        open(cmpDesc);
        assertEquals("firstpick", getAuraUITestingUtil().getEval(
                String.format("return $A.getRoot().getDef().getHelper().mylib.%s;", includeDesc.getName())));

        updateStringSource(libraryDesc, String.format("<aura:library><aura:include name='%s'/></aura:library>",
                includeOtherDesc.getName()));

        open(cmpDesc);
        assertEquals("secondpick", getAuraUITestingUtil().getEval(
                String.format("return $A.getRoot().getDef().getHelper().mylib.%s;", includeOtherDesc.getName())));
    }

    @Test
    public void testPostAfterIncludeChange() throws Exception {
        DefDescriptor<?> helperDesc = addSourceAutoCleanup(HelperDef.class, "({})");
        DefDescriptor<?> libraryDesc = getAuraTestingUtil().createStringSourceDescriptor(null, LibraryDef.class,
                null);
        final DefDescriptor<?> includeDesc = getAuraTestingUtil().createStringSourceDescriptor(null,
                IncludeDef.class, libraryDesc);
        addSourceAutoCleanup(includeDesc, "function a(){return 'initialized'}");
        addSourceAutoCleanup(libraryDesc,
                String.format("<aura:library><aura:include name='%s'/></aura:library>", includeDesc.getName()));
        DefDescriptor<ComponentDef> cmpDesc = setupTriggerComponent(
                String.format("render='client' helper='%s'", helperDesc.getQualifiedName()),
                String.format("<aura:import library='%s' property='mylib'/>", libraryDesc.getDescriptorName()));

        open(cmpDesc);
        assertEquals("initialized", getAuraUITestingUtil().getEval(
                String.format("return $A.getRoot().getDef().getHelper().mylib.%s;", includeDesc.getName())));

        updateStringSource(includeDesc, "function b(){return 'updated'}");

        triggerServerAction();
        getAuraUITestingUtil().waitUntil(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver input) {
                getAuraUITestingUtil().waitForDocumentReady();
                getAuraUITestingUtil().waitForAuraFrameworkReady(null);
                return "updated".equals(getAuraUITestingUtil().getEval(String
                        .format("return $A.getRoot().getDef().getHelper().mylib.%s;", includeDesc.getName())));
            }
        });
    }

    @Test
    public void testPostAfterLibraryChange() throws Exception {
        DefDescriptor<?> helperDesc = addSourceAutoCleanup(HelperDef.class, "({})");
        DefDescriptor<?> libraryDesc = getAuraTestingUtil().createStringSourceDescriptor(null, LibraryDef.class,
                null);
        DefDescriptor<?> includeDesc = getAuraTestingUtil().createStringSourceDescriptor(null, IncludeDef.class,
                libraryDesc);
        addSourceAutoCleanup(includeDesc, "function a(){return 'firstpick'}");
        final DefDescriptor<?> includeOtherDesc = getAuraTestingUtil().createStringSourceDescriptor(null,
                IncludeDef.class, libraryDesc);
        addSourceAutoCleanup(includeOtherDesc, "function b(){return 'secondpick'}");
        addSourceAutoCleanup(libraryDesc,
                String.format("<aura:library><aura:include name='%s'/></aura:library>", includeDesc.getName()));
        DefDescriptor<ComponentDef> cmpDesc = setupTriggerComponent(
                String.format("render='client' helper='%s'", helperDesc.getQualifiedName()),
                String.format("<aura:import library='%s' property='mylib'/>", libraryDesc.getDescriptorName()));

        open(cmpDesc);
        assertEquals("firstpick", getAuraUITestingUtil().getEval(
                String.format("return $A.getRoot().getDef().getHelper().mylib.%s;", includeDesc.getName())));

        updateStringSource(libraryDesc, String.format("<aura:library><aura:include name='%s'/></aura:library>",
                includeOtherDesc.getName()));

        triggerServerAction();
        getAuraUITestingUtil().waitUntil(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver input) {
                getAuraUITestingUtil().waitForDocumentReady();
                getAuraUITestingUtil().waitForAuraFrameworkReady(null);
                return "secondpick".equals(getAuraUITestingUtil().getEval(String
                        .format("return $A.getRoot().getDef().getHelper().mylib.%s;", includeOtherDesc.getName())));
            }
        });
    }

    /**
     * This test verifies that changes to components which are dynamically received are correctly identified
     * after the client is restarted. This is complex to make work: dynamically received defs must be
     * persisted with sufficient versioning information which is later restored (during client restart / framework
     * init), then sent to the server so the server can detect the version change and trigger a client out-of-date
     * message.
     *
     * This test verifies the behavior by retrieving a component from the server, modifying its source on the server,
     * reloading the page, then requesting the same component from the server, asserting that the new source is returned.
     * Without persisting the component version on the client, the server would never know the client's version of
     * the component we're loading is out of date and after a page reload the old component would be displayed.
     *
     * See W-2909975 for additional details.
     */
    // This tests persistent storage, exclude on Safari based browsers
    @ExcludeBrowsers({ BrowserType.SAFARI, BrowserType.IPAD, BrowserType.IPHONE })
    public void _testReloadAfterMarkupChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = addSourceAutoCleanup(ComponentDef.class,
                String.format(baseComponentTag, "", "<div>cmp" + new Date().getTime() + "</div>"));

        DefDescriptor<ApplicationDef> appDesc = addSourceAutoCleanup(ApplicationDef.class, String.format(
                baseApplicationTag, "template='auraStorageTest:componentDefStorageTemplate'",
                "<ui:button label='loadCmp' press='{!c.loadCmp}'/><div id='container' aura:id='container'>app</div>"
                        + "<aura:handler event='aura:initialized' action='{!c.initialized}'/>"));

        String cmpDefString = cmpDesc.getNamespace() + ":" + cmpDesc.getName();
        DefDescriptor<?> controllerDesc = Aura.getDefinitionService().getDefDescriptor(appDesc,
                DefDescriptor.JAVASCRIPT_PREFIX, ControllerDef.class);
        addSourceAutoCleanup(controllerDesc,
                "{" + "loadCmp:function(cmp){$A.createComponent('" + cmpDefString
                        + "', {}, function(newCmp){ cmp.find('container').set('v.body', newCmp); });},"
                        + "initialized:function(cmp){window.initialized=true;}" + "}");

        open(appDesc);

        // Retrieve cmp from server and wait for callback output
        getAuraUITestingUtil().findDomElement(By.cssSelector("button")).click();
        getAuraUITestingUtil().waitUntil(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver d) {
                String text = getText(By.cssSelector("#container"));
                return text.startsWith("cmp");
            }
        }, "Text of app never updated after retrieving component from server");

        // Update the source of the component we retrieve from storage
        String newCmpText = "<div>cmpNew" + new Date().getTime() + "</div>";
        updateStringSource(cmpDesc, String.format(baseComponentTag, "", newCmpText));

        // Wait for dynamically received component def to be persisted.
        // ComponentDefStorage will exist because a dynamic cmp is fetched
        final String getPersistedDef = "var callback = arguments[arguments.length - 1];"
                + "if (!$A) { callback('Aura not initialized'); return; };"
                + "var storage = $A.storageService.getStorage('ComponentDefStorage');"
                + "if (!storage) { callback('No storage'); return; };" + "storage.get('markup://" + cmpDefString
                + "').then(function(item) { callback(item ? 'SUCCESS' : 'empty') });";
        getAuraUITestingUtil().waitUntil(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver d) {
                d.manage().timeouts().setScriptTimeout(1, TimeUnit.SECONDS);
                Object result = ((JavascriptExecutor) getDriver()).executeAsyncScript(getPersistedDef);
                return "SUCCESS".equals(result);
            }
        }, "Definition never persisted on client after server call");

        getDriver().navigate().refresh();

        // After refresh, the page will fire the getApplication bootstrap action, which will get a ClientOutOfSync
        // as the response, dump the storages and reload. Instead of trying to wait for the double reload, wait for
        // Aura Fwk + the app to finish loading (window.initialize) then create def storage (which won't exist
        // because no dynamic defs have been received) and verify it is empty.
        getAuraUITestingUtil().waitUntil(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver d) {
                String script = "var callback = arguments[arguments.length - 1];"
                        + "if (!window.initialized || !$A) { callback(null); return; }"
                        + "if (!$A.storageService.getStorage('ComponentDefStorage')) {"
                        + "  $A.storageService.initStorage({name: 'ComponentDefStorage', persistent: true, secure: false, maxSize: 442368, expiration: 3600, debugLogging: true, clearOnInit: false});"
                        + "}" + "var storage = $A.storageService.getStorage('ComponentDefStorage');"
                        + "storage.getAll().then(" + "  function(items){ callback(Object.keys(items)) },"
                        + "  function() { callback(null); }" + ")";
                Object result = null;
                try {
                    result = ((JavascriptExecutor) getDriver()).executeAsyncScript(script);
                } catch (WebDriverException e) {
                    // If the page reloads during our script WebDriver will throw an error.
                    if (!e.getMessage().contains("document unloaded while waiting for result")) {
                        throw e;
                    }
                }
                return result != null && ((ArrayList<?>) result).size() == 0;
            }
        }, "Storages never cleared after reload");

        // The page will reload after the storage is cleared so wait for it to be fully initialized then retrieve
        // the original component from the server and verify it has the updated source.
        getAuraUITestingUtil().waitForAuraInit();
        getAuraUITestingUtil().findDomElement(By.cssSelector("button")).click();
        getAuraUITestingUtil().waitForElementTextContains(By.cssSelector("#container"), "cmpNew", true);
    }

    @ServiceComponent
    public static class TestController implements Controller {
        @AuraEnabled
        public String getString(@Key("param") String param) throws Exception {
            return "Overridden: " + param;
        }
    }

    /*
     * This scenario is when a component changes during a release, and the original action is no longer there (moved or removed)
     */
    @Test
    public void testPostAfterServerControllerChange() throws Exception {
        DefDescriptor<ComponentDef> cmpDesc = addSourceAutoCleanup(ComponentDef.class,
                String.format(baseComponentTag,
                        "controller='java://org.auraframework.components.test.java.controller.JavaTestController'",
                        "<aura:attribute name='output' type='String'/>"
                                + "<button class='button' onclick='{!c.post}'>post</button>"
                                + "<div id='out'>{!v.output}</div>"));
        DefDescriptor<?> controllerDesc = definitionService.getDefDescriptor(cmpDesc,
                DefDescriptor.JAVASCRIPT_PREFIX, ControllerDef.class);
        addSourceAutoCleanup(controllerDesc,
                "{post:function(c){var a=c.get('c.getString');a.setParams({param:'dummy'});a.setCallback(this,function(res){c.set('v.output',res.getReturnValue())});$A.enqueueAction(a);}}");
        open(cmpDesc);

        By outputLocator = By.cssSelector("#out");

        getAuraUITestingUtil().findDomElement(By.cssSelector("button.button")).click();
        getAuraUITestingUtil().waitForElementText(outputLocator, "dummy", true);

        updateStringSource(cmpDesc, String.format(baseComponentTag,
                "controller='java://org.auraframework.integration.test.ClientOutOfSyncUITest$TestController'",
                "<aura:attribute name='output' type='String'/>"
                        + "<button class='button' onclick='{!c.post}'>post</button>"
                        + "<div id='out'>{!v.output}</div>"));
        triggerServerAction();
        getAuraUITestingUtil().waitForElementText(outputLocator, "", true);

        getAuraUITestingUtil().findDomElement(By.cssSelector("button.button")).click();
        getAuraUITestingUtil().waitForElementText(outputLocator, "Overridden: dummy", true);
    }
}