001/*
002 * (C) Copyright 2013-2017 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     <a href="mailto:grenard@nuxeo.com">Guillaume</a>
018 */
019package org.nuxeo.functionaltests;
020
021import static org.nuxeo.functionaltests.AbstractTest.NUXEO_URL;
022import static org.nuxeo.functionaltests.Constants.ADMINISTRATOR;
023
024import java.io.File;
025import java.lang.reflect.Field;
026import java.lang.reflect.Method;
027import java.util.Collections;
028import java.util.LinkedList;
029import java.util.List;
030
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.junit.internal.runners.statements.RunAfters;
034import org.junit.rules.TestWatchman;
035import org.junit.runners.model.FrameworkMethod;
036import org.junit.runners.model.Statement;
037import org.nuxeo.client.NuxeoClient;
038import org.nuxeo.client.NuxeoClient.Builder;
039import org.nuxeo.common.utils.URIUtils;
040import org.openqa.selenium.remote.RemoteWebDriver;
041
042import okhttp3.Response;
043
044/**
045 * Watchman to log info about the test and create snapshot on failure.
046 *
047 * @since 5.8
048 */
049public class LogTestWatchman extends TestWatchman {
050
051    private static final Log log = LogFactory.getLog(LogTestWatchman.class);
052
053    protected String lastScreenshot;
054
055    protected String lastPageSource;
056
057    protected String filePrefix;
058
059    protected RemoteWebDriver driver;
060
061    protected String serverURL;
062
063    public LogTestWatchman(final RemoteWebDriver driver, final String serverURL) {
064        this.driver = driver;
065        this.serverURL = serverURL;
066    }
067
068    public LogTestWatchman() {
069    }
070
071    @Override
072    @SuppressWarnings("unchecked")
073    public Statement apply(final Statement base, final FrameworkMethod method, Object target) {
074        return new Statement() {
075            @Override
076            public void evaluate() throws Throwable {
077                starting(method);
078                try {
079                    if (base instanceof RunAfters) {
080                        // Hack JUnit: in order to take screenshot at the right
081                        // time we add through reflection an after
082                        // function that will be executed before all other
083                        // ones. See NXP-12742
084                        Field aftersField = RunAfters.class.getDeclaredField("afters");
085                        aftersField.setAccessible(true);
086
087                        List<FrameworkMethod> afters = (List<FrameworkMethod>) aftersField.get(base);
088                        if (afters != null && !afters.isEmpty()) {
089                            try {
090                                // Improve this and instead of finding a
091                                // special function, we could register
092                                // functions specially annotated.
093                                FrameworkMethod first = afters.get(0);
094                                Method m = AbstractTest.class.getMethod("runBeforeAfters", (Class<?>[]) null);
095                                FrameworkMethod f = new FrameworkMethod(m);
096                                if (first != null && !first.equals(f)) {
097                                    for (;;) {
098                                        String aftersClassName = afters.getClass().getName();
099                                        if (aftersClassName.endsWith("$UnmodifiableList")
100                                                || aftersClassName.endsWith("$UnmodifiableRandomAccessList")) {
101                                            Class<?> unmodifiableListClass = Collections.unmodifiableList(
102                                                    new LinkedList<>()).getClass();
103                                            Field listField = unmodifiableListClass.getDeclaredField("list");
104                                            listField.setAccessible(true);
105                                            afters = (List<FrameworkMethod>) listField.get(afters);
106                                        } else {
107                                            break;
108                                        }
109                                    }
110                                    afters.add(0, f);
111                                }
112                            } catch (NoSuchMethodException e) {
113                                // Do nothing
114                            }
115                        }
116                    }
117                    base.evaluate();
118                    succeeded(method);
119                } catch (Throwable t) {
120                    failed(t, method);
121                    throw t;
122                } finally {
123                    finished(method);
124                }
125            }
126        };
127    }
128
129    @Override
130    public void failed(Throwable e, FrameworkMethod method) {
131        String className = getTestClassName(method);
132        String methodName = method.getName();
133        log.error(String.format("Test '%s#%s' failed", className, methodName), e);
134
135        if (lastScreenshot == null || lastPageSource == null) {
136            ScreenshotTaker taker = new ScreenshotTaker();
137
138            if (lastScreenshot == null) {
139                File temp = taker.takeScreenshot(driver, filePrefix);
140                lastScreenshot = temp != null ? temp.getAbsolutePath() : null;
141            }
142
143            if (lastPageSource == null) {
144                File temp = taker.dumpPageSource(driver, filePrefix);
145                lastPageSource = temp != null ? temp.getAbsolutePath() : null;
146            }
147
148        }
149        log.info(String.format("Created screenshot file named '%s'", lastScreenshot));
150        log.info(String.format("Created page source file named '%s'", lastPageSource));
151        super.failed(e, method);
152    }
153
154    @Override
155    public void finished(FrameworkMethod method) {
156        log.info(String.format("Finished test '%s#%s'", getTestClassName(method), method.getName()));
157        lastScreenshot = null;
158        lastPageSource = null;
159        super.finished(method);
160    }
161
162    public RemoteWebDriver getDriver() {
163        return driver;
164    }
165
166    public String getServerURL() {
167        return serverURL;
168    }
169
170    protected String getTestClassName(FrameworkMethod method) {
171        return method.getMethod().getDeclaringClass().getName();
172    }
173
174    protected void logOnServer(String message) {
175        if (driver != null) {
176            NuxeoClient client = new NuxeoClientForWebDriver(
177                    new Builder().url(NUXEO_URL).authentication(ADMINISTRATOR, ADMINISTRATOR));
178            Response response = client.get(NUXEO_URL + "/restAPI/systemLog");
179            response.body().close();
180            if (response.isSuccessful()) {
181                driver.get(String.format("%s/restAPI/systemLog?token=dolog&level=WARN&message=----- WebDriver: %s",
182                        serverURL, URIUtils.quoteURIPathComponent(message, true)));
183                return;
184            }
185        }
186        log.warn(String.format("Cannot log on server message: %s", message));
187    }
188
189    public void runBeforeAfters() {
190        if (driver != null) {
191            ScreenshotTaker taker = new ScreenshotTaker();
192            lastScreenshot = taker.takeScreenshot(driver, filePrefix).getAbsolutePath();
193            lastPageSource = taker.dumpPageSource(driver, filePrefix).getAbsolutePath();
194        }
195    }
196
197    public void setDriver(RemoteWebDriver driver) {
198        this.driver = driver;
199    }
200
201    public void setServerURL(String serverURL) {
202        this.serverURL = serverURL;
203    }
204
205    @Override
206    public void starting(FrameworkMethod method) {
207        String message = String.format("Starting test '%s#%s'", getTestClassName(method), method.getName());
208        log.info(message);
209        String className = getTestClassName(method);
210        String methodName = method.getName();
211        filePrefix = String.format("screenshot-lastpage-%s-%s", className, methodName);
212        logOnServer(message);
213    }
214
215    @Override
216    public void succeeded(FrameworkMethod method) {
217        if (lastPageSource != null) {
218            new File(lastPageSource).delete();
219        }
220        if (lastScreenshot != null) {
221            new File(lastScreenshot).delete();
222        }
223    }
224
225    /**
226     * We need this class in order to make simple HTTP GET calls to know if we can log or not.
227     * <p />
228     * For some tests, REST API is not available, so basic client didn't succeed to connect.
229     *
230     * @since 9.3
231     */
232    public static class NuxeoClientForWebDriver extends NuxeoClient {
233
234        protected NuxeoClientForWebDriver(Builder builder) {
235            super(builder);
236        }
237    }
238
239}