diff --git a/CHANGELOG.md b/CHANGELOG.md index 2790b3ae0ab..9fea60fa0a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,7 +75,9 @@ Here is an overview of all new **experimental** features: - **Grafana dashboard**: Fix dashboard to handle wildcard scaledObject variables ([#6214](https://github.com/kedacore/keda/issues/6214)) - **Kafka**: Fix logic to scale to zero on invalid offset even with earliest offsetResetPolicy ([#5689](https://github.com/kedacore/keda/issues/5689)) - **RabbitMQ Scaler**: Add connection name for AMQP ([#5958](https://github.com/kedacore/keda/issues/5958)) -- **Selenium Scaler**: Add Support for Username and Password Authentication ([#6144](https://github.com/kedacore/keda/issues/6144)) +- **Selenium Grid Scaler**: Add optional auth parameters `username`, `password`, `authType`, `accessToken` to configure a secure GraphQL endpoint ([#6144](https://github.com/kedacore/keda/issues/6144)) +- **Selenium Grid Scaler**: Add parameter `nodeMaxSessions` to configure scaler sync with `--max-sessions` capacity in the Node ([#6080](https://github.com/kedacore/keda/issues/6080)) +- **Selenium Grid Scaler**: Improve logic based on node stereotypes, node sessions and queue requests capabilities ([#6080](https://github.com/kedacore/keda/issues/6080)) - TODO ([#XXX](https://github.com/kedacore/keda/issues/XXX)) ### Fixes diff --git a/pkg/scalers/selenium_grid_scaler.go b/pkg/scalers/selenium_grid_scaler.go index 09f0adfd319..057181c87e7 100644 --- a/pkg/scalers/selenium_grid_scaler.go +++ b/pkg/scalers/selenium_grid_scaler.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - "math" "net/http" "strings" @@ -29,55 +28,86 @@ type seleniumGridScaler struct { type seleniumGridScalerMetadata struct { triggerIndex int - URL string `keda:"name=url, order=triggerMetadata;authParams"` + URL string `keda:"name=url, order=authParams;triggerMetadata"` + AuthType string `keda:"name=authType, order=authParams;resolvedEnv, optional"` + Username string `keda:"name=username, order=authParams;resolvedEnv, optional"` + Password string `keda:"name=password, order=authParams;resolvedEnv, optional"` + AccessToken string `keda:"name=accessToken, order=authParams;resolvedEnv, optional"` BrowserName string `keda:"name=browserName, order=triggerMetadata"` SessionBrowserName string `keda:"name=sessionBrowserName, order=triggerMetadata, optional"` ActivationThreshold int64 `keda:"name=activationThreshold, order=triggerMetadata, optional"` BrowserVersion string `keda:"name=browserVersion, order=triggerMetadata, optional, default=latest"` UnsafeSsl bool `keda:"name=unsafeSsl, order=triggerMetadata, optional, default=false"` PlatformName string `keda:"name=platformName, order=triggerMetadata, optional, default=linux"` - - // auth - Username string `keda:"name=username, order=authParams;resolvedEnv;triggerMetadata, optional"` - Password string `keda:"name=password, order=authParams;resolvedEnv;triggerMetadata, optional"` + NodeMaxSessions int `keda:"name=nodeMaxSessions, order=triggerMetadata, optional, default=1"` TargetValue int64 } -type seleniumResponse struct { - Data data `json:"data"` +type SeleniumResponse struct { + Data Data `json:"data"` +} + +type Data struct { + Grid Grid `json:"grid"` + NodesInfo NodesInfo `json:"nodesInfo"` + SessionsInfo SessionsInfo `json:"sessionsInfo"` +} + +type Grid struct { + SessionCount int `json:"sessionCount"` + MaxSession int `json:"maxSession"` + TotalSlots int `json:"totalSlots"` +} + +type NodesInfo struct { + Nodes Nodes `json:"nodes"` } -type data struct { - Grid grid `json:"grid"` - SessionsInfo sessionsInfo `json:"sessionsInfo"` +type SessionsInfo struct { + SessionQueueRequests []string `json:"sessionQueueRequests"` } -type grid struct { - MaxSession int `json:"maxSession"` - NodeCount int `json:"nodeCount"` +type Nodes []struct { + ID string `json:"id"` + Status string `json:"status"` + SessionCount int `json:"sessionCount"` + MaxSession int `json:"maxSession"` + SlotCount int `json:"slotCount"` + Stereotypes string `json:"stereotypes"` + Sessions Sessions `json:"sessions"` } -type sessionsInfo struct { - SessionQueueRequests []string `json:"sessionQueueRequests"` - Sessions []seleniumSession `json:"sessions"` +type ReservedNodes struct { + ID string `json:"id"` + MaxSession int `json:"maxSession"` + SlotCount int `json:"slotCount"` } -type seleniumSession struct { +type Sessions []struct { ID string `json:"id"` Capabilities string `json:"capabilities"` - NodeID string `json:"nodeId"` + Slot Slot `json:"slot"` +} + +type Slot struct { + ID string `json:"id"` + Stereotype string `json:"stereotype"` } -type capability struct { +type Capability struct { BrowserName string `json:"browserName"` BrowserVersion string `json:"browserVersion"` PlatformName string `json:"platformName"` } +type Stereotypes []struct { + Slots int `json:"slots"` + Stereotype Capability `json:"stereotype"` +} + const ( DefaultBrowserVersion string = "latest" - DefaultPlatformName string = "linux" ) func NewSeleniumGridScaler(config *scalersconfig.ScalerConfig) (Scaler, error) { @@ -121,7 +151,7 @@ func parseSeleniumGridScalerMetadata(config *scalersconfig.ScalerConfig) (*selen return meta, nil } -// No cleanup required for selenium grid scaler +// No cleanup required for Selenium Grid scaler func (s *seleniumGridScaler) Close(context.Context) error { if s.httpClient != nil { s.httpClient.CloseIdleConnections() @@ -156,7 +186,7 @@ func (s *seleniumGridScaler) GetMetricSpecForScaling(context.Context) []v2.Metri func (s *seleniumGridScaler) getSessionsCount(ctx context.Context, logger logr.Logger) (int64, error) { body, err := json.Marshal(map[string]string{ - "query": "{ grid { maxSession, nodeCount }, sessionsInfo { sessionQueueRequests, sessions { id, capabilities, nodeId } } }", + "query": "{ grid { sessionCount, maxSession, totalSlots }, nodesInfo { nodes { id, status, sessionCount, maxSession, slotCount, stereotypes, sessions { id, capabilities, slot { id, stereotype } } } }, sessionsInfo { sessionQueueRequests } }", }) if err != nil { @@ -168,8 +198,11 @@ func (s *seleniumGridScaler) getSessionsCount(ctx context.Context, logger logr.L return -1, err } - // Add HTTP Auth - req.SetBasicAuth(s.metadata.Username, s.metadata.Password) + if (s.metadata.AuthType == "" || strings.EqualFold(s.metadata.AuthType, "Basic")) && s.metadata.Username != "" && s.metadata.Password != "" { + req.SetBasicAuth(s.metadata.Username, s.metadata.Password) + } else if !strings.EqualFold(s.metadata.AuthType, "Basic") && s.metadata.AccessToken != "" { + req.Header.Set("Authorization", fmt.Sprintf("%s %s", s.metadata.AuthType, s.metadata.AccessToken)) + } res, err := s.httpClient.Do(req) if err != nil { @@ -186,62 +219,165 @@ func (s *seleniumGridScaler) getSessionsCount(ctx context.Context, logger logr.L if err != nil { return -1, err } - v, err := getCountFromSeleniumResponse(b, s.metadata.BrowserName, s.metadata.BrowserVersion, s.metadata.SessionBrowserName, s.metadata.PlatformName, logger) + v, err := getCountFromSeleniumResponse(b, s.metadata.BrowserName, s.metadata.BrowserVersion, s.metadata.SessionBrowserName, s.metadata.PlatformName, s.metadata.NodeMaxSessions, logger) if err != nil { return -1, err } return v, nil } -func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion string, sessionBrowserName string, platformName string, logger logr.Logger) (int64, error) { +func countMatchingSlotsStereotypes(stereotypes Stereotypes, request Capability, browserName string, browserVersion string, sessionBrowserName string, platformName string) int { + var matchingSlots int + for _, stereotype := range stereotypes { + if checkCapabilitiesMatch(stereotype.Stereotype, request, browserName, browserVersion, sessionBrowserName, platformName) { + matchingSlots += stereotype.Slots + } + } + return matchingSlots +} + +func countMatchingSessions(sessions Sessions, request Capability, browserName string, browserVersion string, sessionBrowserName string, platformName string, logger logr.Logger) int { + var matchingSessions int + for _, session := range sessions { + var capability = Capability{} + if err := json.Unmarshal([]byte(session.Capabilities), &capability); err == nil { + if checkCapabilitiesMatch(capability, request, browserName, browserVersion, sessionBrowserName, platformName) { + matchingSessions++ + } + } else { + logger.Error(err, fmt.Sprintf("Error when unmarshaling session capabilities: %s", err)) + } + } + return matchingSessions +} + +func checkCapabilitiesMatch(capability Capability, requestCapability Capability, browserName string, browserVersion string, sessionBrowserName string, platformName string) bool { + // Ensure the logic should be aligned with DefaultSlotMatcher in Selenium Grid - SeleniumHQ/selenium/java/src/org/openqa/selenium/grid/data/DefaultSlotMatcher.java + // A browserName matches when one of the following conditions is met: + // 1. `browserName` in capability matches with `browserName` or `sessionBrowserName` in scaler metadata + // 2. `browserName` in request capability is empty or not provided + var browserNameMatches = strings.EqualFold(capability.BrowserName, browserName) || strings.EqualFold(capability.BrowserName, sessionBrowserName) || + requestCapability.BrowserName == "" + // A browserVersion matches when one of the following conditions is met: + // 1. `browserVersion` in request capability is empty or not provided or `stable` + // 2. `browserVersion` in capability matches with prefix of the scaler metadata `browserVersion` + // 3. `browserVersion` in scaler metadata is `latest` + var browserVersionMatches = requestCapability.BrowserVersion == "" || requestCapability.BrowserVersion == "stable" || + strings.HasPrefix(capability.BrowserVersion, browserVersion) || browserVersion == DefaultBrowserVersion + // A platformName matches when one of the following conditions is met: + // 1. `platformName` in request capability is empty or not provided + // 2. `platformName` in capability is empty or not provided + // 3. `platformName` in capability matches with the scaler metadata `platformName` + // 4. `platformName` in scaler metadata is empty or not provided + var platformNameMatches = requestCapability.PlatformName == "" || capability.PlatformName == "" || + strings.EqualFold(capability.PlatformName, platformName) || platformName == "" + return browserNameMatches && browserVersionMatches && platformNameMatches +} + +func checkNodeReservedSlots(reservedNodes []ReservedNodes, nodeID string, availableSlots int) int { + for _, reservedNode := range reservedNodes { + if strings.EqualFold(reservedNode.ID, nodeID) { + return reservedNode.SlotCount + } + } + return availableSlots +} + +func updateOrAddReservedNode(reservedNodes []ReservedNodes, nodeID string, slotCount int, maxSession int) []ReservedNodes { + for i, reservedNode := range reservedNodes { + if strings.EqualFold(reservedNode.ID, nodeID) { + // Update remaining available slots for the reserved node + reservedNodes[i].SlotCount = slotCount + return reservedNodes + } + } + // Add new reserved node if not found + return append(reservedNodes, ReservedNodes{ID: nodeID, SlotCount: slotCount, MaxSession: maxSession}) +} + +func getCountFromSeleniumResponse(b []byte, browserName string, browserVersion string, sessionBrowserName string, platformName string, nodeMaxSessions int, logger logr.Logger) (int64, error) { + // The returned count of the number of new Nodes will be scaled up var count int64 - var seleniumResponse = seleniumResponse{} + // Track number of available slots of existing Nodes in the Grid can be reserved for the matched requests + var availableSlots int + // Track number of matched requests in the sessions queue will be served by this scaler + var queueSlots int + var seleniumResponse = SeleniumResponse{} if err := json.Unmarshal(b, &seleniumResponse); err != nil { return 0, err } var sessionQueueRequests = seleniumResponse.Data.SessionsInfo.SessionQueueRequests - for _, sessionQueueRequest := range sessionQueueRequests { - var capability = capability{} - if err := json.Unmarshal([]byte(sessionQueueRequest), &capability); err == nil { - if capability.BrowserName == browserName { - var platformNameMatches = capability.PlatformName == "" || strings.EqualFold(capability.PlatformName, platformName) - if strings.HasPrefix(capability.BrowserVersion, browserVersion) && platformNameMatches { - count++ - } else if len(strings.TrimSpace(capability.BrowserVersion)) == 0 && browserVersion == DefaultBrowserVersion && platformNameMatches { - count++ - } + var nodes = seleniumResponse.Data.NodesInfo.Nodes + // Track list of existing Nodes that have available slots for the matched requests + var reservedNodes []ReservedNodes + // Track list of new Nodes will be scaled up with number of available slots following scaler parameter `nodeMaxSessions` + var newRequestNodes []ReservedNodes + for requestIndex, sessionQueueRequest := range sessionQueueRequests { + var isRequestMatched bool + var requestCapability = Capability{} + if err := json.Unmarshal([]byte(sessionQueueRequest), &requestCapability); err == nil { + if checkCapabilitiesMatch(requestCapability, requestCapability, browserName, browserVersion, sessionBrowserName, platformName) { + queueSlots++ + isRequestMatched = true } } else { - logger.Error(err, fmt.Sprintf("Error when unmarshaling session queue requests: %s", err)) + logger.Error(err, fmt.Sprintf("Error when unmarshaling sessionQueueRequest capability: %s", err)) } - } - var sessions = seleniumResponse.Data.SessionsInfo.Sessions - for _, session := range sessions { - var capability = capability{} - if err := json.Unmarshal([]byte(session.Capabilities), &capability); err == nil { - var platformNameMatches = capability.PlatformName == "" || strings.EqualFold(capability.PlatformName, platformName) - if capability.BrowserName == sessionBrowserName { - if strings.HasPrefix(capability.BrowserVersion, browserVersion) && platformNameMatches { - count++ - } else if browserVersion == DefaultBrowserVersion && platformNameMatches { - count++ + // Skip the request if the capability does not match the scaler parameters + if !isRequestMatched { + continue + } + + var isRequestReserved bool + // Check if the matched request can be assigned to available slots of existing Nodes in the Grid + for _, node := range nodes { + // Check if node is UP and has available slots (maxSession > sessionCount) + if strings.EqualFold(node.Status, "UP") && checkNodeReservedSlots(reservedNodes, node.ID, node.MaxSession-node.SessionCount) > 0 { + var stereotypes = Stereotypes{} + var availableSlotsMatch int + if err := json.Unmarshal([]byte(node.Stereotypes), &stereotypes); err == nil { + // Count available slots that match the request capability and scaler metadata + availableSlotsMatch += countMatchingSlotsStereotypes(stereotypes, requestCapability, browserName, browserVersion, sessionBrowserName, platformName) + } else { + logger.Error(err, fmt.Sprintf("Error when unmarshaling node stereotypes: %s", err)) + } + // Count ongoing sessions that match the request capability and scaler metadata + var currentSessionsMatch = countMatchingSessions(node.Sessions, requestCapability, browserName, browserVersion, sessionBrowserName, platformName, logger) + // Count remaining available slots can be reserved for this request + var availableSlotsCanBeReserved = checkNodeReservedSlots(reservedNodes, node.ID, node.MaxSession-node.SessionCount) + // Reserve one available slot for the request if available slots match is greater than current sessions match + if availableSlotsMatch > currentSessionsMatch { + availableSlots++ + reservedNodes = updateOrAddReservedNode(reservedNodes, node.ID, availableSlotsCanBeReserved-1, node.MaxSession) + isRequestReserved = true + break } } - } else { - logger.Error(err, fmt.Sprintf("Error when unmarshaling sessions info: %s", err)) + } + // Check if the matched request can be assigned to available slots of new Nodes will be scaled up, since the scaler parameter `nodeMaxSessions` can be greater than 1 + if !isRequestReserved { + for _, newRequestNode := range newRequestNodes { + if newRequestNode.SlotCount > 0 { + newRequestNodes = updateOrAddReservedNode(newRequestNodes, newRequestNode.ID, newRequestNode.SlotCount-1, nodeMaxSessions) + isRequestReserved = true + break + } + } + } + // Check if a new Node should be scaled up to reserve for the matched request + if !isRequestReserved { + newRequestNodes = updateOrAddReservedNode(newRequestNodes, string(rune(requestIndex)), nodeMaxSessions-1, nodeMaxSessions) } } - var gridMaxSession = int64(seleniumResponse.Data.Grid.MaxSession) - var gridNodeCount = int64(seleniumResponse.Data.Grid.NodeCount) - - if gridMaxSession > 0 && gridNodeCount > 0 { - // Get count, convert count to next highest int64 - var floatCount = float64(count) / (float64(gridMaxSession) / float64(gridNodeCount)) - count = int64(math.Ceil(floatCount)) + if queueSlots > availableSlots { + count = int64(len(newRequestNodes)) + } else { + count = 0 } + return count, nil } diff --git a/pkg/scalers/selenium_grid_scaler_test.go b/pkg/scalers/selenium_grid_scaler_test.go index 557861b357e..4e896f5694d 100644 --- a/pkg/scalers/selenium_grid_scaler_test.go +++ b/pkg/scalers/selenium_grid_scaler_test.go @@ -16,6 +16,7 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { sessionBrowserName string browserVersion string platformName string + nodeMaxSessions int } tests := []struct { name string @@ -29,7 +30,6 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { b: []byte(nil), browserName: "", }, - // want: 0, wantErr: true, }, { @@ -38,52 +38,290 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { b: []byte(""), browserName: "", }, - // want: resource.NewQuantity(0, resource.DecimalSI), wantErr: true, }, { - name: "no active sessions should return count as 0", + name: "no sessionQueueRequests should return count as 0", args: args{ b: []byte(`{ - "data": { - "grid":{ - "maxSession": 0, - "nodeCount": 0 + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 0, + "totalSlots": 0 + }, + "nodesInfo": { + "nodes": [] }, "sessionsInfo": { - "sessionQueueRequests": [], - "sessions": [] + "sessionQueueRequests": [] } + } } - }`), + `), browserName: "", }, want: 0, wantErr: false, }, { - name: "active sessions with no matching browsername should return count as 0", + name: "12 sessionQueueRequests with 4 requests matching browserName chrome should return count as 4", args: args{ b: []byte(`{ - "data": { - "grid":{ - "maxSession": 1, - "nodeCount": 1 + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 0, + "totalSlots": 0 + }, + "nodesInfo": { + "nodes": [] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\n \"browserName\": \"chrome\",\n \"goog:chromeOptions\": {\n \"extensions\": [\n ],\n \"args\": [\n \"disable-features=DownloadBubble,DownloadBubbleV2\"\n ]\n },\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_download_file (ChromeTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_with_frames (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_download_file (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_title_and_maximize_window (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"browserName\": \"chrome\",\n \"goog:chromeOptions\": {\n \"extensions\": [\n ],\n \"args\": [\n \"disable-features=DownloadBubble,DownloadBubbleV2\"\n ]\n },\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_play_video (ChromeTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"browserName\": \"chrome\",\n \"goog:chromeOptions\": {\n \"extensions\": [\n ],\n \"args\": [\n \"disable-features=DownloadBubble,DownloadBubbleV2\"\n ]\n },\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_select_from_a_dropdown (ChromeTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_visit_basic_auth_secured_page (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_select_from_a_dropdown (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"browserName\": \"chrome\",\n \"goog:chromeOptions\": {\n \"extensions\": [\n ],\n \"args\": [\n \"disable-features=DownloadBubble,DownloadBubbleV2\"\n ]\n },\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_title (ChromeTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_title (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_accept_languages (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_play_video (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}" + ] + } + } + } + `), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + }, + want: 4, + wantErr: false, + }, + { + name: "2 sessionQueueRequests and 1 available nodeStereotypes with matching browserName firefox should return count as 1", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 7, + "totalSlots": 7 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\"\n}"], - "sessions": [ + "nodesInfo": { + "nodes": [ + { + "id": "82ee33bd-390e-4dd6-aee2-06b17ecee18e", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-m5n8z-4br6x\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "reserved", + "capabilities": "{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-m5n8z-4br6x\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}", + "slot": { + "id": "83c9d9f5-f79d-4dea-bc9b-ce61bf2bc01c", + "stereotype": "{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-m5n8z-4br6x\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}" + } } - ] + ] + }, + { + "id": "b4d3d31a-3239-4c09-a5f5-3650d4fcef48", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-s2gq6-82lwb\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "reserved", + "capabilities": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-s2gq6-82lwb\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}", + "slot": { + "id": "b03b80c0-95f8-4b9c-ba06-bebd2568ce3d", + "stereotype": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-s2gq6-82lwb\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}" + } + } + ] + }, + { + "id": "f3e67bf7-3c40-42d4-ab10-666b49c88925", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-xh95p-9c2cl\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [] + }, + { + "id": "f1e315fe-5f32-4a73-bb31-b73ed9a728e5", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-j2xbn-lq76c\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "reserved", + "capabilities": "{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-j2xbn-lq76c\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}", + "slot": { + "id": "9d91cd87-b443-4a0c-93e7-eea8c4661207", + "stereotype": "{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-j2xbn-lq76c\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}" + } + } + ] + }, + { + "id": "0ae48415-a230-4bc4-a26c-4fc4ffc3abc1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-xk6mm-2m6jh\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "reserved", + "capabilities": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-xk6mm-2m6jh\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}", + "slot": { + "id": "2c1fc5c4-881a-48fd-9b9e-b4d3ecbc1bd8", + "stereotype": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-xk6mm-2m6jh\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}" + } + } + ] + }, + { + "id": "284fa982-5be0-44a6-b64e-e2e76fe52d1f", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-bvq59-6dh6q\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "reserved", + "capabilities": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-bvq59-6dh6q\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}", + "slot": { + "id": "5f8f9ba0-0f61-473e-b367-b68d9368dc24", + "stereotype": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-bvq59-6dh6q\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}" + } + } + ] + }, + { + "id": "451442d0-3649-4b21-a5a5-32bc847f1765", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-42xbf-zpdd4\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [] + }, + { + "id": "a4d26330-e5be-4630-b4da-9078f2495ece", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-qt9z2-6xx86\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "reserved", + "capabilities": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-qt9z2-6xx86\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}", + "slot": { + "id": "38bd0b09-ffe0-46e9-8983-bd208270c8da", + "stereotype": "{\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-qt9z2-6xx86\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}" + } + } + ] + }, + { + "id": "e81f0038-fc72-4045-9de1-b98143053eae", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-v7nrv-xsfkb\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "reserved", + "capabilities": "{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-v7nrv-xsfkb\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}", + "slot": { + "id": "43b992cc-39bb-4b0f-92b6-99603a543459", + "stereotype": "{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-v7nrv-xsfkb\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n}" + } + } + ] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_accept_languages (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_play_video (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}" + ] } + } } - }`), - browserName: "", - sessionBrowserName: "", + `), + browserName: "firefox", + sessionBrowserName: "firefox", + browserVersion: "latest", + platformName: "linux", + }, + want: 1, + wantErr: false, + }, + { + name: "1 sessionQueueRequests and 1 available nodeStereotypes with matching browserName chrome should return count as 0", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 0, + "totalSlots": 0 + }, + "nodesInfo": { + "nodes": [ + { + "id": "f3e67bf7-3c40-42d4-ab10-666b49c88925", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-chrome-name-xh95p-9c2cl\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [] + }, + { + "id": "451442d0-3649-4b21-a5a5-32bc847f1765", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[\n {\n \"slots\": 1,\n \"stereotype\": {\n \"browserName\": \"firefox\",\n \"browserVersion\": \"130.0\",\n \"moz:firefoxOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002ffirefox\"\n },\n \"platformName\": \"linux\",\n \"se:containerName\": \"my-firefox-name-42xbf-zpdd4\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\n \"acceptInsecureCerts\": true,\n \"browserName\": \"firefox\",\n \"moz:debuggerAddress\": true,\n \"moz:firefoxOptions\": {\n \"prefs\": {\n \"remote.active-protocols\": 3\n },\n \"profile\": \"profile\"\n },\n \"pageLoadStrategy\": \"normal\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_accept_languages (FirefoxTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}", + "{\n \"browserName\": \"chrome\",\n \"goog:chromeOptions\": {\n \"extensions\": [\n ],\n \"args\": [\n \"disable-features=DownloadBubble,DownloadBubbleV2\"\n ]\n },\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"se:downloadsEnabled\": true,\n \"se:name\": \"test_visit_basic_auth_secured_page (ChromeTests)\",\n \"se:recordVideo\": true,\n \"se:screenResolution\": \"1920x1080\"\n}" + ] + } + } + } + `), + browserName: "chrome", + sessionBrowserName: "chrome", browserVersion: "latest", platformName: "linux", }, @@ -91,73 +329,290 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { wantErr: false, }, { - name: "active sessions with matching browsername should return count as 2", + name: "1 sessionQueueRequests Linux and 1 available nodeStereotypes Windows with matching browserName chrome should return count as 1", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 2, + "totalSlots": 2 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Windows 11\"}}]", + "sessions": [] + }, + { + "id": "node-2", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"firefox\", \"browserVersion\": \"130.0\", \"platformName\": \"Windows 11\"}}]", + "sessions": [] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"firefox\", \"browserVersion\": \"130.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}" + ] + } + } + } + `), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + }, + want: 1, + wantErr: false, + }, + { + name: "scaler browserVersion is latest, 2 sessionQueueRequests wihtout browserVersion, 2 available nodeStereotypes with different versions and platforms, should return count as 1", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 0, + "totalSlots": 0 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [] + }, + { + "id": "node-2", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"92.0\", \"platformName\": \"Windows 11\"}}]", + "sessions": [] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}" + ] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + }, + want: 1, + wantErr: false, + }, + { + name: "scaler browserVersion is latest, 5 sessionQueueRequests wihtout browserVersion also 1 different platformName, 1 available nodeStereotypes with 3 slots Linux and 1 node Windows, should return count as 1", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 6, + "totalSlots": 6 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 0, + "maxSession": 3, + "slotCount": 3, + "stereotypes": "[{\"slots\": 3, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [] + }, + { + "id": "node-2", + "status": "UP", + "sessionCount": 0, + "maxSession": 3, + "slotCount": 3, + "stereotypes": "[{\"slots\": 3, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"92.0\", \"platformName\": \"Windows 11\"}}]", + "sessions": [] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"Windows 11\"}" + ] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + }, + want: 1, + wantErr: false, + }, + { + name: "queue request with browserName browserVersion and browserVersion but no available nodes should return count as 1", args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 1, "maxSession": 1, - "nodeCount": 1 + "totalSlots": 1 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"firefox\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"firefox\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] + } + ] }, "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\"\n}"], - "sessions": [] + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] } } }`), browserName: "chrome", sessionBrowserName: "chrome", - browserVersion: "latest", + browserVersion: "91.0", platformName: "linux", }, - want: 2, + want: 1, wantErr: false, }, { - name: "2 session queue with matching browsername and browserversion should return count as 1", + name: "1 queue request with browserName browserVersion and browserVersion but 2 nodes without available slots should return count as 1", args: args{ b: []byte(`{ "data": { - "grid":{ - "maxSession": 4, - "nodeCount": 2 + "grid": { + "sessionCount": 2, + "maxSession": 2, + "totalSlots": 2 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] + }, + { + "id": "node-2", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-2", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] + } + ] }, "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\"\n}"] + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] } } }`), browserName: "chrome", sessionBrowserName: "chrome", - browserVersion: "latest", + browserVersion: "91.0", platformName: "linux", }, want: 1, wantErr: false, }, { - name: "2 active sessions with matching browsername on 2 nodes and maxSession=4 should return count as 1 (rounded up from 0.75)", + name: "2 session queue with matching browsername and browserversion of 2 available slots should return count as 0", args: args{ b: []byte(`{ "data": { - "grid":{ - "maxSession": 4, - "nodeCount": 1 + "grid": { + "sessionCount": 0, + "maxSession": 2, + "totalSlots": 2 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\"\n}"], - "sessions": [ + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [] }, { - "id": "0f9c5a941aa4d755a54b84be1f6535b2", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983d" + "id": "node-2", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] } } }`), @@ -166,66 +621,162 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "91.0", platformName: "linux", }, - want: 1, + want: 0, wantErr: false, }, { - name: "2 active sessions with matching browsername on 1 node and maxSession=3 should return count as 1 (rounded up from 0.33)", + name: "2 queue requests with browserName browserVersion and platformName matching 2 available slots on 2 different nodes should return count as 0", args: args{ b: []byte(`{ "data": { - "grid":{ - "maxSession": 3, - "nodeCount": 1 + "grid": { + "sessionCount": 2, + "maxSession": 4, + "totalSlots": 4 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\"\n}"], - "sessions": [ + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 2, + "slotCount": 2, + "stereotypes": "[{\"slots\": 2, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] }, { - "id": "0f9c5a941aa4d755a54b84be1f6535b2", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983d" + "id": "node-2", + "status": "UP", + "sessionCount": 1, + "maxSession": 2, + "slotCount": 2, + "stereotypes": "[{\"slots\": 2, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-2", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] } } }`), browserName: "chrome", sessionBrowserName: "chrome", - browserVersion: "latest", + browserVersion: "91.0", platformName: "linux", }, - want: 1, + want: 0, wantErr: false, }, { - name: "2 active sessions with matching browsername on 2 nodes should return count as 5", + name: "1 queue request with browserName browserVersion and platformName matching 1 available slot on node has 3 max sessions should return count as 0", args: args{ b: []byte(`{ "data": { - "grid":{ - "maxSession": 2, - "nodeCount": 2 + "grid": { + "sessionCount": 2, + "maxSession": 3, + "totalSlots": 3 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 2, + "maxSession": 3, + "slotCount": 3, + "stereotypes": "[{\"slots\": 3, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + }, + { + "id": "session-2", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] + } + ] }, "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}"], - "sessions": [ + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "91.0", + platformName: "linux", + }, + want: 0, + wantErr: false, + }, + { + name: "3 queue requests with browserName browserVersion and platformName but 2 running nodes are busy should return count as 3", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 2, + "maxSession": 2, + "totalSlots": 2 + }, + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] }, { - "id": "0f9c5a941aa4d755a54b84be1f6535b2", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983d" + "id": "node-2", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-2", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] } } }`), @@ -234,32 +785,57 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "91.0", platformName: "linux", }, - want: 5, + want: 3, wantErr: false, }, { - name: "2 active sessions with matching browsername on 2 nodes with 3 other versions in queue should return count as 2 with default browserVersion and PlatformName", + name: "3 queue requests with browserName browserVersion and platformName but 2 running nodes are busy with different versions should return count as 3", args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 2, "maxSession": 2, - "nodeCount": 2 + "totalSlots": 2 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}"], - "sessions": [ + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] }, { - "id": "0f9c5a941aa4d755a54b84be1f6535b2", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983d" + "id": "node-2", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-2", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"90.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"92.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"93.0\", \"platformName\": \"linux\"}" + ] } } }`), @@ -268,32 +844,57 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "latest", platformName: "linux", }, - want: 2, + want: 3, wantErr: false, }, { - name: "2 active sessions with matching browsername on 2 nodes should return count as 5 with default browserVersion / PlatformName and incoming sessions do not have versions", + name: "3 queue requests with browserName and platformName but 2 running nodes are busy with different versions should return count as 3", args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 2, "maxSession": 2, - "nodeCount": 2 + "totalSlots": 2 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\"}","{\n \"browserName\": \"chrome\"}","{\n \"browserName\": \"chrome\"}"], - "sessions": [ + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] }, { - "id": "0f9c5a941aa4d755a54b84be1f6535b2", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983d" + "id": "node-2", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-2", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}" + ] } } }`), @@ -302,7 +903,7 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "latest", platformName: "linux", }, - want: 5, + want: 3, wantErr: false, }, { @@ -310,19 +911,34 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 1, "maxSession": 1, - "nodeCount": 1 + "totalSlots": 1 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"chrome\"\n}"], - "sessions": [ + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] } } }`), @@ -335,23 +951,163 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { wantErr: false, }, { - name: "1 active msedge session with matching browsername/sessionBrowserName should return count as 3", + name: "1 request without browserName and browserVersion stable can be match any available node should return count as 0", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 1, + "totalSlots": 1 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"v128.0\", \"platformName\": \"linux\"}}]", + "sessions": [] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserVersion\": \"stable\", \"platformName\": \"linux\"}" + ] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + }, + want: 0, + wantErr: false, + }, + { + name: "1 request without browserName and browserVersion stable should return count as 1", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 1, + "maxSession": 1, + "totalSlots": 1 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"v128.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"v128.0\", \"platformName\": \"linux\"}" + } + ] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserVersion\": \"stable\", \"platformName\": \"linux\"}" + ] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + }, + want: 1, + wantErr: false, + }, + { + name: "2 queue requests with browserName in string match node stereotype and scaler metadata browserVersion should return count as 1", args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 1, "maxSession": 1, - "nodeCount": 1 + "totalSlots": 1 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"msedge\", \"browserVersion\": \"dev\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"msedge\", \"browserVersion\": \"dev\", \"platformName\": \"linux\"}" + } + ] + } + ] }, "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"MicrosoftEdge\",\n \"browserVersion\": \"91.0\"\n}","{\n \"browserName\": \"MicrosoftEdge\",\n \"browserVersion\": \"91.0\"\n}"], - "sessions": [ + "sessionQueueRequests": [ + "{\"browserName\": \"MicrosoftEdge\", \"browserVersion\": \"beta\", \"platformName\": \"linux\"}", + "{\"browserName\": \"MicrosoftEdge\", \"browserVersion\": \"dev\", \"platformName\": \"linux\"}" + ] + } + } + }`), + browserName: "MicrosoftEdge", + sessionBrowserName: "msedge", + browserVersion: "dev", + platformName: "linux", + }, + want: 1, + wantErr: false, + }, + { + name: "2 queue requests with matching browsername/sessionBrowserName but 1 node is busy should return count as 2", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 1, + "maxSession": 1, + "totalSlots": 1 + }, + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"msedge\",\n \"browserVersion\": \"91.0.4472.114\",\n \"msedge\": {\n \"msedgedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"ms:edgeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"msedge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"msedge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"MicrosoftEdge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"MicrosoftEdge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] } } }`), @@ -360,27 +1116,124 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "91.0", platformName: "linux", }, - want: 3, + want: 2, wantErr: false, }, { - name: "1 active msedge session while asking for 2 chrome sessions should return a count of 2", + name: "2 queue requests with matching browsername/sessionBrowserName and 1 node is is available should return count as 1", args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 0, "maxSession": 1, - "nodeCount": 1 + "totalSlots": 1 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"msedge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"MicrosoftEdge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}", + "{\"browserName\": \"MicrosoftEdge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + ] + } + } + }`), + browserName: "MicrosoftEdge", + sessionBrowserName: "msedge", + browserVersion: "91.0", + platformName: "linux", + }, + want: 1, + wantErr: false, + }, { + name: "2 queue requests with platformName and without platformName and node with 1 slot available should return count as 1", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 1, + "maxSession": 2, + "totalSlots": 2 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 2, + "slotCount": 2, + "stereotypes": "[{\"slots\": 2, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"Windows 11\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"Windows 11\"}" + } + ] + } + ] }, "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\"\n}"], - "sessions": [ + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"Windows 11\"}" + ] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "91.0", + platformName: "Windows 11", + }, + want: 1, + wantErr: false, + }, + { + name: "1 active msedge session while asking for 2 chrome sessions should return a count of 2", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 1, + "maxSession": 1, + "totalSlots": 1 + }, + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"msedge\",\n \"browserVersion\": \"91.0.4472.114\",\n \"msedge\": {\n \"msedgedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"ms:edgeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"msedge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"msedge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}" + ] } } }`), @@ -393,23 +1246,39 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { wantErr: false, }, { - name: "1 active msedge session with maxSessions=3 while asking for 3 chrome sessions should return a count of 1", + name: "3 queue requests browserName chrome platformName linux but 1 node has maxSessions=3 with browserName msedge should return a count of 3", args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 1, "maxSession": 3, - "nodeCount": 1 + "totalSlots": 3 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\"\n}"], - "sessions": [ + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"msedge\",\n \"browserVersion\": \"91.0.4472.114\",\n \"msedge\": {\n \"msedgedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"ms:edgeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "node-1", + "status": "UP", + "sessionCount": 1, + "maxSession": 3, + "slotCount": 3, + "stereotypes": "[{\"slots\": 3, \"stereotype\": {\"browserName\": \"msedge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}}]", + "sessions": [ + { + "id": "session-1", + "capabilities": "{\"browserName\": \"msedge\", \"browserVersion\": \"91.0\", \"platformName\": \"linux\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}" + ] } } }`), @@ -418,7 +1287,7 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "latest", platformName: "linux", }, - want: 1, + want: 3, wantErr: false, }, { @@ -426,36 +1295,58 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { args: args{ b: []byte(`{ "data": { - "grid":{ - "maxSession": 1, - "nodeCount": 1 + "grid": { + "maxSession": 0, + "nodeCount": 0, + "totalSlots": 0 + }, + "nodesInfo": { + "nodes": [] }, "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\"\n}","{\n \"browserName\": \"chrome\",\n \"platformName\": \"Windows 11\"\n}"], - "sessions": [] + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"Windows 11\"}" + ] } } }`), browserName: "chrome", sessionBrowserName: "chrome", browserVersion: "latest", - platformName: "Windows 11", + platformName: "", }, want: 2, wantErr: false, }, { - name: "sessions requests with matching browsername and platformName should return count as 1", + name: "2 queue requests with 1 matching browsername and platformName and 1 existing slot is available should return count as 0", args: args{ b: []byte(`{ "data": { - "grid":{ + "grid": { + "sessionCount": 0, "maxSession": 1, - "nodeCount": 1 + "totalSlots": 1 + }, + "nodesInfo": { + "nodes": [ + { + "id": "node-1", + "status": "UP", + "sessionCount": 0, + "maxSession": 1, + "slotCount": 1, + "stereotypes": "[{\"slots\": 1, \"stereotype\": {\"browserName\": \"chrome\", \"browserVersion\": \"91.0\", \"platformName\": \"Windows 11\"}}]", + "sessions": [] + } + ] }, "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"platformName\": \"linux\"\n}","{\n \"browserName\": \"chrome\",\n \"platformName\": \"Windows 11\"\n}"], - "sessions": [] + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"Windows 11\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}" + ] } } }`), @@ -464,32 +1355,46 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "latest", platformName: "Windows 11", }, - want: 1, + want: 0, wantErr: false, }, { - name: "sessions requests and active sessions with matching browsername and platformName should return count as 2", + name: "2 queue requests with 1 request matching browserName and platformName but 1 existing node is busy should return count as 1", args: args{ b: []byte(`{ "data": { - "grid":{ - "maxSession": 1, - "nodeCount": 1 + "grid": { + "sessionCount": 2, + "maxSession": 2, + "totalSlots": 2 }, - "sessionsInfo": { - "sessionQueueRequests": ["{\n \"browserName\": \"chrome\",\n \"platformName\": \"linux\"\n}","{\n \"browserName\": \"chrome\",\n \"platformName\": \"Windows 11\",\n \"browserVersion\": \"91.0\"\n}"], - "sessions": [ + "nodesInfo": { + "nodes": [ { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"Windows 11\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" - }, - { - "id": "0f9c5a941aa4d755a54b84be1f6535b1", - "capabilities": "{\n \"acceptInsecureCerts\": false,\n \"browserName\": \"chrome\",\n \"browserVersion\": \"91.0.4472.114\",\n \"chrome\": {\n \"chromedriverVersion\": \"91.0.4472.101 (af52a90bf87030dd1523486a1cd3ae25c5d76c9b-refs\\u002fbranch-heads\\u002f4472@{#1462})\",\n \"userDataDir\": \"\\u002ftmp\\u002f.com.google.Chrome.DMqx9m\"\n },\n \"goog:chromeOptions\": {\n \"debuggerAddress\": \"localhost:35839\"\n },\n \"networkConnectionEnabled\": false,\n \"pageLoadStrategy\": \"normal\",\n \"platformName\": \"linux\",\n \"proxy\": {\n },\n \"se:cdp\": \"http:\\u002f\\u002flocalhost:35839\",\n \"se:cdpVersion\": \"91.0.4472.114\",\n \"se:vncEnabled\": true,\n \"se:vncLocalAddress\": \"ws:\\u002f\\u002flocalhost:7900\\u002fwebsockify\",\n \"setWindowRect\": true,\n \"strictFileInteractability\": false,\n \"timeouts\": {\n \"implicit\": 0,\n \"pageLoad\": 300000,\n \"script\": 30000\n },\n \"unhandledPromptBehavior\": \"dismiss and notify\",\n \"webauthn:extension:largeBlob\": true,\n \"webauthn:virtualAuthenticators\": true\n}", - "nodeId": "d44dcbc5-0b2c-4d5e-abf4-6f6aa5e0983c" + "id": "82ee33bd-390e-4dd6-aee2-06b17ecee18e", + "status": "UP", + "sessionCount": 2, + "maxSession": 2, + "slotCount": 2, + "stereotypes": "[\n {\n \"slots\": 2,\n \"stereotype\": {\n \"browserName\": \"chrome\",\n \"browserVersion\": \"128.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"se:containerName\": \"my-chrome-name-m5n8z-4br6x\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "0f9c5a941aa4d755a54b84be1f6535b1", + "capabilities": "{\"browserName\": \"chrome\", \"platformName\": \"Windows 11\", \"browserVersion\": \"91.0\"}" + }, + { + "id": "0f9c5a941aa4d755a54b84be1f6535b1", + "capabilities": "{\"browserName\": \"chrome\", \"platformName\": \"linux\", \"browserVersion\": \"91.0\"}" + } + ] } ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"Windows 11\"}" + ] } } }`), @@ -498,13 +1403,133 @@ func Test_getCountFromSeleniumResponse(t *testing.T) { browserVersion: "91.0", platformName: "Windows 11", }, + want: 1, + wantErr: false, + }, + { + name: "5 queue requests with scaler parameter nodeMaxSessions is 2 should return count as 3", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 0, + "totalSlots": 0 + }, + "nodesInfo": { + "nodes": [] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}" + ] + } + } + } + `), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + nodeMaxSessions: 2, + }, + want: 3, + wantErr: false, + }, + { + name: "5 queue requests with scaler parameter nodeMaxSessions is 3 should return count as 2", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 0, + "maxSession": 0, + "totalSlots": 0 + }, + "nodesInfo": { + "nodes": [] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}", + "{\"browserName\": \"chrome\", \"browserVersion\": \"128.0\", \"platformName\": \"Linux\"}" + ] + } + } + } + `), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "latest", + platformName: "linux", + nodeMaxSessions: 3, + }, + want: 2, + wantErr: false, + }, + { + name: "5 queue requests with request matching browserName and platformName and scaler param nodeMaxSessions is 3 and existing node with 1 available slot should return count as 2", + args: args{ + b: []byte(`{ + "data": { + "grid": { + "sessionCount": 2, + "maxSession": 3, + "totalSlots": 3 + }, + "nodesInfo": { + "nodes": [ + { + "id": "82ee33bd-390e-4dd6-aee2-06b17ecee18e", + "status": "UP", + "sessionCount": 2, + "maxSession": 3, + "slotCount": 3, + "stereotypes": "[\n {\n \"slots\": 3,\n \"stereotype\": {\n \"browserName\": \"chrome\",\n \"platformName\": \"linux\",\n \"browserVersion\": \"91.0\",\n \"goog:chromeOptions\": {\n \"binary\": \"\\u002fusr\\u002fbin\\u002fchromium\"\n },\n \"se:containerName\": \"my-chrome-name-m5n8z-4br6x\",\n \"se:downloadsEnabled\": true,\n \"se:noVncPort\": 7900,\n \"se:vncEnabled\": true\n }\n }\n]", + "sessions": [ + { + "id": "0f9c5a941aa4d755a54b84be1f6535b1", + "capabilities": "{\"browserName\": \"chrome\", \"platformName\": \"Linux\", \"browserVersion\": \"91.0\"}" + }, + { + "id": "0f9c5a941aa4d755a54b84be1f6535b1", + "capabilities": "{\"browserName\": \"chrome\", \"platformName\": \"linux\", \"browserVersion\": \"91.0\"}" + } + ] + } + ] + }, + "sessionsInfo": { + "sessionQueueRequests": [ + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}", + "{\"browserName\": \"chrome\", \"platformName\": \"linux\"}" + ] + } + } + }`), + browserName: "chrome", + sessionBrowserName: "chrome", + browserVersion: "91.0", + platformName: "linux", + nodeMaxSessions: 3, + }, want: 2, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := getCountFromSeleniumResponse(tt.args.b, tt.args.browserName, tt.args.browserVersion, tt.args.sessionBrowserName, tt.args.platformName, logr.Discard()) + got, err := getCountFromSeleniumResponse(tt.args.b, tt.args.browserName, tt.args.browserVersion, tt.args.sessionBrowserName, tt.args.platformName, tt.args.nodeMaxSessions, logr.Discard()) if (err != nil) != tt.wantErr { t.Errorf("getCountFromSeleniumResponse() error = %v, wantErr %v", err, tt.wantErr) return @@ -564,6 +1589,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { TargetValue: 1, BrowserVersion: "latest", PlatformName: "linux", + NodeMaxSessions: 1, }, }, { @@ -585,6 +1611,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { TargetValue: 1, BrowserVersion: "latest", PlatformName: "linux", + NodeMaxSessions: 1, }, }, { @@ -592,7 +1619,9 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { args: args{ config: &scalersconfig.ScalerConfig{ AuthParams: map[string]string{ - "url": "http://user:password@selenium-hub:4444/graphql", + "url": "http://selenium-hub:4444/graphql", + "username": "user", + "password": "password", }, TriggerMetadata: map[string]string{ "browserName": "MicrosoftEdge", @@ -602,12 +1631,15 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { }, wantErr: false, want: &seleniumGridScalerMetadata{ - URL: "http://user:password@selenium-hub:4444/graphql", + URL: "http://selenium-hub:4444/graphql", + Username: "user", + Password: "password", BrowserName: "MicrosoftEdge", SessionBrowserName: "msedge", TargetValue: 1, BrowserVersion: "latest", PlatformName: "linux", + NodeMaxSessions: 1, }, }, { @@ -635,6 +1667,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { PlatformName: "linux", Username: "username", Password: "password", + NodeMaxSessions: 1, }, }, { @@ -658,6 +1691,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { BrowserVersion: "91.0", UnsafeSsl: false, PlatformName: "linux", + NodeMaxSessions: 1, }, }, { @@ -683,6 +1717,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { BrowserVersion: "91.0", UnsafeSsl: true, PlatformName: "linux", + NodeMaxSessions: 1, }, }, { @@ -723,6 +1758,7 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { BrowserVersion: "91.0", UnsafeSsl: true, PlatformName: "linux", + NodeMaxSessions: 1, }, }, { @@ -749,6 +1785,145 @@ func Test_parseSeleniumGridScalerMetadata(t *testing.T) { BrowserVersion: "91.0", UnsafeSsl: true, PlatformName: "Windows 11", + NodeMaxSessions: 1, + }, + }, + { + name: "valid url, browsername, unsafeSsl, activationThreshold, nodeMaxSessions and platformName with trigger auth params should return metadata", + args: args{ + config: &scalersconfig.ScalerConfig{ + TriggerMetadata: map[string]string{ + "url": "http://selenium-hub:4444/graphql", + "browserName": "chrome", + "browserVersion": "91.0", + "unsafeSsl": "true", + "activationThreshold": "10", + "platformName": "Windows 11", + "nodeMaxSessions": "3", + }, + AuthParams: map[string]string{ + "username": "user", + "password": "password", + }, + }, + }, + wantErr: false, + want: &seleniumGridScalerMetadata{ + URL: "http://selenium-hub:4444/graphql", + Username: "user", + Password: "password", + BrowserName: "chrome", + SessionBrowserName: "chrome", + TargetValue: 1, + ActivationThreshold: 10, + BrowserVersion: "91.0", + UnsafeSsl: true, + PlatformName: "Windows 11", + NodeMaxSessions: 3, + }, + }, + { + name: "url in trigger auth param takes precedence over url in trigger metadata", + args: args{ + config: &scalersconfig.ScalerConfig{ + TriggerMetadata: map[string]string{ + "url": "http://invalid.dns:4444/graphql", + "browserName": "chrome", + "browserVersion": "91.0", + "unsafeSsl": "true", + "activationThreshold": "10", + "platformName": "Windows 11", + "nodeMaxSessions": "3", + }, + AuthParams: map[string]string{ + "url": "http://selenium-hub:4444/graphql", + "username": "user", + "password": "password", + }, + }, + }, + wantErr: false, + want: &seleniumGridScalerMetadata{ + URL: "http://selenium-hub:4444/graphql", + Username: "user", + Password: "password", + BrowserName: "chrome", + SessionBrowserName: "chrome", + TargetValue: 1, + ActivationThreshold: 10, + BrowserVersion: "91.0", + UnsafeSsl: true, + PlatformName: "Windows 11", + NodeMaxSessions: 3, + }, + }, + { + name: "auth type is not Basic and access token is provided", + args: args{ + config: &scalersconfig.ScalerConfig{ + TriggerMetadata: map[string]string{ + "url": "http://selenium-hub:4444/graphql", + "browserName": "chrome", + "browserVersion": "91.0", + "unsafeSsl": "true", + "activationThreshold": "10", + "platformName": "Windows 11", + "nodeMaxSessions": "3", + }, + AuthParams: map[string]string{ + "url": "http://selenium-hub:4444/graphql", + "authType": "OAuth2", + "accessToken": "my-access-token", + }, + }, + }, + wantErr: false, + want: &seleniumGridScalerMetadata{ + URL: "http://selenium-hub:4444/graphql", + AuthType: "OAuth2", + AccessToken: "my-access-token", + BrowserName: "chrome", + SessionBrowserName: "chrome", + TargetValue: 1, + ActivationThreshold: 10, + BrowserVersion: "91.0", + UnsafeSsl: true, + PlatformName: "Windows 11", + NodeMaxSessions: 3, + }, + }, + { + name: "authenticating with bearer access token", + args: args{ + config: &scalersconfig.ScalerConfig{ + TriggerMetadata: map[string]string{ + "browserName": "chrome", + "browserVersion": "91.0", + "unsafeSsl": "true", + "activationThreshold": "10", + "platformName": "Windows 11", + "nodeMaxSessions": "3", + }, + AuthParams: map[string]string{ + "url": "http://selenium-hub:4444/graphql", + "authType": "Bearer", + "accessToken": "my-access-token", + }, + }, + }, + wantErr: false, + want: &seleniumGridScalerMetadata{ + URL: "http://selenium-hub:4444/graphql", + AuthType: "Bearer", + AccessToken: "my-access-token", + BrowserName: "chrome", + SessionBrowserName: "chrome", + TargetValue: 1, + ActivationThreshold: 10, + BrowserVersion: "91.0", + UnsafeSsl: true, + PlatformName: "Windows 11", + NodeMaxSessions: 3, }, }, } diff --git a/tests/scalers/selenium/selenium_test.go b/tests/scalers/selenium/selenium_test.go index 8d4b43cef19..a5bb01bdd97 100644 --- a/tests/scalers/selenium/selenium_test.go +++ b/tests/scalers/selenium/selenium_test.go @@ -24,33 +24,47 @@ const ( ) var ( - testNamespace = fmt.Sprintf("%s-ns", testName) - chromeDeploymentName = fmt.Sprintf("%s-chrome", testName) - firefoxDeploymentName = fmt.Sprintf("%s-firefox", testName) - edgeDeploymentName = fmt.Sprintf("%s-edge", testName) - hubDeploymentName = fmt.Sprintf("%s-hub", testName) - scaledObjectName = fmt.Sprintf("%s-so", testName) - hubHost = fmt.Sprintf("selenium-hub.%s", testNamespace) - hubPort = 4444 - hubGraphURL = fmt.Sprintf("http://%s:%d/graphql", hubHost, hubPort) - minReplicaCount = 0 - maxReplicaCount = 1 + testNamespace = fmt.Sprintf("%s-ns", testName) + secretName = fmt.Sprintf("%s-secret", testName) + triggerAuthName = fmt.Sprintf("%s-trigger-auth", testName) + chromeDeploymentName = fmt.Sprintf("%s-chrome", testName) + firefoxDeploymentName = fmt.Sprintf("%s-firefox", testName) + edgeDeploymentName = fmt.Sprintf("%s-edge", testName) + hubDeploymentName = fmt.Sprintf("%s-hub", testName) + scaledObjectName = fmt.Sprintf("%s-so", testName) + hubHost = fmt.Sprintf("%s:%s@selenium-hub.%s", hubBasicAuthUsername, hubBasicAuthPassword, testNamespace) + hubPort = 4444 + hubGraphURL = fmt.Sprintf("http://%s:%d/graphql", hubHost, hubPort) + hubBasicAuthUsername = "admin" + hubBasicAuthPassword = "admin" + hubBasicAuthUsernameB64enc = "YWRtaW4=" + hubBasicAuthPasswordB64enc = "YWRtaW4=" + hubBasicAuthHeader = "YWRtaW46YWRtaW4=" + minReplicaCount = 0 + maxReplicaCount = 1 ) type templateData struct { - TestNamespace string - ChromeDeploymentName string - FirefoxDeploymentName string - EdgeDeploymentName string - HubDeploymentName string - HubHost string - HubPort int - HubGraphURL string - WithVersion bool - JobName string - ScaledObjectName string - MinReplicaCount int - MaxReplicaCount int + TestNamespace string + SecretName string + TriggerAuthName string + ChromeDeploymentName string + FirefoxDeploymentName string + EdgeDeploymentName string + HubDeploymentName string + HubHost string + HubPort int + HubGraphURL string + HubBasicAuthUsername string + HubBasicAuthPassword string + HubBasicAuthUsernameB64enc string + HubBasicAuthPasswordB64enc string + HubBasicAuthHeader string + WithVersion bool + JobName string + ScaledObjectName string + MinReplicaCount int + MaxReplicaCount int } const ( @@ -63,9 +77,9 @@ metadata: labels: app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest data: SE_EVENT_BUS_HOST: selenium-hub SE_EVENT_BUS_PUBLISH_PORT: "4442" @@ -82,9 +96,9 @@ metadata: name: selenium-chrome-node app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: type: ClusterIP selector: @@ -107,9 +121,9 @@ metadata: app.kubernetes.io/name: selenium-chrome-node app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: replicas: 0 selector: @@ -123,7 +137,7 @@ spec: spec: containers: - name: selenium-chrome-node - image: selenium/node-chrome:4.0.0-rc-1-prerelease-20210618 + image: selenium/node-chrome:latest imagePullPolicy: IfNotPresent envFrom: - configMapRef: @@ -141,6 +155,34 @@ spec: sizeLimit: 1Gi ` + secretTemplate = ` +apiVersion: v1 +kind: Secret +metadata: + name: {{.SecretName}} + namespace: {{.TestNamespace}} +type: Opaque +data: + username: '{{.HubBasicAuthUsernameB64enc}}' + password: '{{.HubBasicAuthPasswordB64enc}}' +` + + scaledTriggerAuthTemplate = ` +apiVersion: keda.sh/v1alpha1 +kind: TriggerAuthentication +metadata: + name: {{.TriggerAuthName}} + namespace: {{.TestNamespace}} +spec: + secretTargetRef: + - parameter: username + name: {{.SecretName}} + key: username + - parameter: password + name: {{.SecretName}} + key: password +` + chromeScaledObjectTemplate = ` apiVersion: keda.sh/v1alpha1 kind: ScaledObject @@ -159,6 +201,8 @@ spec: url: '{{.HubGraphURL}}' browserName: 'chrome' activationThreshold: '1' + authenticationRef: + name: '{{.TriggerAuthName}}' ` firefoxNodeServiceTemplate = ` @@ -171,9 +215,9 @@ metadata: name: selenium-firefox-node app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: type: ClusterIP selector: @@ -195,9 +239,9 @@ metadata: app.kubernetes.io/name: selenium-firefox-node app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: replicas: 0 selector: @@ -211,7 +255,7 @@ spec: spec: containers: - name: selenium-firefox-node - image: selenium/node-firefox:4.0.0-rc-1-prerelease-20210618 + image: selenium/node-firefox:latest imagePullPolicy: IfNotPresent envFrom: - configMapRef: @@ -247,6 +291,8 @@ spec: url: '{{.HubGraphURL}}' browserName: 'firefox' activationThreshold: '1' + authenticationRef: + name: '{{.TriggerAuthName}}' ` edgeNodeServiceTemplate = ` @@ -259,9 +305,9 @@ metadata: name: selenium-edge-node app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: type: ClusterIP selector: @@ -284,9 +330,9 @@ metadata: app.kubernetes.io/name: selenium-edge-node app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: replicas: 0 selector: @@ -300,7 +346,7 @@ spec: spec: containers: - name: selenium-edge-node - image: selenium/node-edge:4.0.0-rc-1-prerelease-20210618 + image: selenium/node-edge:latest imagePullPolicy: IfNotPresent envFrom: - configMapRef: @@ -337,6 +383,8 @@ spec: browserName: 'MicrosoftEdge' sessionBrowserName: 'msedge' activationThreshold: '1' + authenticationRef: + name: '{{.TriggerAuthName}}' ` hubServiceTemplate = ` @@ -349,9 +397,9 @@ metadata: app: selenium-hub app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: selector: app: selenium-hub @@ -382,9 +430,9 @@ metadata: app.kubernetes.io/name: selenium-hub app.kubernetes.io/managed-by: helm app.kubernetes.io/instance: selenium-hpa - app.kubernetes.io/version: 4.0.0-beta-1-prerelease-20210114 - app.kubernetes.io/component: selenium-grid-4.0.0-beta-1-prerelease-20210114 - helm.sh/chart: selenium-grid-0.2.0 + app.kubernetes.io/version: latest + app.kubernetes.io/component: latest + helm.sh/chart: latest spec: replicas: 1 selector: @@ -396,8 +444,13 @@ spec: spec: containers: - name: selenium-hub - image: selenium/hub:4.0.0-rc-1-prerelease-20210618 + image: selenium/hub:latest imagePullPolicy: IfNotPresent + env: + - name: SE_ROUTER_USERNAME + value: '{{.HubBasicAuthUsername}}' + - name: SE_ROUTER_PASSWORD + value: '{{.HubBasicAuthPassword}}' ports: - containerPort: 4444 protocol: TCP @@ -409,6 +462,9 @@ spec: httpGet: path: /wd/hub/status port: 4444 + httpHeaders: + - name: Authorization + value: Basic {{.HubBasicAuthHeader}} initialDelaySeconds: 10 periodSeconds: 10 timeoutSeconds: 10 @@ -418,6 +474,9 @@ spec: httpGet: path: /wd/hub/status port: 4444 + httpHeaders: + - name: Authorization + value: Basic {{.HubBasicAuthHeader}} initialDelaySeconds: 12 periodSeconds: 10 timeoutSeconds: 10 @@ -519,18 +578,27 @@ func testScaleIn(t *testing.T, kc *kubernetes.Clientset) { func getTemplateData() (templateData, []Template) { return templateData{ - TestNamespace: testNamespace, - ChromeDeploymentName: chromeDeploymentName, - FirefoxDeploymentName: firefoxDeploymentName, - EdgeDeploymentName: edgeDeploymentName, - HubDeploymentName: hubDeploymentName, - HubHost: hubHost, - HubPort: hubPort, - HubGraphURL: hubGraphURL, - ScaledObjectName: scaledObjectName, - MinReplicaCount: minReplicaCount, - MaxReplicaCount: maxReplicaCount, + TestNamespace: testNamespace, + SecretName: secretName, + TriggerAuthName: triggerAuthName, + ChromeDeploymentName: chromeDeploymentName, + FirefoxDeploymentName: firefoxDeploymentName, + EdgeDeploymentName: edgeDeploymentName, + HubDeploymentName: hubDeploymentName, + HubHost: hubHost, + HubPort: hubPort, + HubGraphURL: hubGraphURL, + HubBasicAuthUsername: hubBasicAuthUsername, + HubBasicAuthPassword: hubBasicAuthPassword, + HubBasicAuthUsernameB64enc: hubBasicAuthUsernameB64enc, + HubBasicAuthPasswordB64enc: hubBasicAuthPasswordB64enc, + HubBasicAuthHeader: hubBasicAuthHeader, + ScaledObjectName: scaledObjectName, + MinReplicaCount: minReplicaCount, + MaxReplicaCount: maxReplicaCount, }, []Template{ + {Name: "secretTemplate", Config: secretTemplate}, + {Name: "scaledTriggerAuthTemplate", Config: scaledTriggerAuthTemplate}, {Name: "eventBusConfigTemplate", Config: eventBusConfigTemplate}, {Name: "hubDeploymentTemplate", Config: hubDeploymentTemplate}, {Name: "hubServiceTemplate", Config: hubServiceTemplate},