code: // 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.chrome;
  
  import static java.util.stream.Collectors.toSet;
  import static org.assertj.core.api.Assertions.assertThat;
  import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
  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 static org.openqa.selenium.chrome.ChromeDriverLogLevel.OFF;
  import static org.openqa.selenium.chrome.ChromeDriverLogLevel.SEVERE;
  import static org.openqa.selenium.remote.CapabilityType.ACCEPT_INSECURE_CERTS;
  import static org.openqa.selenium.remote.CapabilityType.TIMEOUTS;
  
  import java.io.File;
  import java.time.Duration;
  import java.util.Arrays;
  import java.util.Base64;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.function.Predicate;
  import org.junit.jupiter.api.Tag;
  import org.junit.jupiter.api.Test;
  import org.openqa.selenium.AcceptedW3CCapabilityKeys;
  import org.openqa.selenium.MutableCapabilities;
  import org.openqa.selenium.PageLoadStrategy;
  import org.openqa.selenium.UnexpectedAlertBehaviour;
  import org.openqa.selenium.testing.TestUtilities;
  
  @Tag("UnitTests")
  class ChromeOptionsTest {
  
    @Test
    void optionsAsMapShouldBeImmutable() {
      Map<String, Object> options = new ChromeOptions().asMap();
      assertThatExceptionOfType(UnsupportedOperationException.class)
          .isThrownBy(() -> options.put("browserType", "firefox"));
  
      Map<String, Object> googOptions = (Map<String, Object>) options.get(ChromeOptions.CAPABILITY);
      assertThatExceptionOfType(UnsupportedOperationException.class)
          .isThrownBy(() -> googOptions.put("binary", ""));
  
      List<String> extensions = (List<String>) googOptions.get("extensions");
      assertThatExceptionOfType(UnsupportedOperationException.class)
          .isThrownBy(() -> extensions.add("x"));
  
      List<String> args = (List<String>) googOptions.get("args");
      assertThatExceptionOfType(UnsupportedOperationException.class)
          .isThrownBy(() -> args.add("-help"));
    }
  
    @Test
    void canBuildLogLevelFromStringRepresentation() {
      assertThat(ChromeDriverLogLevel.fromString("off")).isEqualTo(OFF);
      assertThat(ChromeDriverLogLevel.fromString("SEVERE")).isEqualTo(SEVERE);
    }
  
    @Test
    void canAddW3CCompliantOptions() {
      ChromeOptions chromeOptions = new ChromeOptions();
      chromeOptions
          .setBrowserVersion("99")
          .setPlatformName("9 3/4")
          .setUnhandledPromptBehaviour(UnexpectedAlertBehaviour.IGNORE)
          .setAcceptInsecureCerts(true)
          .setPageLoadStrategy(PageLoadStrategy.EAGER)
          .setStrictFileInteractability(true)
          .setImplicitWaitTimeout(Duration.ofSeconds(1))
          .setPageLoadTimeout(Duration.ofSeconds(2))
          .setScriptTimeout(Duration.ofSeconds(3));
  
      Map<String, Object> mappedOptions = chromeOptions.asMap();
      assertThat(mappedOptions.get("browserName")).isEqualTo("chrome");
      assertThat(mappedOptions.get("browserVersion")).isEqualTo("99");
      assertThat(mappedOptions.get("platformName")).isEqualTo("9 3/4");
      assertThat(mappedOptions.get("unhandledPromptBehavior")).hasToString("ignore");
      assertThat(mappedOptions.get("acceptInsecureCerts")).isEqualTo(true);
      assertThat(mappedOptions.get("pageLoadStrategy")).hasToString("eager");
      assertThat(mappedOptions.get("strictFileInteractability")).isEqualTo(true);
  
      Map<String, Long> expectedTimeouts = new HashMap<>();
      expectedTimeouts.put("implicit", 1000L);
      expectedTimeouts.put("pageLoad", 2000L);
      expectedTimeouts.put("script", 3000L);
  
      assertThat(expectedTimeouts).isEqualTo(mappedOptions.get("timeouts"));
    }
  
    @Test
    void canAddSequentialTimeouts() {
      ChromeOptions chromeOptions = new ChromeOptions();
      chromeOptions.setImplicitWaitTimeout(Duration.ofSeconds(1));
  
      Map<String, Object> mappedOptions = chromeOptions.asMap();
      Map<String, Long> expectedTimeouts = new HashMap<>();
  
      expectedTimeouts.put("implicit", 1000L);
      assertThat(expectedTimeouts).isEqualTo(mappedOptions.get("timeouts"));
  
      chromeOptions.setPageLoadTimeout(Duration.ofSeconds(2));
      expectedTimeouts.put("pageLoad", 2000L);
      Map<String, Object> mappedOptions2 = chromeOptions.asMap();
      assertThat(expectedTimeouts).isEqualTo(mappedOptions2.get("timeouts"));
    }
  
    @Test
    void mixAddingTimeoutsCapsAndSetter() {
      ChromeOptions chromeOptions = new ChromeOptions();
      chromeOptions.setCapability(TIMEOUTS, Map.of("implicit", 1000));
      chromeOptions.setPageLoadTimeout(Duration.ofSeconds(2));
  
      Map<String, Number> expectedTimeouts = new HashMap<>();
      expectedTimeouts.put("implicit", 1000);
      expectedTimeouts.put("pageLoad", 2000L);
  
      assertThat(chromeOptions.asMap().get("timeouts")).isEqualTo(expectedTimeouts);
    }
  
    @Test
    void mergingOptionsMergesArguments() {
      ChromeOptions one = new ChromeOptions().addArguments("verbose");
      ChromeOptions two = new ChromeOptions().addArguments("silent");
      ChromeOptions merged = one.merge(two);
  
      assertThat(merged.asMap())
          .asInstanceOf(MAP)
          .extractingByKey(ChromeOptions.CAPABILITY)
          .asInstanceOf(MAP)
          .extractingByKey("args")
          .asInstanceOf(LIST)
          .containsExactly("--remote-allow-origins=*", "verbose", "silent");
    }
  
    @Test
    void mergingOptionsMergesEncodedExtensions() {
      String ext1 = Base64.getEncoder().encodeToString("ext1".getBytes());
      String ext2 = Base64.getEncoder().encodeToString("ext2".getBytes());
  
      ChromeOptions one = new ChromeOptions().addEncodedExtensions(ext1);
      ChromeOptions two = new ChromeOptions().addEncodedExtensions(ext2);
      ChromeOptions merged = one.merge(two);
  
      assertThat(merged.asMap())
          .asInstanceOf(MAP)
          .extractingByKey(ChromeOptions.CAPABILITY)
          .asInstanceOf(MAP)
          .extractingByKey("extensions")
          .asInstanceOf(LIST)
          .containsExactly(ext1, ext2);
    }
  
    @Test
    void mergingOptionsMergesExtensions() {
      File ext1 = TestUtilities.createTmpFile("ext1");
      String ext1Encoded = Base64.getEncoder().encodeToString("ext1".getBytes());
      File ext2 = TestUtilities.createTmpFile("ext2");
      String ext2Encoded = Base64.getEncoder().encodeToString("ext2".getBytes());
  
      ChromeOptions one = new ChromeOptions().addExtensions(ext1);
      ChromeOptions two = new ChromeOptions().addExtensions(ext2);
      ChromeOptions merged = one.merge(two);
  
      assertThat(merged.asMap())
          .asInstanceOf(MAP)
          .extractingByKey(ChromeOptions.CAPABILITY)
          .asInstanceOf(MAP)
          .extractingByKey("extensions")
          .asInstanceOf(LIST)
          .containsExactly(ext1Encoded, ext2Encoded);
    }
  
    @Test
    void mergingOptionsMergesEncodedExtensionsAndFileExtensions() {
      File ext1 = TestUtilities.createTmpFile("ext1");
      String ext1Encoded = Base64.getEncoder().encodeToString("ext1".getBytes());
      String ext2 = Base64.getEncoder().encodeToString("ext2".getBytes());
  
      ChromeOptions one = new ChromeOptions().addExtensions(ext1);
      ChromeOptions two = new ChromeOptions().addEncodedExtensions(ext2);
      ChromeOptions merged = one.merge(two);
  
      assertThat(merged.asMap())
          .asInstanceOf(MAP)
          .extractingByKey(ChromeOptions.CAPABILITY)
          .asInstanceOf(MAP)
          .extractingByKey("extensions")
          .asInstanceOf(LIST)
          .containsExactly(ext1Encoded, ext2);
    }
  
    @Test
    void mergingOptionsMergesExperimentalOptions() {
      ChromeOptions one =
          new ChromeOptions()
              .setExperimentalOption("opt1", "val1")
              .setExperimentalOption("opt2", "val2");
      ChromeOptions two =
          new ChromeOptions()
              .setExperimentalOption("opt2", "val4")
              .setExperimentalOption("opt3", "val3");
      ChromeOptions merged = one.merge(two);
  
      assertThat(merged.asMap())
          .asInstanceOf(MAP)
          .extractingByKey(ChromeOptions.CAPABILITY)
          .asInstanceOf(MAP)
          .containsEntry("opt1", "val1")
          .containsEntry("opt2", "val4")
          .containsEntry("opt3", "val3");
    }
  
    @Test
    void mergingOptionsWithMutableCapabilities() {
      File ext1 = TestUtilities.createTmpFile("ext1");
      String ext1Encoded = Base64.getEncoder().encodeToString("ext1".getBytes());
      String ext2 = Base64.getEncoder().encodeToString("ext2".getBytes());
  
      MutableCapabilities one = new MutableCapabilities();
  
      ChromeOptions options = new ChromeOptions();
      options.addArguments("verbose");
      options.addArguments("silent");
      options.setExperimentalOption("opt1", "val1");
      options.setExperimentalOption("opt2", "val4");
      options.addExtensions(ext1);
      options.addEncodedExtensions(ext2);
      options.setAcceptInsecureCerts(true);
      File binary = TestUtilities.createTmpFile("binary");
      options.setBinary(binary);
  
      one.setCapability(ChromeOptions.CAPABILITY, options);
  
      ChromeOptions two = new ChromeOptions();
      two.addArguments("verbose");
      two.setExperimentalOption("opt2", "val2");
      two.setExperimentalOption("opt3", "val3");
      two = two.merge(one);
  
      Map<String, Object> map = two.asMap();
  
      assertThat(map)
          .asInstanceOf(MAP)
          .extractingByKey(ChromeOptions.CAPABILITY)
          .asInstanceOf(MAP)
          .extractingByKey("args")
          .asInstanceOf(LIST)
          .containsExactly("--remote-allow-origins=*", "verbose", "silent");
  
      assertThat(map)
          .asInstanceOf(MAP)
          .extractingByKey(ChromeOptions.CAPABILITY)
          .asInstanceOf(MAP)
          .containsEntry("opt1", "val1")
          .containsEntry("opt2", "val4")
          .containsEntry("opt3", "val3");
  
      assertThat(map)
          .asInstanceOf(MAP)
          .extractingByKey(ACCEPT_INSECURE_CERTS)
          .isExactlyInstanceOf(Boolean.class);
  
      assertThat(map)
          .asInstanceOf(MAP)
          .extractingByKey(ChromeOptions.CAPABILITY)
          .asInstanceOf(MAP)
          .extractingByKey("extensions")
          .asInstanceOf(LIST)
          .containsExactly(ext1Encoded, ext2);
  
      assertThat(map)
          .asInstanceOf(MAP)
          .extractingByKey(ChromeOptions.CAPABILITY)
          .asInstanceOf(MAP)
          .extractingByKey("binary")
          .asInstanceOf(STRING)
          .isEqualTo(binary.getPath());
    }
  
    @Test
    void mergingOptionsWithOptionsAsMutableCapabilities() {
      File ext1 = TestUtilities.createTmpFile("ext1");
      String ext1Encoded = Base64.getEncoder().encodeToString("ext1".getBytes());
      String ext2 = Base64.getEncoder().encodeToString("ext2".getBytes());
  
      MutableCapabilities browserCaps = new MutableCapabilities();
  
      File binary = TestUtilities.createTmpFile("binary");
  
      browserCaps.setCapability("binary", binary.getPath());
      browserCaps.setCapability("opt1", "val1");
      browserCaps.setCapability("opt2", "val4");
      browserCaps.setCapability("args", Arrays.asList("silent", "verbose"));
      browserCaps.setCapability("extensions", Arrays.asList(ext1, ext2));
  
      MutableCapabilities one = new MutableCapabilities();
      one.setCapability(ChromeOptions.CAPABILITY, browserCaps);
  
      ChromeOptions two = new ChromeOptions();
      two.addArguments("verbose");
      two.setExperimentalOption("opt2", "val2");
      two.setExperimentalOption("opt3", "val3");
      two = two.merge(one);
  
      Map<String, Object> map = two.asMap();
  
      assertThat(map)
          .asInstanceOf(MAP)
          .extractingByKey(ChromeOptions.CAPABILITY)
          .asInstanceOf(MAP)
          .extractingByKey("args")
          .asInstanceOf(LIST)
          .containsExactly("--remote-allow-origins=*", "verbose", "silent");
  
      assertThat(map).asInstanceOf(MAP).containsEntry("opt1", "val1");
  
      assertThat(map).asInstanceOf(MAP).containsEntry("opt2", "val4");
  
      assertThat(map)
          .asInstanceOf(MAP)
          .extractingByKey(ChromeOptions.CAPABILITY)
          .asInstanceOf(MAP)
          .containsEntry("opt2", "val2")
          .containsEntry("opt3", "val3");
  
      assertThat(map)
          .asInstanceOf(MAP)
          .extractingByKey(ChromeOptions.CAPABILITY)
          .asInstanceOf(MAP)
          .extractingByKey("extensions")
          .asInstanceOf(LIST)
          .containsExactly(ext1Encoded, ext2);
  
      assertThat(map)
          .asInstanceOf(MAP)
          .extractingByKey(ChromeOptions.CAPABILITY)
          .asInstanceOf(MAP)
          .extractingByKey("binary")
          .asInstanceOf(STRING)
          .isEqualTo(binary.getPath());
    }
  
    @Test
    void isW3CSafe() {
      Map<String, Object> converted =
          new ChromeOptions()
              .setBinary("some/path")
              .addArguments("--headless")
              .setLogLevel(ChromeDriverLogLevel.INFO)
              .asMap();
  
      Predicate<String> badKeys = new AcceptedW3CCapabilityKeys().negate();
      Set<String> seen = converted.keySet().stream().filter(badKeys).collect(toSet());
  
      assertThat(seen).isEmpty();
    }
  
    @Test
    void shouldBeAbleToSetAnAndroidOption() {
      Map<String, Object> converted =
          new ChromeOptions().setAndroidActivity("com.cheese.nom").asMap();
  
      assertThat(converted)
          .extractingByKey(ChromeOptions.CAPABILITY)
          .asInstanceOf(MAP)
          .extractingByKey("androidActivity")
          .isEqualTo("com.cheese.nom");
    }
  }
  