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

io.percy.selenium.Percy Maven / Gradle / Ivy

package io.percy.selenium;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClientBuilder;

import org.json.JSONObject;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.Nullable;

import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;

/**
 * Percy client for visual testing.
 */
public class Percy {

    private static final Logger LOGGER = Logger.getLogger(Percy.class.getName());

    // We'll expect this file to exist at the root of our classpath, as a resource.
    private static final String AGENTJS_FILE = "percy-agent.js";

    // Selenium WebDriver we'll use for accessing the web pages to snapshot.
    private WebDriver driver;

    // The JavaScript contained in percy-agent.js
    private String percyAgentJs;

    // Environment information like Java, browser, & SDK versions
    private Environment env;

    // Is the Percy Agent process running or not
    private boolean percyIsRunning = true;

    /**
     * @param driver The Selenium WebDriver object that will hold the browser
     *               session to snapshot.
     */
    public Percy(WebDriver driver) {
        this.driver = driver;
        this.env = new Environment(driver);
        this.percyAgentJs = loadPercyAgentJs();
    }

    /**
     * Attempts to load percy-agent.js from `http://localhost:5338/percy-agent.js`.
     *
     * This JavaScript is critical for capturing snapshots. It serializes and captures
     * the DOM. Without it, snapshots cannot be captured.
     */
    @Nullable
    private String loadPercyAgentJs() {
        HttpClient httpClient = HttpClientBuilder.create().build();

        try {
            //Creating a HttpGet object
            HttpGet httpget = new HttpGet("http://localhost:5338/percy-agent.js");

            //Executing the Get request
            HttpResponse response = httpClient.execute(httpget);

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200){
                throw new RuntimeException("Failed with HTTP error code : " + statusCode);
            }

            HttpEntity httpEntity = response.getEntity();
            String agentJSString = EntityUtils.toString(httpEntity);

            return agentJSString;
        } catch (Exception ex) {
            System.out.println("[percy] An error occured while retrieving percy-agent.js: " + ex);
            percyIsRunning = false;
            System.out.println("[percy] Percy has been disabled");
            return null;
        }
    }

    /**
     * Take a snapshot and upload it to Percy.
     *
     * @param name The human-readable name of the snapshot. Should be unique.
     *
     */
    public void snapshot(String name) {
        snapshot(name, null, null, false, null);
    }

    /**
     * Take a snapshot and upload it to Percy.
     *
     * @param name   The human-readable name of the snapshot. Should be unique.
     * @param widths The browser widths at which you want to take the snapshot. In
     *               pixels.
     */
    public void snapshot(String name, List widths) {
        snapshot(name, widths, null, false, null);
    }

    /**
     * Take a snapshot and upload it to Percy.
     *
     * @param name   The human-readable name of the snapshot. Should be unique.
     * @param widths The browser widths at which you want to take the snapshot. In
     *               pixels.
     * @param minHeight The minimum height of the resulting snapshot. In pixels.
     */
    public void snapshot(String name, List widths, Integer minHeight) {
        snapshot(name, widths, minHeight, false, null);
    }

    /**
     * Take a snapshot and upload it to Percy.
     *
     * @param name   The human-readable name of the snapshot. Should be unique.
     * @param widths The browser widths at which you want to take the snapshot. In
     *               pixels.
     * @param minHeight The minimum height of the resulting snapshot. In pixels.
     * @param enableJavaScript Enable JavaScript in the Percy rendering environment
     */
    public void snapshot(String name, List widths, Integer minHeight, boolean enableJavaScript) {
        snapshot(name, widths, minHeight, false, null);
    }

    /**
     * Take a snapshot and upload it to Percy.
     *
     * @param name      The human-readable name of the snapshot. Should be unique.
     * @param widths    The browser widths at which you want to take the snapshot.
     *                  In pixels.
     * @param minHeight The minimum height of the resulting snapshot. In pixels.
     * @param enableJavaScript Enable JavaScript in the Percy rendering environment
     * @param percyCSS Percy specific CSS that is only applied in Percy's browsers
     */
    public void snapshot(String name, @Nullable List widths, Integer minHeight, boolean enableJavaScript, String percyCSS) {
        String domSnapshot = "";

        if (percyAgentJs == null) {
            // This would happen if we couldn't load percy-agent.js in the constructor.
            return;
        }

        try {
            JavascriptExecutor jse = (JavascriptExecutor) driver;
            jse.executeScript(percyAgentJs);
            domSnapshot = (String) jse.executeScript(buildSnapshotJS());
        } catch (WebDriverException e) {
            // For some reason, the execution in the browser failed.
            System.out.println("[percy] Something went wrong attempting to take a snapshot: " + e.getMessage());
        }

        postSnapshot(domSnapshot, name, widths, minHeight, driver.getCurrentUrl(), enableJavaScript, percyCSS);
    }

    /**
     * POST the DOM taken from the test browser to the Percy Agent node process.
     *
     * @param domSnapshot Stringified & serialized version of the site/applications DOM
     * @param name        The human-readable name of the snapshot. Should be unique.
     * @param widths      The browser widths at which you want to take the snapshot.
     *                    In pixels.
     * @param minHeight   The minimum height of the resulting snapshot. In pixels.
     * @param enableJavaScript Enable JavaScript in the Percy rendering environment
     * @param percyCSS Percy specific CSS that is only applied in Percy's browsers
     */
    private void postSnapshot(String domSnapshot, String name, @Nullable List widths, Integer minHeight, String url, boolean enableJavaScript, String percyCSS) {
        if (percyIsRunning == false) {
            return;
        }

        // Build a JSON object to POST back to the agent node process
        JSONObject json = new JSONObject();
        json.put("url", url);
        json.put("name", name);
        json.put("percyCSS", percyCSS);
        json.put("minHeight", minHeight);
        json.put("domSnapshot", domSnapshot);
        json.put("clientInfo", env.getClientInfo());
        json.put("enableJavaScript", enableJavaScript);
        json.put("environmentInfo", env.getEnvironmentInfo());
        // Sending an empty array of widths to agent breaks asset discovery
        if (widths != null && widths.size() != 0) {
            json.put("widths", widths);
        }

        StringEntity entity = new StringEntity(json.toString(), ContentType.APPLICATION_JSON);
        HttpClient httpClient = HttpClientBuilder.create().build();

        try {
            HttpPost request = new HttpPost("http://localhost:5338/percy/snapshot");
            request.setEntity(entity);
            // We don't really care about the response -- as long as their test suite doesn't fail
            HttpResponse response = httpClient.execute(request);
        } catch (Exception ex) {
            System.out.println("[percy] An error occured when sending the DOM to agent: " + ex);
            percyIsRunning = false;
            System.out.println("[percy] Percy has been disabled");
        }

    }

    private String getAgentOptions() {
        StringBuilder info = new StringBuilder();
        info.append("{ ");
        info.append(String.format("handleAgentCommunication: false"));
        info.append(" }");
        return info.toString();
    }

    /**
     * @return A String containing the JavaScript needed to instantiate a PercyAgent
     *         and take a snapshot.
     */
    private String buildSnapshotJS() {
        StringBuilder jsBuilder = new StringBuilder();
        jsBuilder.append(String.format("var percyAgentClient = new PercyAgent(%s)\n", getAgentOptions()));
        jsBuilder.append(String.format("return percyAgentClient.snapshot('not used')"));

        return jsBuilder.toString();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy