com.tascape.qa.th.webui.comm.Firefox.java Source code

Java tutorial

Introduction

Here is the source code for com.tascape.qa.th.webui.comm.Firefox.java

Source

/*
 * Copyright 2015 - 2016 Nebula Bay.
 *
 * 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 com.tascape.qa.th.webui.comm;

import com.tascape.qa.th.SystemConfiguration;
import com.tascape.qa.th.Utils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.text.ParseException;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import org.apache.commons.io.FileUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.firefox.internal.ProfilesIni;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author linsong wang
 */
public class Firefox extends WebBrowser {
    private static final Logger LOG = LoggerFactory.getLogger(Firefox.class);

    public static final int FIREBUG_PAGELOADEDTIMEOUT_MILLI = 180000;

    public static final String SYSPROP_FF_BINARY = "qa.th.comm.FF_BINARY";

    public static final String SYSPROP_FF_PROFILE_NAME = "qa.th.comm.FF_PROFILE_NAME";

    public static final String DEFAULT_FF_PROFILE_NAME = "default";

    private Firebug firebug = null;

    public Firefox(boolean enableFirebug) throws Exception {
        FirefoxProfile profile;

        ProfilesIni profileIni = new ProfilesIni();
        String profileName = sysConfig.getProperty(SYSPROP_FF_PROFILE_NAME);
        if (profileName != null) {
            LOG.debug("Load Firefox profile named as {}", profileName);
            profile = profileIni.getProfile(profileName);
        } else {
            LOG.debug("Load Firefox profile named as {}", DEFAULT_FF_PROFILE_NAME);
            profile = profileIni.getProfile(DEFAULT_FF_PROFILE_NAME);
        }
        if (profile == null) {
            throw new Exception("Cannot find Firefox profile");
        }

        profile.setPreference("app.update.enabled", false);
        profile.setEnableNativeEvents(false);
        profile.setAcceptUntrustedCertificates(true);
        profile.setAssumeUntrustedCertificateIssuer(false);
        profile.setPreference("dom.max_chrome_script_run_time", 0);
        profile.setPreference("dom.max_script_run_time", 0);
        if (enableFirebug) {
            this.firebug = new Firebug();
            this.firebug.updateProfile(profile);
        }
        long end = System.currentTimeMillis() + 180000;
        while (System.currentTimeMillis() < end) {
            try {
                super.setWebDriver(new FirefoxDriver(profile));
                break;
            } catch (org.openqa.selenium.WebDriverException ex) {
                String msg = ex.getMessage();
                LOG.warn(msg);
                if (!msg.contains("Unable to bind to locking port 7054 within 45000 ms")) {
                    throw ex;
                }
            }
        }
    }

    public Firebug getFirebug() {
        return firebug;
    }

    @Override
    public int getPageLoadTimeMillis(String url) throws Exception {
        return this.firebug.getPageLoadTimeMillis(url);
    }

    @Override
    public int getAjaxLoadTimeMillis(Ajax ajax) throws Exception {
        return this.firebug.getAjaxLoadTimeMillis(ajax);
    }

    public class Firebug implements Extension {
        private final String tokenNetExport = UUID.randomUUID().toString();

        private final Path harPath = sysConfig.getLogPath().resolve(sysConfig.getExecId())
                .resolve(SystemConfiguration.CONSTANT_LOG_KEEP_ALIVE_PREFIX + "har-" + System.currentTimeMillis());

        public void clearHarDir() throws IOException {
            File[] hars = this.harPath.toFile().listFiles((File dir, String name) -> name.endsWith(".har"));
            if (hars != null) {
                for (File f : hars) {
                    f.delete();
                }
            }
        }

        public int getPageLoadTimeMillis(String url)
                throws IOException, JSONException, InterruptedException, ParseException {
            Utils.sleep(2000, "");
            this.clearHarDir();
            Firefox.this.get(url);
            JSONObject json = this.waitForFirebugNetExport();
            return HarLog.parse(json).getOverallLoadTimeMillis();
        }

        public int getAjaxLoadTimeMillis(Ajax ajax) throws Exception {
            this.doNetClear();
            ajax.doRequest();
            Utils.sleep(5000, "Wait for ajax to load");
            try {
                if (ajax.getByDisapper() != null) {
                    Firefox.this.waitForNoElement(ajax.getByDisapper(), AJAX_TIMEOUT_SECONDS);
                }
                if (ajax.getByAppear() != null) {
                    Firefox.this.waitForElement(ajax.getByAppear(), AJAX_TIMEOUT_SECONDS);
                }
                if (ajax.getByDisapper() == null && ajax.getByAppear() == null) {
                    Utils.sleep(5000, "Wait for ajax to load");
                }
            } finally {
                this.clearHarDir();
                this.doNetExport();
            }
            JSONObject json = this.waitForFirebugNetExport();
            return HarLog.parse(json).getOverallLoadTimeMillis();
        }

        private void doNetClear() {
            String js = "window.NetExport.clear(\"" + tokenNetExport + "\")";
            Firefox.this.executeScript(Void.class, js);
        }

        private void doNetExport() {
            String js = "window.NetExport.triggerExport(\"" + tokenNetExport + "\")";
            Firefox.this.executeScript(Void.class, js);
        }

        private JSONObject waitForFirebugNetExport() throws IOException, InterruptedException {
            long end = System.currentTimeMillis() + FIREBUG_PAGELOADEDTIMEOUT_MILLI;
            File[] harFiles = {};
            while (System.currentTimeMillis() < end) {
                Utils.sleep(10000, "Wait for netexport http archive file");
                try {
                    File[] hars = this.harPath.toFile().listFiles((File dir, String name) -> name.endsWith(".har"));
                    if (hars == null || hars.length == 0) {
                        continue;
                    }
                    if (hars.length != harFiles.length) {
                        harFiles = hars;
                        continue;
                    }

                    this.clearHarDir();
                    this.doNetExport();
                    hars = this.harPath.toFile().listFiles((File dir, String name) -> name.endsWith(".har"));
                    File har = hars[0];
                    LOG.debug("Load data from {}", har.getAbsolutePath());
                    JSONObject json = new JSONObject(FileUtils.readFileToString(har));
                    FileUtils.copyFile(har, this.harPath.resolve(har.getName() + ".json").toFile());
                    this.clearHarDir();
                    return json;
                } catch (IOException | JSONException ex) {
                    LOG.warn(ex.getMessage());
                }
            }
            throw new IOException("Cannot load firebug netexport har file");
        }

        @Override
        public void updateProfile(FirefoxProfile profile) {
            String domain = "extensions.firebug.";
            profile.setPreference(domain + "onByDefault", true);
            profile.setPreference(domain + "allPagesActivation", "on");
            profile.setPreference(domain + "defaultPanelName", "net");
            profile.setPreference(domain + "net.enableSites", true);
            profile.setPreference(domain + "netexport.alwaysEnableAutoExport", true);
            profile.setPreference(domain + "netexport.autoExportToFile", true);
            profile.setPreference(domain + "netexport.showPreview", false);
            profile.setPreference(domain + "netexport.timeout", 180000); // default 60000
            profile.setPreference(domain + "netexport.pageLoadedTimeout", 1500); // default 1500
            profile.setPreference(domain + "netexport.secretToken", tokenNetExport);
            profile.setPreference(domain + "netexport.defaultLogDir", harPath.toFile().getAbsolutePath());
        }
    }

    public static interface Extension {
        public void updateProfile(FirefoxProfile profile);
    }
}

class HarLog {
    private static final Logger LOG = LoggerFactory.getLogger(HarLog.class);

    public String version;

    public Creator creator;

    public Browser browser;

    public List<Page> pages;

    public List<Entry> entries;

    public static HarLog parse(String harJson) throws JSONException {
        JSONObject json = new JSONObject(harJson);
        return HarLog.parse(json);
    }

    public static HarLog parse(JSONObject harJson) throws JSONException {
        JSONObject json = harJson.getJSONObject("log");
        HarLog har = new HarLog();

        har.version = json.getString("version");
        har.creator = Creator.parse(json.getJSONObject("creator"));
        har.browser = Browser.parse(json.getJSONObject("browser"));
        har.pages = new LinkedList<>();
        for (int i = 0; i < json.getJSONArray("pages").length(); i++) {
            har.pages.add(Page.parse(json.getJSONArray("pages").getJSONObject(i)));
        }
        har.entries = new LinkedList<>();
        for (int i = 0; i < json.getJSONArray("entries").length(); i++) {
            har.entries.add(Entry.parse(json.getJSONArray("entries").getJSONObject(i)));
        }
        return har;
    }

    public static class Creator {
        public String name;

        public String version;

        public static Creator parse(JSONObject json) throws JSONException {
            Creator o = new Creator();
            o.name = json.getString("name");
            o.version = json.getString("version");
            return o;
        }
    }

    public static class Browser {
        public String name;

        public String version;

        public static Browser parse(JSONObject json) throws JSONException {
            Browser o = new Browser();
            o.name = json.getString("name");
            o.version = json.getString("version");
            return o;
        }
    }

    public static class Page {

        public String startedDateTime;

        public String id;

        public String title;

        public PageTimings pageTimings;

        public static class PageTimings {

            public int onContentLoad;

            public int onLoad;

            public String toString;

            public static PageTimings parse(JSONObject json) throws JSONException {
                PageTimings o = new PageTimings();
                o.toString = json.toString();
                o.onContentLoad = json.getInt("onContentLoad");
                o.onLoad = json.getInt("onLoad");
                return o;
            }
        }

        public static Page parse(JSONObject json) throws JSONException {
            Page o = new Page();
            o.startedDateTime = json.getString("startedDateTime");
            o.id = json.getString("id");
            o.title = json.getString("title");
            o.pageTimings = PageTimings.parse(json.getJSONObject("pageTimings"));
            return o;
        }
    }

    public static class Entry {

        public String pageref;

        public String startedDateTime;

        public int time;

        public Request request;

        public Response response;

        public Timings timings;

        public String serverIPAddress;

        public int connection;

        public static class Request {
            public String method;

            public String url;

            public String httpVersion;

            public static Request parse(JSONObject json) throws JSONException {
                Request o = new Request();
                o.method = json.getString("method");
                o.url = json.getString("url");
                o.httpVersion = json.getString("httpVersion");
                return o;
            }
        }

        public static class Response {
            public int status;

            public String statusText;

            public String httpVersion;

            public String redirectURL;

            public static Response parse(JSONObject json) throws JSONException {
                Response o = new Response();
                o.status = json.getInt("status");
                o.statusText = json.getString("statusText");
                o.httpVersion = json.getString("httpVersion");
                o.redirectURL = json.getString("redirectURL");
                return o;
            }
        }

        public static class Timings {
            public int blocked;

            public int dns;

            public int connect;

            public int send;

            public int wait;

            public int receive;

            public static Timings paser(JSONObject json) throws JSONException {
                Timings o = new Timings();
                o.blocked = json.getInt("blocked");
                o.dns = json.getInt("dns");
                o.send = json.getInt("send");
                o.wait = json.getInt("wait");
                o.receive = json.getInt("receive");
                return o;
            }
        }

        public static Entry parse(JSONObject json) throws JSONException {
            Entry o = new Entry();
            o.pageref = json.getString("pageref");
            o.startedDateTime = json.getString("startedDateTime");
            o.time = json.getInt("time");
            o.request = Request.parse(json.getJSONObject("request"));
            o.response = Response.parse(json.getJSONObject("response"));
            o.timings = Timings.paser(json.getJSONObject("timings"));
            o.serverIPAddress = json.optString("serverIPAddress");
            o.connection = json.optInt("connection");
            return o;
        }
    }

    int getOverallLoadTimeMillis() throws ParseException {
        final String format = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
        long start = Long.MAX_VALUE;
        long end = Long.MIN_VALUE;
        for (Entry entry : this.entries) {
            long s = Utils.getTime(entry.startedDateTime, format);
            start = Math.min(start, s);
            long e = s + entry.time;
            end = Math.max(end, e);
            LOG.debug("{}/{} - {}", entry.request.method, entry.response.status, entry.request.url);
        }
        if (end <= start) {
            return -1;
        }
        long time = end - start;
        LOG.debug("Overall load time {} ms", time);
        return (int) (time);
    }

    int getLatestPageLoadTimeMillis() {
        Page p = this.pages.get(pages.size() - 1);
        LOG.debug("{}", p.pageTimings.toString);
        String id = p.id;
        for (Entry e : entries) {
            if (e.pageref.equals(p.id)) {
                LOG.debug("Page URL {}", e.request.url);
                break;
            }
        }
        return Math.max(p.pageTimings.onContentLoad, p.pageTimings.onLoad);
    }

    int getLatestEntryLoadTimeMillis(String urlRegex) {
        for (int i = this.entries.size() - 1; i >= 0; i--) {
            Entry e = this.entries.get(i);
            if (e.request.url.matches(urlRegex)) {
                LOG.debug("Request URL {}", e.request.url);
                return e.time;
            }
        }
        return -1;
    }
}