From 6bc5a589ddebcf825e49dd10d20fb9e784ff2a24 Mon Sep 17 00:00:00 2001 From: Puja Jagani Date: Mon, 30 Jan 2023 17:35:15 +0530 Subject: [PATCH] [java] Merge capabilities of slot with the new session request capabilities (#11369) --- .../config/SessionCapabilitiesMutator.java | 136 ++++++-- .../SessionCapabilitiesMutatorTest.java | 292 ++++++++++++++++++ 2 files changed, 408 insertions(+), 20 deletions(-) create mode 100644 java/test/org/openqa/selenium/grid/node/config/SessionCapabilitiesMutatorTest.java diff --git a/java/src/org/openqa/selenium/grid/node/config/SessionCapabilitiesMutator.java b/java/src/org/openqa/selenium/grid/node/config/SessionCapabilitiesMutator.java index 476ea85f81955..e1efabcf18f4b 100644 --- a/java/src/org/openqa/selenium/grid/node/config/SessionCapabilitiesMutator.java +++ b/java/src/org/openqa/selenium/grid/node/config/SessionCapabilitiesMutator.java @@ -22,8 +22,9 @@ import org.openqa.selenium.Capabilities; import org.openqa.selenium.ImmutableCapabilities; import org.openqa.selenium.PersistentCapabilities; - +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Function; @@ -56,33 +57,128 @@ public Capabilities apply(Capabilities capabilities) { } String browserName = capabilities.getBrowserName().toLowerCase(); - if (!BROWSER_OPTIONS.containsKey(browserName)) { - return capabilities; - } - String options = BROWSER_OPTIONS.get(browserName); - if (!slotStereotype.asMap().containsKey(options)) { - return capabilities; + if (slotStereotype.asMap().containsKey(options) && capabilities.asMap().containsKey(options)) { + + @SuppressWarnings("unchecked") + Map + stereotypeOptions = + (Map) slotStereotype.asMap().get(options); + + @SuppressWarnings("unchecked") + Map capsOptions = (Map) capabilities.asMap().get(options); + + // Merge top level capabilities, excluding browser specific options. + // This will overwrite the browser options too, but it does not matter since we tackle it separately just after this. + Map toReturn = new HashMap<>(slotStereotype.merge(capabilities).asMap()); + + // Merge browser specific stereotype and capabilities options + switch (browserName.toLowerCase()) { + case "chrome": + case "microsoftedge": + case "msedge": + toReturn.put(options, mergeChromiumOptions(stereotypeOptions, capsOptions)); + break; + case "firefox": + toReturn.put(options, mergeFirefoxOptions(stereotypeOptions, capsOptions)); + break; + default: + break; + } + + return new ImmutableCapabilities(toReturn); } - @SuppressWarnings("unchecked") - Map stereotypeOptions = (Map) slotStereotype.asMap().get(options); + return slotStereotype.merge(capabilities); + } - Map toReturn = new HashMap<>(capabilities.asMap()); + private Map mergeChromiumOptions(Map stereotypeOptions, + Map capsOptions) { + Map toReturn = new HashMap<>(stereotypeOptions); - if (!toReturn.containsKey(options)) { - toReturn.put(options, stereotypeOptions); - return new ImmutableCapabilities(toReturn); + for (String name : capsOptions.keySet()) { + if (name.equals("args")) { + List arguments = + (List) (capsOptions.getOrDefault(("args"), new ArrayList<>())); + + List stereotypeArguments = + (List) (stereotypeOptions.getOrDefault(("args"), new ArrayList<>())); + + arguments.forEach(arg -> { + if (!stereotypeArguments.contains(arg)) { + stereotypeArguments.add(arg); + } + }); + toReturn.put("args", stereotypeArguments); + } + + if (name.equals("extensions")) { + List extensionList = (List) (capsOptions.get(("extensions"))); + + List stereotypeExtensions = + (List) (stereotypeOptions.getOrDefault(("extensions"), new ArrayList<>())); + + extensionList.forEach(extension -> { + if (!stereotypeExtensions.contains(extension)) { + stereotypeExtensions.add(extension); + } + }); + + toReturn.put("extensions", stereotypeExtensions); + } + + if (!name.equals("binary") && !name.equals("extensions") && !name.equals("args")) { + toReturn.put(name, capsOptions.get(name)); + } } - @SuppressWarnings("unchecked") - Map capsOptions = (Map) toReturn.get(options); - stereotypeOptions.forEach((key, value) -> { - if (!capsOptions.containsKey(key)) { - capsOptions.put(key, value); + return toReturn; + } + + private Map mergeFirefoxOptions(Map stereotypeOptions, + Map capsOptions) { + Map toReturn = new HashMap<>(stereotypeOptions); + + for (String name : capsOptions.keySet()) { + if (name.equals("args")) { + List + arguments = + (List) (capsOptions.getOrDefault(("args"), new ArrayList<>())); + List stereotypeArguments = + (List) (stereotypeOptions.getOrDefault(("args"), new ArrayList<>())); + arguments.forEach(arg -> { + if (!stereotypeArguments.contains(arg)) { + stereotypeArguments.add(arg); + } + }); + toReturn.put("args", stereotypeArguments); + } + + if (name.equals("prefs")) { + Map prefs = + (Map) (capsOptions.getOrDefault(("prefs"), new HashMap<>())); + + Map stereotypePrefs = + (Map) (stereotypeOptions.getOrDefault(("prefs"), new HashMap<>())); + + stereotypePrefs.putAll(prefs); + toReturn.put("prefs", stereotypePrefs); + } + + if (name.equals("profile")) { + String rawProfile = + (String) capsOptions.get("profile"); + + toReturn.put("profile", rawProfile); + } + + if (name.equals("log")) { + Map logLevelMap = + (Map) capsOptions.get("log"); + toReturn.put("log", logLevelMap); } - }); + } - return new ImmutableCapabilities(toReturn); + return toReturn; } } diff --git a/java/test/org/openqa/selenium/grid/node/config/SessionCapabilitiesMutatorTest.java b/java/test/org/openqa/selenium/grid/node/config/SessionCapabilitiesMutatorTest.java new file mode 100644 index 0000000000000..9fb61248d1757 --- /dev/null +++ b/java/test/org/openqa/selenium/grid/node/config/SessionCapabilitiesMutatorTest.java @@ -0,0 +1,292 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.openqa.selenium.grid.node.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.LIST; +import static org.assertj.core.api.InstanceOfAssertFactories.MAP; +import static org.assertj.core.api.InstanceOfAssertFactories.STRING; + +import org.junit.jupiter.api.Test; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.ImmutableCapabilities; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class SessionCapabilitiesMutatorTest { + + private SessionCapabilitiesMutator sessionCapabilitiesMutator; + private Capabilities stereotype; + private Capabilities capabilities; + + @Test + void shouldMergeStereotypeWithoutOptionsWithCapsWithOptions() { + stereotype = new ImmutableCapabilities( + "browserName", "chrome", + "unhandledPromptBehavior", "accept"); + + sessionCapabilitiesMutator = new SessionCapabilitiesMutator(stereotype); + + Map chromeOptions = new HashMap<>(); + chromeOptions.put("args", Arrays.asList("incognito", "window-size=500,500")); + + capabilities = new ImmutableCapabilities( + "browserName", "chrome", + "goog:chromeOptions", chromeOptions, + "pageLoadStrategy", "normal"); + + Map modifiedCapabilities = sessionCapabilitiesMutator.apply(capabilities).asMap(); + + assertThat(modifiedCapabilities.get("browserName")).isEqualTo("chrome"); + assertThat(modifiedCapabilities.get("unhandledPromptBehavior")).isEqualTo("accept"); + assertThat(modifiedCapabilities.get("pageLoadStrategy")).isEqualTo("normal"); + assertThat(modifiedCapabilities) + .extractingByKey("goog:chromeOptions").asInstanceOf(MAP) + .extractingByKey("args").asInstanceOf(LIST) + .contains("incognito", "window-size=500,500"); + } + + @Test + void shouldMergeStereotypeWithOptionsWithCapsWithoutOptions() { + Map chromeOptions = new HashMap<>(); + chromeOptions.put("args", Arrays.asList("incognito", "window-size=500,500")); + + stereotype = new ImmutableCapabilities( + "browserName", "chrome", + "goog:chromeOptions", chromeOptions, + "unhandledPromptBehavior", "accept"); + + sessionCapabilitiesMutator = new SessionCapabilitiesMutator(stereotype); + + capabilities = new ImmutableCapabilities( + "browserName", "chrome", + "pageLoadStrategy", "normal"); + + Map modifiedCapabilities = sessionCapabilitiesMutator.apply(capabilities).asMap(); + + assertThat(modifiedCapabilities.get("browserName")).isEqualTo("chrome"); + assertThat(modifiedCapabilities.get("unhandledPromptBehavior")).isEqualTo("accept"); + assertThat(modifiedCapabilities.get("pageLoadStrategy")).isEqualTo("normal"); + assertThat(modifiedCapabilities) + .extractingByKey("goog:chromeOptions").asInstanceOf(MAP) + .extractingByKey("args").asInstanceOf(LIST) + .contains("incognito", "window-size=500,500"); + } + + @Test + void shouldMergeChromeSpecificOptionsFromStereotypeAndCaps() { + String ext1 = Base64.getEncoder().encodeToString("ext1".getBytes()); + String ext2 = Base64.getEncoder().encodeToString("ext2".getBytes()); + + Map stereotypeOptions = new HashMap<>(); + stereotypeOptions.put("args", new ArrayList<>(Arrays.asList("incognito", "window-size=500,500"))); + stereotypeOptions.put("extensions", new ArrayList<>(Collections.singletonList(ext1))); + stereotypeOptions.put("binary", "/path/to/binary"); + stereotypeOptions.put("opt1", "val1"); + stereotypeOptions.put("opt2", "val4"); + + stereotype = new ImmutableCapabilities( + "browserName", "chrome", + "goog:chromeOptions", stereotypeOptions); + + sessionCapabilitiesMutator = new SessionCapabilitiesMutator(stereotype); + + Map capabilityOptions = new HashMap<>(); + capabilityOptions.put("args", Arrays.asList("incognito", "--headless")); + capabilityOptions.put("extensions", new ArrayList<>(Collections.singletonList(ext2))); + capabilityOptions.put("binary", "/path/to/caps/binary"); + capabilityOptions.put("opt2", "val2"); + capabilityOptions.put("opt3", "val3"); + + capabilities = new ImmutableCapabilities( + "browserName", "chrome", + "goog:chromeOptions", capabilityOptions); + + Map modifiedCapabilities = sessionCapabilitiesMutator.apply(capabilities).asMap(); + + assertThat(modifiedCapabilities) + .extractingByKey("goog:chromeOptions").asInstanceOf(MAP) + .extractingByKey("args").asInstanceOf(LIST) + .containsExactly("incognito", "window-size=500,500", "--headless"); + + assertThat(modifiedCapabilities) + .extractingByKey("goog:chromeOptions").asInstanceOf(MAP) + .containsEntry("opt1", "val1") + .containsEntry("opt2", "val2") + .containsEntry("opt3", "val3"); + + assertThat(modifiedCapabilities) + .extractingByKey("goog:chromeOptions").asInstanceOf(MAP) + .extractingByKey("extensions").asInstanceOf(LIST) + .containsExactly(ext1, ext2); + + assertThat(modifiedCapabilities) + .extractingByKey("goog:chromeOptions").asInstanceOf(MAP) + .extractingByKey("binary").asInstanceOf(STRING) + .isEqualTo("/path/to/binary"); + } + + @Test + void shouldMergeEdgeSpecificOptionsFromStereotypeAndCaps() { + String ext1 = Base64.getEncoder().encodeToString("ext1".getBytes()); + String ext2 = Base64.getEncoder().encodeToString("ext2".getBytes()); + + Map stereotypeOptions = new HashMap<>(); + stereotypeOptions.put("args", new ArrayList<>(Arrays.asList("incognito", "window-size=500,500"))); + stereotypeOptions.put("extensions", new ArrayList<>(Collections.singletonList(ext1))); + stereotypeOptions.put("opt1", "val1"); + stereotypeOptions.put("opt2", "val4"); + + stereotype = new ImmutableCapabilities( + "browserName", "microsoftedge", + "ms:edgeOptions", stereotypeOptions); + + sessionCapabilitiesMutator = new SessionCapabilitiesMutator(stereotype); + + Map capabilityOptions = new HashMap<>(); + capabilityOptions.put("args", Arrays.asList("incognito", "--headless")); + capabilityOptions.put("extensions", new ArrayList<>(Collections.singletonList(ext2))); + capabilityOptions.put("binary", "/path/to/binary"); + capabilityOptions.put("opt2", "val2"); + capabilityOptions.put("opt3", "val3"); + + capabilities = new ImmutableCapabilities( + "browserName", "microsoftedge", + "ms:edgeOptions", capabilityOptions); + + Map modifiedCapabilities = sessionCapabilitiesMutator.apply(capabilities).asMap(); + + assertThat(modifiedCapabilities) + .extractingByKey("ms:edgeOptions").asInstanceOf(MAP) + .extractingByKey("args").asInstanceOf(LIST) + .containsExactly("incognito", "window-size=500,500", "--headless"); + + assertThat(modifiedCapabilities) + .extractingByKey("ms:edgeOptions").asInstanceOf(MAP) + .containsEntry("opt1", "val1") + .containsEntry("opt2", "val2") + .containsEntry("opt3", "val3"); + + assertThat(modifiedCapabilities) + .extractingByKey("ms:edgeOptions").asInstanceOf(MAP) + .extractingByKey("extensions").asInstanceOf(LIST) + .containsExactly(ext1, ext2); + + assertThat(modifiedCapabilities) + .extractingByKey("ms:edgeOptions").asInstanceOf(MAP) + .extractingByKey("binary").isNull(); + } + + @Test + void shouldMergeFirefoxSpecificOptionsFromStereotypeAndCaps() { + Map stereotypeOptions = new HashMap<>(); + stereotypeOptions.put("args", new ArrayList<>(Arrays.asList("verbose", "silent"))); + + Map prefs = new HashMap<>(); + prefs.put("opt1", "val1"); + prefs.put("opt2", "val4"); + stereotypeOptions.put("prefs", prefs); + stereotypeOptions.put("binary", "/path/to/binary"); + + Map debugLog = new HashMap<>(); + debugLog.put("level", "debug"); + stereotypeOptions.put("log", debugLog); + + stereotypeOptions.put("profile", "profile-string"); + + stereotype = new ImmutableCapabilities( + "browserName", "firefox", + "moz:firefoxOptions", stereotypeOptions); + + sessionCapabilitiesMutator = new SessionCapabilitiesMutator(stereotype); + + Map capabilityOptions = new HashMap<>(); + capabilityOptions.put("args", Collections.singletonList("-headless")); + + Map capabilityPrefs = new HashMap<>(); + capabilityPrefs.put("opt1", "val1"); + capabilityPrefs.put("opt2", "val2"); + capabilityPrefs.put("opt3", "val3"); + capabilityOptions.put("prefs", capabilityPrefs); + + Map infoLog = new HashMap<>(); + infoLog.put("level", "info"); + capabilityOptions.put("log", infoLog); + capabilityOptions.put("profile", "different-profile-string"); + + capabilityOptions.put("binary", "/path/to/caps/binary"); + + capabilities = new ImmutableCapabilities( + "browserName", "firefox", + "moz:firefoxOptions", capabilityOptions); + + Map modifiedCapabilities = sessionCapabilitiesMutator.apply(capabilities).asMap(); + + assertThat(modifiedCapabilities) + .extractingByKey("moz:firefoxOptions").asInstanceOf(MAP) + .extractingByKey("args").asInstanceOf(LIST) + .containsExactly("verbose", "silent", "-headless"); + + assertThat(modifiedCapabilities) + .extractingByKey("moz:firefoxOptions").asInstanceOf(MAP) + .extractingByKey("prefs").asInstanceOf(MAP) + .containsEntry("opt1", "val1") + .containsEntry("opt2", "val2") + .containsEntry("opt3", "val3"); + + assertThat(modifiedCapabilities) + .extractingByKey("moz:firefoxOptions").asInstanceOf(MAP) + .extractingByKey("log").asInstanceOf(MAP) + .containsEntry("level", "info"); + + assertThat(modifiedCapabilities) + .extractingByKey("moz:firefoxOptions").asInstanceOf(MAP) + .extractingByKey("binary").asInstanceOf(STRING) + .isEqualTo("/path/to/binary"); + + assertThat(modifiedCapabilities) + .extractingByKey("moz:firefoxOptions").asInstanceOf(MAP) + .extractingByKey("profile").asInstanceOf(STRING) + .isEqualTo("different-profile-string"); + } + + @Test + void shouldMergeTopLevelStereotypeAndCaps() { + stereotype = new ImmutableCapabilities( + "browserName", "chrome", + "unhandledPromptBehavior", "accept", + "pageLoadStrategy", "eager"); + + sessionCapabilitiesMutator = new SessionCapabilitiesMutator(stereotype); + + capabilities = new ImmutableCapabilities( + "browserName", "chrome", + "pageLoadStrategy", "normal"); + + Map modifiedCapabilities = sessionCapabilitiesMutator.apply(capabilities).asMap(); + + assertThat(modifiedCapabilities.get("browserName")).isEqualTo("chrome"); + assertThat(modifiedCapabilities.get("unhandledPromptBehavior")).isEqualTo("accept"); + assertThat(modifiedCapabilities.get("pageLoadStrategy")).isEqualTo("normal"); + } +}