In our small but very dynamic company, more than a hundred tasks are being tested every day. All of them are checked both in the test environment and in environments more close to the real one. The vast majority of tasks related to the web, checked by autotests, which we have a lot.
About six months ago, there were so many tests and tasks that our small farm with Selenium at rush hour began to literally “choke” on requests for a new Firefox or Chrome session. It looked like this: on the Selenium grid there is a queue of sessions waiting for a free browser. Users continue to run autotests, and this queue continues to grow, but browsers are busy with old tasks and sessions “fall off” with a timeout.
At that time, the maximum number of nodes shared between Firefox, Chrome, Internet Explorer and PhantomJS was about 200. One solution that occurred to me was to track the number of free nodes before running the test and “stick” to the tests in the setup method (), while free nodes are not enough.
In the descriptions of the changes, Selenium at one time slipped through the functionality of receiving information from the grid using HTTP requests. Available commands can be viewed directly in the servlet code HubStatusServlet.java . There are only three of them: configuration (configuration), slotCounts (number of slots) and newSessionRequestCount (number of sessions in the queue for a browser).
The request format is quite tricky: this is a GET request, but with a body. For experiments, use cURL and check that these commands return:
$ curl -XGET http://selenium1:5555/grid/api/hub -d '{"configuration":[]}'
{ 'success': true, 'port': '5555', 'hubConfig': '/usr/local/selenium-rc/grid.json', 'host': 'selenium1.d3', 'servlets': 'org.openqa.grid.web.servlet.HubStatusServlet', 'cleanUpCycle': 5000, 'browserTimeout': 120000, 'newSessionWaitTimeout': 30000, 'capabilityMatcher': 'org.openqa.grid.internal.utils.DefaultCapabilityMatcher', 'prioritizer': null, 'throwOnCapabilityNotPresent': true, 'nodePolling': 5000, 'maxSession': 5, 'role': 'hub', 'jettyMaxThreads': - 1, 'timeout': 90000 }
$ curl -XGET http://selenium1:5555/grid/api/hub -d '{"configuration":["slotCounts"]}'
{ 'success': true, 'slotCounts': { 'free': 50, 'total': 196 } }
curl -XGET http://selenium1:5555/grid/api/hub -d '{"configuration":["newSessionRequestCount"]}'
{ 'success': true, 'newSessionRequestCount': 3 }
We have all the tests for Selenium written in PHP, in it a similar query will look like this:
<?php $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, 'http://selenium1:5555/grid/api/hub'); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET'); curl_setopt($curl, CURLOPT_POSTFIELDS, '{"configuration":["slotCounts"]}'); curl_exec($curl);
In principle, by requesting the total number of slots and the number of waiting sessions in the setUp () test method, you can begin to wait. But this is not very convenient if you have unevenly allocated resources to different browsers. For example, we have about a third more Firefox nodes than Google Chrome. And Internet Explorer and MS Edge occupy only about 10 nodes (and they can be divided by version). It turns out that there may be no free nodes for Chrome, although the Selenium Grid says that there are still free nodes.
Therefore, we had to add the servlet functionality in order to understand how many browsers are available to us. The patch itself is not very big , here is its code:
diff --git a/java/server/src/org/openqa/grid/web/servlet/HubStatusServlet.java b/java/server/src/org/openqa/grid/web/servlet/HubStatusServlet.java index 8b9c578..550c5db 100644 --- a/java/server/src/org/openqa/grid/web/servlet/HubStatusServlet.java +++ b/java/server/src/org/openqa/grid/web/servlet/HubStatusServlet.java @@ -29,10 +29,12 @@ import org.openqa.grid.internal.Registry; import org.openqa.grid.internal.RemoteProxy; import org.openqa.grid.internal.TestSlot; +import org.openqa.selenium.remote.CapabilityType; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -128,6 +130,11 @@ private JsonObject getResponse(HttpServletRequest request) throws IOException { paramsToReturn.remove("slotCounts"); } + if (paramsToReturn.contains("browserSlotsCount")) { + res.add("browserSlotsCount", getBrowserSlotsCount()); + paramsToReturn.remove("browserSlotsCount"); + } + for (String key : paramsToReturn) { Object value = allParams.get(key); if (value == null) { @@ -169,6 +176,53 @@ private JsonObject getSlotCounts() { return result; } + private JsonObject getBrowserSlotsCount() { + int freeSlots = 0; + int totalSlots = 0; + + Map<String, Integer> freeBrowserSlots = new HashMap<>(); + Map<String, Integer> totalBrowserSlots = new HashMap<>(); + + for (RemoteProxy proxy : getRegistry().getAllProxies()) { + for (TestSlot slot : proxy.getTestSlots()) { + String + slot_browser_name = + slot.getCapabilities().get(CapabilityType.BROWSER_NAME).toString().toUpperCase(); + if (slot.getSession() == null) { + if (freeBrowserSlots.containsKey(slot_browser_name)) { + freeBrowserSlots.put(slot_browser_name, freeBrowserSlots.get(slot_browser_name) + 1); + } else { + freeBrowserSlots.put(slot_browser_name, 1); + } + freeSlots += 1; + } + if (totalBrowserSlots.containsKey(slot_browser_name)) { + totalBrowserSlots.put(slot_browser_name, totalBrowserSlots.get(slot_browser_name) + 1); + } else { + totalBrowserSlots.put(slot_browser_name, 1); + } + totalSlots += 1; + } + } + + JsonObject result = new JsonObject(); + + for (String str : totalBrowserSlots.keySet()) { + JsonObject browser = new JsonObject(); + browser.addProperty("total", totalBrowserSlots.get(str)); + if (freeBrowserSlots.containsKey(str)) { + browser.addProperty("free", freeBrowserSlots.get(str)); + } else { + browser.addProperty("free", 0); + } + result.add(str, browser); + } + + result.addProperty("total", totalSlots); + result.addProperty("total_free", freeSlots); + return result; + } + private JsonObject getRequestJSON(HttpServletRequest request) throws IOException { JsonObject requestJSON = null; BufferedReader rd = new BufferedReader(new InputStreamReader(request.getInputStream()));
It is still a little embarrassing that I did not design it by all the rules (with tests, etc.) and did not send it to SeleniumHQ. I promise that I will do it in the near future, if readers find the functionality useful :)
We put a patch on the local copy of the source code of Selenium, we assemble our own assembly Selenium-grid ( there is a detailed assembly instruction here ). If you don’t want to mess around with the build, you can try what I’ve already collected: https://github.com/leipreachan/misc_scripts/tree/master/blob/selenium
Now restart the selenium-grid and see what values ​​it returns:
curl -XGET http://selenium1:5555/grid/api/hub -d '{"configuration":["browserSlotsCount"]}'
and the result:
{ 'success': true, 'browserSlotsCount': { 'IEXPLORER': { 'total': 4, 'free': 3 }, 'FIREFOX': { 'total': 95, 'free': 50 }, 'MICROSOFTEDGE': { 'total': 1, 'free': 1 }, 'PHANTOMJS': { 'total': 20, 'free': 20 }, 'CHROME': { 'total': 76, 'free': 75 }, 'total': 196, 'total_free': 149 } }
So, now we know what free browsers and in what quantity we have in Selenium Grid. It remains to slightly correct the setup () (or similar) method:
For us personally, it began to look like that, during rush hour, selenium tests go a little slower, but much, much more stable. Given that we have several hundreds of tests run automatically, this greatly simplified the life of everyone involved in testing.
Artyom Soldatkin
Lead qa engineer badoo
Source: https://habr.com/ru/post/307502/
All Articles