All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.otto.jlineup.config.JobConfig Maven / Gradle / Ivy

The newest version!
package de.otto.jlineup.config;

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import de.otto.jlineup.JacksonWrapper;
import de.otto.jlineup.browser.Browser;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

import static com.fasterxml.jackson.annotation.JsonInclude.Include;
import static com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_COMMENTS;
import static com.fasterxml.jackson.databind.MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS;
import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
import static de.otto.jlineup.config.DeviceConfig.deviceConfig;
import static de.otto.jlineup.config.DeviceConfig.deviceConfigBuilder;
import static de.otto.jlineup.config.UrlConfig.urlConfigBuilder;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonDeserialize(builder = JobConfig.Builder.class)
public final class JobConfig  {

    static final String LINEUP_CONFIG_DEFAULT_PATH = "./lineup.json";
    static final String EXAMPLE_URL = "https://www.example.com";

    public static final int DEFAULT_WARMUP_BROWSER_CACHE_TIME = 0;
    public static final int DEFAULT_REPORT_FORMAT = 2;

    static final Browser.Type DEFAULT_BROWSER = Browser.Type.CHROME_HEADLESS;
    static final float DEFAULT_MAX_DIFF = 0;
    public static final int DEFAULT_WINDOW_WIDTH = 800;
    public static final int DEFAULT_WINDOW_HEIGHT = 800;
    static final float DEFAULT_PIXEL_RATIO = 1.0f;
    static final float DEFAULT_GLOBAL_WAIT_AFTER_PAGE_LOAD = 0f;
    public static final String DEFAULT_PATH = "";
    public static final ImmutableList DEFAULT_PATHS = ImmutableList.of(DEFAULT_PATH);
    static final int DEFAULT_MAX_SCROLL_HEIGHT = 100000;
    static final float DEFAULT_WAIT_AFTER_PAGE_LOAD = 0;
    static final float DEFAULT_WAIT_AFTER_SCROLL = 0;
    static final float DEFAULT_WAIT_FOR_NO_ANIMATION_AFTER_SCROLL = 0;
    static final float DEFAULT_WAIT_FOR_FONTS_TIME = 0;
    public static final double DEFAULT_MAX_ANTI_ALIAS_COLOR_DISTANCE = 2.3d;
    public static final double DEFAULT_MAX_COLOR_DISTANCE = 2.3d;
    static final int DEFAULT_THREADS = 0; // '0' means not set which is transformed to '1' when creating the threadpool
    static final int DEFAULT_PAGELOAD_TIMEOUT = 120;
    static final int DEFAULT_SCREENSHOT_RETRIES = 0;
    static final int DEFAULT_GLOBAL_TIMEOUT = 1800;
    public static final float DEFAULT_WAIT_FOR_SELECTORS_TIMEOUT = 10.0f;

    public static final HttpCheckConfig DEFAULT_HTTP_CHECK_CONFIG = new HttpCheckConfig();

    public final Map urls;
    public final Browser.Type browser;

    @JsonInclude(Include.NON_DEFAULT)
    public final String name;

    @JsonInclude(Include.NON_DEFAULT)
    public final String message;

    @JsonInclude(Include.NON_DEFAULT)
    public final String approvalLink;

    @JsonProperty("wait-after-page-load")
    @JsonAlias({"async-wait"})
    public final Float globalWaitAfterPageLoad;
    public final int pageLoadTimeout;
    public final Integer windowHeight;
    @JsonInclude(value = Include.CUSTOM, valueFilter = ReportFormatFilter.class)
    public final Integer reportFormat;
    @JsonInclude(Include.NON_DEFAULT)
    public final int screenshotRetries;
    @JsonInclude(Include.NON_DEFAULT)
    public final int threads;
    @JsonProperty("timeout")
    public final int globalTimeout;
    public final boolean debug;
    @JsonInclude(Include.NON_DEFAULT)
    public final boolean logToFile;
    public final boolean checkForErrorsInLog;
    @JsonInclude(value = Include.CUSTOM, valueFilter = HttpCheckFilter.class)
    public final HttpCheckConfig httpCheck;

    @JsonInclude(Include.NON_DEFAULT)
    public final JobConfig mergeConfig;

    public JobConfig() {
        this(jobConfigBuilder());
    }

    private JobConfig(Builder builder) {
        name = builder.name;
        message = builder.message;
        approvalLink = builder.approvalLink;
        urls = builder.urls;
        browser = builder.browser;
        globalWaitAfterPageLoad = builder.globalWaitAfterPageLoad;
        pageLoadTimeout = builder.pageLoadTimeout;
        windowHeight = builder.windowHeight;
        threads = builder.threads;
        screenshotRetries = builder.screenshotRetries;
        reportFormat = builder.reportFormat;
        globalTimeout = builder.globalTimeout;
        debug = builder.debug;
        logToFile = builder.logToFile;
        checkForErrorsInLog = builder.checkForErrorsInLog;
        httpCheck = builder.httpCheck;
        mergeConfig = builder.mergeConfig;
    }

    public static String prettyPrint(JobConfig jobConfig) {
        return JacksonWrapper.serializeObject(jobConfig);
    }

    public static String prettyPrintWithAllFields(JobConfig jobConfig) {
        ObjectMapper objectMapper = JsonMapper.builder()
                .configure(JsonReadFeature.ALLOW_TRAILING_COMMA, true)
                .configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS, true)
                .configure(ACCEPT_CASE_INSENSITIVE_ENUMS, true)
                .configure(ALLOW_COMMENTS, true)
                .configure(INDENT_OUTPUT, true)
                .build();
        objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.KEBAB_CASE);
        objectMapper.addMixIn(JobConfig.class, JobConfigMixIn.class);
        objectMapper.addMixIn(UrlConfig.class, UrlConfigMixIn.class);
        try {
            return objectMapper.writeValueAsString(jobConfig);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("There is a problem while writing the " + jobConfig.getClass().getCanonicalName() + " with Jackson.", e);
        }
    }

    /*
     *
     *
     *
     *  BEGIN of getters block
     *
     *  For GraalVM (JSON is empty if no getters are here)
     *
     *
     *
     */

    public Map getUrls() {
        return urls;
    }

    public Browser.Type getBrowser() {
        return browser;
    }

    public String getName() {
        return name;
    }

    public String getMessage() {
        return message;
    }

    @JsonIgnore
    public String getSanitizedMessage() {
        return message != null ? message.replace("\\n", "\n") : null;
    }

    public String getApprovalLink() {
        return approvalLink;
    }

    public Float getGlobalWaitAfterPageLoad() {
        return globalWaitAfterPageLoad;
    }

    public int getPageLoadTimeout() {
        return pageLoadTimeout;
    }

    public Integer getWindowHeight() {
        return windowHeight;
    }

    public Integer getReportFormat() {
        return reportFormat;
    }

    public int getScreenshotRetries() {
        return screenshotRetries;
    }

    public int getThreads() {
        return threads;
    }

    public int getGlobalTimeout() {
        return globalTimeout;
    }

    public boolean isDebug() {
        return debug;
    }

    public boolean isLogToFile() {
        return logToFile;
    }

    public boolean isCheckForErrorsInLog() {
        return checkForErrorsInLog;
    }

    public HttpCheckConfig getHttpCheck() {
        return httpCheck;
    }

    public JobConfig getMergeConfig() {
        return mergeConfig;
    }

    /*
     *
     *
     *
     *  END of getters block
     *
     *  For GraalVM (JSON is empty if no getters are here)
     *
     *
     *
     */

    public static Builder copyOfBuilder(JobConfig jobConfig) {
        return jobConfigBuilder()
                .withName(jobConfig.name)
                .withMessage(jobConfig.message)
                .withApprovalLink(jobConfig.approvalLink)
                .withUrls(jobConfig.urls)
                .withHttpCheck(jobConfig.httpCheck)
                .withBrowser(jobConfig.browser)
                .withGlobalWaitAfterPageLoad(jobConfig.globalWaitAfterPageLoad)
                .withPageLoadTimeout(jobConfig.pageLoadTimeout)
                .withWindowHeight(jobConfig.windowHeight)
                .withThreads(jobConfig.threads)
                .withScreenshotRetries(jobConfig.screenshotRetries)
                .withReportFormat(jobConfig.reportFormat)
                .withGlobalTimeout(jobConfig.globalTimeout)
                .withDebug(jobConfig.debug)
                .withLogToFile(jobConfig.logToFile)
                .withMergeConfig(jobConfig.mergeConfig)
                .withCheckForErrorsInLog(jobConfig.checkForErrorsInLog);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        JobConfig jobConfig = (JobConfig) o;
        return pageLoadTimeout == jobConfig.pageLoadTimeout && screenshotRetries == jobConfig.screenshotRetries && threads == jobConfig.threads && globalTimeout == jobConfig.globalTimeout && debug == jobConfig.debug && logToFile == jobConfig.logToFile && checkForErrorsInLog == jobConfig.checkForErrorsInLog && Objects.equals(urls, jobConfig.urls) && browser == jobConfig.browser && Objects.equals(name, jobConfig.name) && Objects.equals(message, jobConfig.message) && Objects.equals(approvalLink, jobConfig.approvalLink) && Objects.equals(globalWaitAfterPageLoad, jobConfig.globalWaitAfterPageLoad) && Objects.equals(windowHeight, jobConfig.windowHeight) && Objects.equals(reportFormat, jobConfig.reportFormat) && Objects.equals(httpCheck, jobConfig.httpCheck) && Objects.equals(mergeConfig, jobConfig.mergeConfig);
    }

    @Override
    public int hashCode() {
        return Objects.hash(urls, browser, name, message, approvalLink, globalWaitAfterPageLoad, pageLoadTimeout, windowHeight, reportFormat, screenshotRetries, threads, globalTimeout, debug, logToFile, checkForErrorsInLog, httpCheck, mergeConfig);
    }

    @Override
    public String toString() {
        return "JobConfig{" +
                "urls=" + urls +
                ", browser=" + browser +
                ", name='" + name + '\'' +
                ", message='" + message + '\'' +
                ", approvalLink='" + approvalLink + '\'' +
                ", globalWaitAfterPageLoad=" + globalWaitAfterPageLoad +
                ", pageLoadTimeout=" + pageLoadTimeout +
                ", windowHeight=" + windowHeight +
                ", reportFormat=" + reportFormat +
                ", screenshotRetries=" + screenshotRetries +
                ", threads=" + threads +
                ", globalTimeout=" + globalTimeout +
                ", debug=" + debug +
                ", logToFile=" + logToFile +
                ", checkForErrorsInLog=" + checkForErrorsInLog +
                ", httpCheck=" + httpCheck +
                ", mergeConfig=" + mergeConfig +
                '}';
    }

    public static JobConfig defaultConfig() {
        return defaultConfig(EXAMPLE_URL);
    }

    public static JobConfig defaultConfig(String url) {
        return jobConfigBuilder().withName("Default").withUrls(ImmutableMap.of(url, urlConfigBuilder().build())).build();
    }

    public static Builder jobConfigBuilder() {
        return new Builder();
    }

    public static JobConfig exampleConfig() {
        return exampleConfigBuilder().build().insertDefaults();
    }

    public static JobConfig.Builder exampleConfigBuilder() {
        return jobConfigBuilder()
                .withName("Example")
                .withMessage("This is an example message, which will be shown in the report.")
                .withCheckForErrorsInLog(true)
                .withUrls(ImmutableMap.of("https://www.example.com",

                        urlConfigBuilder()
                                .withPaths(ImmutableList.of("/"))
                                .withCookies(ImmutableList.of(
                                        new Cookie("exampleCookieName", "exampleValue", "www.example.com", "/", new Date(1000L), false, false, false)
                                ))
                                .withEnvMapping(ImmutableMap.of("live", "www"))
                                .withLocalStorage(ImmutableMap.of("exampleLocalStorageKey", "value"))
                                .withSessionStorage(ImmutableMap.of("exampleSessionStorageKey", "value"))
                                .withDevices(ImmutableList.of(deviceConfig(850,600), deviceConfig(1000, 850), deviceConfig(1200, 1000)))
                                .withJavaScript("console.log('This is JavaScript!')")
                                .withHttpCheck(new HttpCheckConfig(true))
                                .withStrictColorComparison(false)
                                .withRemoveSelectors(ImmutableSet.of("#removeNodeWithThisId", ".removeNodesWithThisClass"))
                                .withWaitForSelectors(ImmutableSet.of("h1"))
                                .withFailIfSelectorsNotFound(false)
                                .build())
                );
    }

    public static JobConfig readConfig(final String workingDir, final String configFileName) throws IOException {
        List searchPaths = new ArrayList<>();
        Path configFilePath = Paths.get(workingDir + "/" + configFileName);
        searchPaths.add(configFilePath.toString());
        if (!Files.exists(configFilePath)) {
            configFilePath = Paths.get(configFileName);
            searchPaths.add(configFilePath.toString());
            if (!Files.exists(configFilePath)) {
                configFilePath = Paths.get(LINEUP_CONFIG_DEFAULT_PATH);
                searchPaths.add(configFilePath.toString());
                if (!Files.exists(configFilePath)) {
                    String cwd = Paths.get(".").toAbsolutePath().normalize().toString();
                    throw new FileNotFoundException("JobConfig file not found. Search locations were: " + Arrays.toString(searchPaths.toArray()) + " - working dir: " + cwd);
                }
            }
        }
        BufferedReader br = new BufferedReader(new FileReader(configFilePath.toString()));
        return JacksonWrapper.deserializeConfig(br);
    }

    /**
     * This function actively checks for unset values that need to be replaced with defaults
     * @return a new JobConfig including the former values with defaults added
     */
    public JobConfig insertDefaults() {

        //If no urls are configured, insertDefaults can't really work, validation will fail in a later step
        if (this.urls == null) {
            return this;
        }

        Builder jobConfigBuilder = copyOfBuilder(this).withUrls(ImmutableMap.of());
        this.urls.forEach((url, urlConfig) -> {

            UrlConfig.Builder urlConfigBuilder = UrlConfig.copyOfBuilder(urlConfig);

            if (urlConfig.url == null) {
                urlConfigBuilder.withUrl(url);
            }

            //If both are not set, use default window width
            List windowWidths = urlConfig.windowWidths;
            if (windowWidths == null && urlConfig.devices == null) {
                windowWidths = ImmutableList.of(DEFAULT_WINDOW_WIDTH);
            }

            int windowHeight = this.windowHeight != null ? this.windowHeight : DEFAULT_WINDOW_HEIGHT;

            final List deviceConfigs;
            if (urlConfig.devices == null) {
                deviceConfigs = new ArrayList<>();
                windowWidths.forEach(width -> deviceConfigs.add(deviceConfigBuilder().withWidth(width).withHeight(windowHeight).build()));
            } else {
                deviceConfigs = urlConfig.devices;
            }
            urlConfigBuilder.withDevices(deviceConfigs);
            //Remove window widths because devices were generated above
            urlConfigBuilder.withWindowWidths(null);

            final List paths = urlConfig.paths != null ? urlConfig.paths : DEFAULT_PATHS;
            urlConfigBuilder.withPaths(paths);

            jobConfigBuilder.addUrlConfig(url, urlConfigBuilder.build());
        });

        //Every url gets device config
        jobConfigBuilder.withWindowHeight(null);

        return jobConfigBuilder.build();
    }

    public static final class Builder {
        private String name = null;
        private String message = null;
        private String approvalLink = null;
        private Map urls = null;
        private Browser.Type browser = DEFAULT_BROWSER;
        private float globalWaitAfterPageLoad = DEFAULT_GLOBAL_WAIT_AFTER_PAGE_LOAD;
        private int pageLoadTimeout = DEFAULT_PAGELOAD_TIMEOUT;
        private Integer windowHeight = null;
        private int reportFormat = DEFAULT_REPORT_FORMAT;
        private int screenshotRetries = DEFAULT_SCREENSHOT_RETRIES;
        private int threads = DEFAULT_THREADS;
        private int globalTimeout = DEFAULT_GLOBAL_TIMEOUT;
        private boolean debug = false;
        private boolean logToFile = false;
        private boolean checkForErrorsInLog = false;
        private HttpCheckConfig httpCheck = DEFAULT_HTTP_CHECK_CONFIG;
        public JobConfig mergeConfig;

        private Builder() {
        }

        public Builder withName(String val) {
            name = val;
            return this;
        }

        public Builder withMessage(String val) {
            message = val;
            return this;
        }

        public Builder withApprovalLink(String val) {
            approvalLink = val;
            return this;
        }

        public Builder withUrls(Map val) {
            urls = val;
            return this;
        }

        public Builder withBrowser(Browser.Type val) {
            browser = val;
            return this;
        }

        @JsonProperty("wait-after-page-load")
        @JsonAlias({"async-wait"})
        public Builder withGlobalWaitAfterPageLoad(float val) {
            globalWaitAfterPageLoad = val;
            return this;
        }

        public Builder withPageLoadTimeout(int val) {
            pageLoadTimeout = val;
            return this;
        }

        public Builder withWindowHeight(Integer val) {
            windowHeight = val;
            return this;
        }

        @JsonInclude(value = Include.CUSTOM, valueFilter = ReportFormatFilter.class)
        public Builder withReportFormat(int val) {
            reportFormat = val;
            return this;
        }

        public Builder withScreenshotRetries(int val) {
            screenshotRetries = val;
            return this;
        }

        public Builder withThreads(int val) {
            threads = val;
            return this;
        }

        public Builder withDebug(boolean val) {
            debug = val;
            return this;
        }

        public Builder withLogToFile(boolean val) {
            logToFile = val;
            return this;
        }

        @JsonProperty("timeout")
        public Builder withGlobalTimeout(int val) {
            globalTimeout = val;
            return this;
        }

        public Builder withCheckForErrorsInLog(boolean val) {
            checkForErrorsInLog = val;
            return this;
        }

        @JsonInclude(value = Include.CUSTOM, valueFilter = HttpCheckFilter.class)
        public Builder withHttpCheck(HttpCheckConfig val) {
            httpCheck = val;
            return this;
        }

        public Builder withMergeConfig(JobConfig val) {
            mergeConfig = val;
            return this;
        }

        public JobConfig build() {
            return new JobConfig(this);
        }

        public Builder addUrlConfig(String url, UrlConfig urlConfig) {
            if (urls == null) {
                urls = ImmutableMap.of(url, urlConfig);
            }
            else {
                urls = ImmutableMap.builder().putAll(urls).put(url, urlConfig).build();
            }
            return this;
        }
    }

    /**
     * This removes all cookie values from the config (used to write the config to the report)
     * @return
     */
    public JobConfig sanitize() {
        return JobConfig.copyOfBuilder(this)
                .withUrls(this.urls.entrySet().stream().collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, entry -> entry.getValue().sanitize())))
                .build();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy