[JAVA] Add image selector to Selenium Grid

Introduction

I feel that it is necessary to do something a little complicated, such as running another application on the remote destination terminal as well as the browser.

Ah, I don't really understand this anymore, so I made it so that it could be moved with an image image: tired_face:

I don't use Stream in the code, and there are some things I'm doing, but I record it as a memorandum.

Due to the image selector, Node is only compatible with 64-bit OS.

Software used

This is the software used for verification. I am building a verification build on Windows.

soft version Use
java 64bit jdk-8.0.212.03-hotspot(AdoptOpenJDK)
selenium-server-standalone.jar 3.141.59
sikulixapi 1.1.4-SNAPSHOT Node image selector
Operates only on 64bit
gson 2.8.5 json file input / output
httpclient 4.5.8 Run REST

Environment

It's a moment with Maven.

Maven file

Maven file (decompression)

pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>selenium-grid-extend</groupId>
  <artifactId>selenium-grid-extend</artifactId>
  <version>0.0.1</version>
  <repositories>
    <repository>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
      <id>sonatype</id>
      <name>sonatype Repository</name>
      <url>http://oss.sonatype.org/content/groups/public</url>
    </repository>
  </repositories>
  <pluginRepositories>
    <pluginRepository>
      <releases>
        <updatePolicy>never</updatePolicy>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
      <id>sonatype</id>
      <name>sonatype Repository</name>
      <url>http://oss.sonatype.org/content/groups/public</url>
    </pluginRepository>
  </pluginRepositories>
  <build>
    <sourceDirectory>src</sourceDirectory>
    <testSourceDirectory>src</testSourceDirectory>
    <resources>
      <resource>
        <directory>resource</directory>
      </resource>
    </resources>
    <testResources>
      <testResource>
        <directory>resource</directory>
      </testResource>
    </testResources>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.seleniumhq.selenium</groupId>
      <artifactId>selenium-server</artifactId>
      <version>3.141.59</version>
    </dependency>
    <dependency>
    	<groupId>com.sikulix</groupId>
    	<artifactId>sikulixapi</artifactId>
    	<version>1.1.4-SNAPSHOT</version>
    </dependency>
    <dependency>
    	<groupId>com.google.code.gson</groupId>
    	<artifactId>gson</artifactId>
    	<version>2.8.5</version>
    </dependency>
    <dependency>
    	<groupId>org.apache.httpcomponents</groupId>
    	<artifactId>httpclient</artifactId>
    	<version>4.5.8</version>
    </dependency>
  </dependencies>
  <properties>
    <java.version>1.8</java.version>
    <file.encoding>UTF-8</file.encoding>
    <project.build.sourceEncoding>${file.encoding}</project.build.sourceEncoding>
    <project.reporting.outputEncoding>${file.encoding}</project.reporting.outputEncoding>
    <maven.compiler.encoding>${file.encoding}</maven.compiler.encoding>
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
  </properties>
</project>

program

This time, the request of / grid / admin / RequestToSessionMachine ~ is sent to the terminal of the session specified in the URL. All exchanges other than URLs are sent in JSON format.

Be careful, there is no session just by connecting Node. A session is created in Hub only when you can operate the browser.

Finally, the request sent to Hub as follows

http://HubIP:HubPort/grid/admin/RequestToSessionMachine/session/99999XXXXX99999/extra/ImageSelector/doubleclick

Request the Node running the "99999XXXXX99999" session as follows, wait for the result, and return it to the caller as it is.

http://NodeIP:NodePort//extra/ImageSelector/doubleclick

Hub side program

It would be best to be able to send it with TestSession.forward, but I stopped using it because I needed SeleniumBasedRequest as an argument and it was troublesome to investigate ~~.

I am updating the access time so that it does not take the session timeout time, but it may not work depending on the timeout time.

Hub side program (deployment)

RequestToSessionMachine.java


package selenium.extend.hub.servlet;

import java.io.BufferedReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.openqa.grid.common.exception.GridException;
import org.openqa.grid.internal.GridRegistry;
import org.openqa.grid.internal.TestSession;
import org.openqa.grid.internal.TestSlot;
import org.openqa.grid.web.servlet.RegistryBasedServlet;

import com.google.gson.Gson;
import com.google.gson.JsonObject;

public class RequestToSessionMachine extends RegistryBasedServlet {

    private static final Pattern SESSION_ID_PATTERN = Pattern.compile("/grid/admin/RequestToSessionMachine/session/([^/]+).*");

    public RequestToSessionMachine() {
        this(null);
    }

    public RequestToSessionMachine(GridRegistry registry) {
        super(registry);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        process(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        process(request, response);
    }

    protected void process(HttpServletRequest request, HttpServletResponse response) throws IOException {
        System.out.println("Start RequestToSessionMachine");

        response.setContentType("application/json");
        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());

        JsonObject json = new JsonObject();

        CloseableHttpClient client = null;
        CloseableHttpResponse res = null;

        try {
            //Get the information associated with the session ID of the URL
            TestSession session = getActiveTestSession(getSessionIdFromPath(request.getRequestURI()));

            if (session != null) {

                //Reset session access time(Extend the timeout period)
                session.setIgnoreTimeout(false);

                //Generate connection URL to node with session
                TestSlot slot = session.getSlot();
                URL remoteRequestURL = new URL(slot.getRemoteURL(), trimSessionPath(request.getRequestURI()));

                //Request Json of body to node as it is
                client = HttpClients.createDefault();

                HttpPost httpPost = new HttpPost(remoteRequestURL.toURI());
                httpPost.setHeader("Content-type", "application/json; charset=UTF-8");

                BufferedReader bufferReaderBody = new BufferedReader(request.getReader());
                StringBuilder jsonBody = new StringBuilder();
                String line = null;

                while ((line = bufferReaderBody.readLine()) != null) {
                    jsonBody.append(line);
                }

                StringEntity entity = new StringEntity(jsonBody.toString(), StandardCharsets.UTF_8);
                httpPost.setEntity(entity);

                res = client.execute(httpPost);

                //Reset session access time(Extend the timeout period)
                session.setIgnoreTimeout(false);

                int status = res.getStatusLine().getStatusCode();
                response.setStatus(status);

                if (status == 200) {
                    Gson gson = new Gson();
                    json = gson.fromJson(EntityUtils.toString(res.getEntity(), StandardCharsets.UTF_8), JsonObject.class);
                } else {
                    json.addProperty("error", "Response Code " + status);
                }

            } else {
                json.addProperty("error", "No Match Active Test Session for Session ID");
            }

        } catch (MalformedURLException e) {
            e.printStackTrace();
            json.addProperty("error", e.getMessage());
        } catch (URISyntaxException e) {
            e.printStackTrace();
            json.addProperty("error", e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            json.addProperty("error", e.getMessage());
        } finally {
            try {
                if (res != null) {
                    res.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
                throw new GridException(e.getMessage());
            } finally {
                if (client != null) {
                    try {
                        client.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                        throw new GridException(e.getMessage());
                    }
                }
            }
        }

        System.out.println("ResponseJson:" + json.toString());
        response.getWriter().print(json);
        response.getWriter().close();

        System.out.println("End RequestToSessionMachine");
    }

    private TestSession getActiveTestSession(String sessionId) {
        Iterator<TestSession> itr = super.getRegistry().getActiveSessions().iterator();
        TestSession session = null;

        System.out.println("Active Session Size:" + super.getRegistry().getActiveSessions().size());
        System.out.println("Search Session ID:" + sessionId);

        while (itr.hasNext()) {
            TestSession s = itr.next();
            if (s.getExternalKey().getKey().equals(sessionId)) {
                session = s;
                break;
            }
        }

        return session;
    }

    private String getSessionIdFromPath(String pathInfo) {
        Matcher matcher = SESSION_ID_PATTERN.matcher(pathInfo);
        if (matcher.matches()) {
            return matcher.group(1);
        }
        throw new IllegalArgumentException("Invalid request. Session Id is not present");
    }

    private String trimSessionPath(String pathInfo) {
        return pathInfo.replaceFirst("/grid/admin/RequestToSessionMachine/session/" + getSessionIdFromPath(pathInfo), "");
    }

}

Node side program

Using the API of sikulix, double-click the matching part in the desktop from the base64ized image of JSON sent.

sikulix-api Please see the official page for the contents that can be operated.

Node side program (expansion)

ImageSelector.java


package selenium.extend.node.servlet;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.sikuli.script.FindFailed;
import org.sikuli.script.Image;
import org.sikuli.script.Screen;

import com.google.gson.Gson;
import com.google.gson.JsonObject;

public class ImageSelector extends HttpServlet {

    private static final String DOUBLECLICK = "/extra/ImageSelector/doubleclick";

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {

        JsonObject jsonResponse = null;
        if (request.getRequestURI().startsWith(DOUBLECLICK)) {
            jsonResponse = doubleClick(request, response);

        } else {
            jsonResponse = new JsonObject();
            jsonResponse.addProperty("error", "Request Command is No Support");
        }

        response.getWriter().print(jsonResponse);
        response.getWriter().close();
    }

    protected JsonObject doubleClick(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("Start ImageSelector");

        response.setContentType("application/json");
        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        response.setStatus(200);

        JsonObject json = new JsonObject();

        try {
            //Extract the information of the body to be selected from json
            BufferedReader bufferReaderBody = new BufferedReader(request.getReader());
            StringBuilder jsonBody = new StringBuilder();
            String line = null;

            while ((line = bufferReaderBody.readLine()) != null) {
                jsonBody.append(line);
            }

            Gson gson = new Gson();
            JsonObject reqJson = gson.fromJson(jsonBody.toString(), JsonObject.class);
            String imageBase64 = reqJson.get("imageBase64").getAsString();

            String[] parts = imageBase64.split(",");
            String imageString = parts[1];

            byte[] imageByte = Base64.getDecoder().decode(imageString);
            ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);
            Image image = new Image(ImageIO.read(bis));

            bis.close();

            //Find the image on the entire screen and double click
            Screen sc = new Screen();
            //Wait time setting
            sc.setAutoWaitTimeout(30);

            sc.doubleClick(image);

            json.addProperty("info", "Done DoubleClick");

        } catch (IOException e) {
            e.printStackTrace();
            json.addProperty("error", e.getMessage());
        } catch (FindFailed e) {
            e.printStackTrace();
            json.addProperty("error", e.getMessage());
        }

        System.out.println("End ImageSelector");

        return json;
    }

}

jar file creation

Jar it for loading into Selenium Grid. The directory structure is as follows.

There is an AllNodes system created in the previous article, but this time it is not necessary. aWS050632.JPG

Export only the following files to the jar. The name is "extend.jar". aWS050634.JPG

Start execution environment

Place "extend.jar" in the Hub and Node directories and load it.

Hub startup

Hub directory structure and commands (decompression)
C:.
│  start-hub.bat
│
└─lib
        commons-logging-1.2.jar
        extend.jar
        gson-2.8.5.jar
        httpclient-4.5.8.jar
        httpcore-4.4.11.jar
        selenium-server-standalone-3.141.59.jar

start-hub.bat


java -cp lib/* org.openqa.grid.selenium.GridLauncherV3 -role hub -servlets "selenium.extend.hub.servlet.RequestToSessionMachine"

When started, the access path to RequestToSessionMachine is displayed as shown in the 4th line.

C:\selenium>java -cp lib/* org.openqa.grid.selenium.GridLauncherV3 -role hub -servlets "selenium.extend.hub.servlet.RequestToSessionMachine"
15:57:41.639 INFO [GridLauncherV3.parse] - Selenium server version: 3.141.59, revision: e82be7d358
15:57:41.764 INFO [GridLauncherV3.lambda$buildLaunchers$5] - Launching Selenium Grid hub on port XXXX
15:57:41.858 INFO [Hub.<init>] - binding selenium.extend.hub.servlet.RequestToSessionMachine to /grid/admin/RequestToSessionMachine/*
2019-05-31 15:57:42.242:INFO::main: Logging initialized @1617ms to org.seleniumhq.jetty9.util.log.StdErrLog
15:57:42.757 INFO [Hub.start] - Selenium Grid hub is up and running
15:57:42.773 INFO [Hub.start] - Nodes should register to http://XXX.XXX.XXX.XXX:XXXX/grid/register/
15:57:42.773 INFO [Hub.start] - Clients should connect to http://XXX.XXX.XXX.XXX:XXXX/wd/hub

Node startup

Hub directory structure and commands (decompression)
C:.
│  chromedriver.exe
│  NodeConfigBrowser.json
│  start-node.bat
│
└─lib
        commons-logging-1.2.jar
        extend.jar
        gson-2.8.5.jar
        httpclient-4.5.8.jar
        httpcore-4.4.11.jar
        jna-4.5.2.jar
        jna-platform-4.5.2.jar
        selenium-server-standalone-3.141.59.jar
        sikulix2tigervnc-2.0.0-SNAPSHOT.jar
        sikulixapi-1.1.4-SNAPSHOT.jar

NodeConfigBrowser.json


{
 "capabilities": [
    {
     "platform": "WINDOWS",
     "browserName": "chrome",
     "maxInstances": 1,
     "seleniumProtocol": "WebDriver"
    }
  ],
 "hub": "http://XXXXXXXX:XXXX/grid/register",
 "register": true
}

start-node.bat


java -Dwebdriver.chrome.driver=chromedriver.exe -cp lib/* org.openqa.grid.selenium.GridLauncherV3 -role node -servlets "selenium.extend.node.servlet.ImageSelector" -nodeConfig NodeConfigBrowser.json

When you start it, the access path to ImageSelector will be displayed as shown in the 4th line.

C:\selenium-node>java -Dwebdriver.chrome.driver=chromedriver.exe -cp lib/* org.openqa.grid.selenium.GridLauncherV3 -role node -servlets "selenium.extend.node.servlet.ImageSelector" -nodeConfig NodeConfigBrowser.json
16:01:08.578 INFO [GridLauncherV3.parse] - Selenium server version: 3.141.59, revision: e82be7d358
16:01:08.704 INFO [GridLauncherV3.lambda$buildLaunchers$7] - Launching a Selenium Grid node on port XXXX
16:01:09.126 INFO [SelfRegisteringRemote.addExtraServlets] - binding selenium.extend.node.servlet.ImageSelector to /extra/ImageSelector/*
2019-05-31 16:01:09.220:INFO::main: Logging initialized @981ms to org.seleniumhq.jetty9.util.log.StdErrLog
16:01:09.485 INFO [WebDriverServlet.<init>] - Initialising WebDriverServlet
16:01:09.594 INFO [SeleniumServer.boot] - Selenium Server is up and running on port 42345
16:01:09.594 INFO [GridLauncherV3.lambda$buildLaunchers$7] - Selenium Grid node is up and ready to register to the hub
16:01:09.750 INFO [SelfRegisteringRemote$1.run] - Starting auto registration thread. Will try to register every 5000 ms.
16:01:10.237 INFO [SelfRegisteringRemote.registerToHub] - Registering the node to the hub: http://XXX.XXX.XXX.XXX:XXXX/grid/register
16:01:10.549 INFO [SelfRegisteringRemote.registerToHub] - The node is registered to the hub and ready to use

Try to move

You can use curl or anything, but since it is troublesome to generate a session, we will use the environment created in the previous article.

Test program

The image of the operation destination is converted to base64 with an appropriate web service, and the information is RESTed.

It might have been easier to confirm in the future if it was incorporated into the base64 conversion program.

Test program (deployment)

GoogleTest.java


package test;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.Test;
import org.openqa.selenium.Point;

import com.google.gson.JsonObject;

import page.Google;

public class GoogleTest extends TestBase {

    @Test
    public void image() throws Exception {

        driver.get(Google._url);
        //Move your browser to an invisible position
        driver.manage().window().setPosition(new Point(-2000, 0));

        CloseableHttpClient client = null;
        CloseableHttpResponse res = null;

        JsonObject json = new JsonObject();

        try {

            //Node connection URL generation
            URL remoteRequestURL = new URL("http://localhost:4444/grid/admin/RequestToSessionMachine/session/" + driver.getSessionId() + "/extra/ImageSelector/doubleclick");

            //Request Json of body to node as it is
            client = HttpClients.createDefault();

            HttpPost httpPost = new HttpPost(remoteRequestURL.toURI());
            httpPost.setHeader("Content-type", "application/json; charset=UTF-8");

            json.addProperty("imageBase64", "data:image/png;base64,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");

            StringEntity entity = new StringEntity(json.toString(), StandardCharsets.UTF_8);
            httpPost.setEntity(entity);

            res = client.execute(httpPost);

            System.out.println(res.getStatusLine().getStatusCode());
            System.out.println(EntityUtils.toString(res.getEntity(), StandardCharsets.UTF_8));

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (res != null) {
                    res.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (client != null) {
                    try {
                        client.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
  1. Launch Chrome
  2. Access google
  3. Move Chrome out of sight
  4. Double-click on the image below

aaWS050367.png

It actually works like this on the Node side: kissing_heart: ggggg1.gif

Recommended Posts

Add image selector to Selenium Grid
Add files to jar files
Extend Selenium Grid (Hub side)
How to add ActionText function
4 Add println to the interpreter
[Rails] Add column to devise
JMX support for Selenium Grid
How to add HDD to Ubuntu
[JQuery] How to preview the selected image immediately + Add image posting gem
How to implement UI automated test using image comparison in Selenium