Selenium Manager in Selenium 4.11.0: New Features and Improvementsx

Faisalkhatri123 - Sep 6 '23 - - Dev Community

Selenium WebDriver, as everyone knows, is used for automating browsers. It is used widely by many organizations for web automation testing. Using Selenium WebDriver, you can automate all the modern browsers like Google Chrome, Mozilla Firefox, Microsoft Edge, and Safari. With the release of the Selenium 4 version, WebDriver APIs run on the W3C standard protocol making Selenium 4 W3C Compliant.

Previously, in older versions of Selenium, it was required to provide the driver executable path to run the automated tests on the respective browser. Third-party libraries like WebDriverManager were used to automate the manual process of updating browser drivers.

With Selenium’s version 4.6.0, Selenium Manager was introduced, providing a huge relief to the automation test engineers as it handles the browser drivers under the hood, so you don’t have to worry about the browser drivers.

In this blog, let’s learn about Selenium WebDriver’s latest release, i.e., version 4.11.0, which ships the following features related to Selenium Manager:

Support for Chrome for Testing endpoints for Automated Chrome Management and ChromeDriver Management.

In this tutorial, learn what is Regression testing, its importance, types, and how to perform it.

What is Selenium Manager?

Selenium Manager is a binary tool generated using Rust that provides automated driver management for Google Chrome, Mozilla Firefox, and Microsoft Edge. Selenium bindings use Selenium Manager by default, so there is no need to download it or add its dependency in the project to use it. It is still in Beta; however, it is becoming a relevant component of Selenium.

Selenium Manager’s Automated Driver Management

As discussed in the earlier section, Selenium Manager is used for automated driver management. When the following statement of code gets executed to run the browser using Selenium WebDriver, Selenium Manager is invoked and checks for the browser version of the respective browser installed on your machine. After downloading the browser driver, it caches it in the local (~/.cache/selenium) folder. This ensures the smooth execution of future automated tests for the same browser/driver version.

This caching of the drivers is done in the same folder location across macOS, Windows, and Linux.

WebDriver driver = new ChromeDriver();
Enter fullscreen mode Exit fullscreen mode

Below is the pictorial representation of Selenium Manager’s automated driver management.

image

When the Selenium automation tests are executed, and no browser driver executable path is provided, Selenium Manager performs the following steps to manage the drivers in an automated way:

Step 1 — Browser version discovery

Selenium Manager checks the browser version installed on the machine where the web automation tests are executed. For example, for Chrome browser version discovery, Selenium Manager uses the command google-chrome –version.

Step 2 — Driver version discovery

On getting the browser version from Step 1, the respective driver version is identified using the online metadata maintained by the browser vendors, e.g., ChromeDriver, geckodriver, or msedgedriver.

Step 3 — Driver Download

The driver artifact is downloaded using the driver URL obtained from Step 2. This artifact is uncompressed and stored locally.

Step 4 — Driver Cache

All the driver binaries are saved to the local cache (~/.cache/selenium). This helps in re-using the same browser driver and executing the tests again if, in the future, the tests are run on the same browser/driver version.

image

Driver Cache — Windows

image

Driver Cache — macOS

Which are the most wanted tools for automation testing that have climbed the top of the ladder so far? Let’s take a look.

Running Tests on Chrome, Firefox, and Edge browsers using Selenium Manager

Let’s now get into the implementation and check out the real-time working of Selenium Manager. But, before we move toward the demo following are some of the details related to the technical stack that we will be using:

  • Programming Language — Java 17

  • Web Automation Tool — Selenium WebDriver (4.11.0)

  • Build Tool — Maven

  • Test Runner — TestNG

Project Setup

A Maven project has been created, and the Selenium WebDriver and TestNG dependencies are updated in the pom.xml file. Similarly, maven-compiler and maven-surefire plugins are also updated in it.

Dependencies updated in pom.xml

image

Filename: pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lambdatest</groupId>
<artifactId>selenium-manager-demo</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<selenium-java.version>4.11.0</selenium-java.version>
<webdrivermanager.version>5.4.1</webdrivermanager.version>
<testng.version>7.8.0</testng.version>
<maven.compiler.version>3.11.0</maven.compiler.version>
<surefire-version>3.1.2</surefire-version>
<java.release.version>17</java.release.version>
<maven.source.encoding>UTF-8</maven.source.encoding>
<suite-xml>test-suite/testng.xml</suite-xml>
<argLine>-Dfile.encoding=UTF-8 -Xdebug -Xnoagent</argLine>
</properties>

<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium-java.version}</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.github.bonigarcia/webdrivermanager -->
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>${webdrivermanager.version}</version>
</dependency>

</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
<configuration>
<release>${java.release.version}</release>
<encoding>${maven.source.encoding}</encoding>
<forceJavacCompilerUse>true</forceJavacCompilerUse>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-version}</version>
<executions>
<execution>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
<properties>
<property>
<name>usedefaultlisteners</name>
<value>false</value>
</property>
</properties>
<suiteXmlFiles>
<suiteXmlFile>${suite-xml}</suiteXmlFile>
</suiteXmlFiles>
<argLine>${argLine}</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>
Enter fullscreen mode Exit fullscreen mode

image

Demonstration — Working with Selenium Manager on Chrome, Firefox, and Edge browsers

In this section, we will be going through a demo of three tests that will be running on Chrome, Firefox, and Edge browsers, where we will be instantiating the WebDriver and the browser driver management will be automatically done by Selenium Manager.

Test Scenario 1

  1. Open Chrome browser.

  2. Navigate to LambdaTest’s eCommerce Playground website.

  3. Assert that the page title is “Your Store”.

Test Scenario 2

  1. Open Firefox browser.

  2. Navigate to LambdaTest’s eCommerce Playground website.

  3. Assert that the page title is “Your Store”.

Test Scenario 3

  1. Open Edge browser.

  2. Navigate to LambdaTest’s eCommerce Playground website.

  3. Assert that the page title is “Your Store”.

image

LambdaTest’s eCommerce Playground Website

Implementation

The following automation tests are written in the SeleniumWebDriverManagerTest class.

public class SeleniumWebDriverManagerTests {
private WebDriver driver;
@Test
public void testChromeLatestVersionWithSeleniumManager() {
this.driver = new ChromeDriver();
this.driver.get("https://ecommerce-playground.lambdatest.io/");

assertEquals(this.driver.getTitle(), "Your Store");
}
@Test
public void testOnFirefoxLatestVersionWithSeleniumManager() {
this.driver = new FirefoxDriver();
this.driver.get("https://ecommerce-playground.lambdatest.io/");

assertEquals(this.driver.getTitle(), "Your Store");
}

@Test
public void testOnEdgeLatestVersionWithSeleniumManager() {
this.driver = new EdgeDriver();
this.driver.get("https://ecommerce-playground.lambdatest.io/");

assertEquals(this.driver.getTitle(), "Your Store");
}

@AfterMethod(alwaysRun = true)
public void tearDown() {
this.driver.quit();
}
}
Enter fullscreen mode Exit fullscreen mode

testChromeLatestVersionWithSeleniumManager() is the code implementation for Scenario 1. It will open LambdaTest’s eCommerce website in the Chrome browser and perform an assertion to match the title.

testOnFirefoxLatestVersionWithSeleniumManager() is the code implementation of Scenario 2. This code snippet launches the website using the Firefox browser and verifies the title through an assertion.

testOnEdgeLatestVersionWithSeleniumManager() implements Scenario 3 and will perform the testing in Edge browser. It will navigate to the website and verify the title.

Notice that in all three tests, we haven’t provided any driver executable path, nor have we used third-party libraries like WebDriverManager. Selenium Manager will automatically handle the browser drivers.

Which are the most wanted automation testing tools that have climbed the top of the ladder so far? Let’s take a look.

Test Execution

Let’s create a testng.xml file and execute all three tests.

image

Here is the screenshot of the test execution showing that all three tests passed successfully.

image

Let’s also check the Driver Cache, as the latest browser driver should be downloaded and stored in it.

image

image

image

Selenium Manager Major Changes — Working with Drivers added to the PATH

With the introduction of Selenium Manager, automated driver management is handled for the Selenium bindings. Selenium Manager is a fallback solution when no drivers are specified during setup.

However, Selenium users can continue to manage the drivers manually by putting the drivers on the PATH or using System Properties, or by using third-party libraries like WebDriverManager. Selenium Manager also helps users to notify the potential problems with the drivers on the PATH.

For example, consider a scenario of running the browser automation tests on Chrome browser where the browser drivers are maintained manually by setting it on the environment variable PATH. So, when writing the tests, the latest version of Chrome browser was 113. Hence the respective browser driver of version 113 was downloaded and set in the environment variable PATH.

Now, as Chrome browser has the auto-update feature, it gets updated with the latest version, i.e., 115, in the background. So, if we run the automation tests, it will throw an error stating that “This version of ChromeDriver only supports Chrome version 113” because, with the latest Chrome browser, version 115, the browser driver with version 112 is incompatible.

With Selenium’s 4.11.0 release, when an incompatible browser driver is found on the PATH, the following warning message is displayed in the console logs when Selenium tests are executed; this helps in better visibility of the error.

image

This shows that when browser drivers are already set in the PATH variable, Selenium Manager will not auto-download the respective browser driver but will show a warning message to update the browser driver manually.

Another approach to consider is leveraging Selenium Manager’s driver management capabilities. By eliminating the need for a driver PATH, Selenium Manager can autonomously identify and download the necessary browser, facilitating the execution of web automation tests.

Demonstration — Checking out the Warning message in Selenium 4.11.0 release for the incompatible browser driver set in the PATH

Test Scenario

  • Download the ChromeDriver executable for Chrome version 113.

  • Set the Chrome browser driver executable in the Environment Variable.

  • Open the Chrome browser having the latest version 115 using Selenium WebDriver.

In this scenario, we will manually set the driver for ChromeDriver version 113 in the environment variable PATH and try to perform Selenium testing on the Chrome browser’s latest version, which is 115.

So ideally, we should get the following warning message in the console output along with SessionNoCreatedException.

image

Implementation

Chrome browser version 115 is currently installed on the local machine.

image

To test this scenario, we need to download the ChromeDriver executable for version 113. So there is a mismatch between the Chrome browser and the ChromeDriver versions. Here, we will be downloading the ChromeDriver.exe for Windows platform.

image

image

Once the ChromeDrive.exe is downloaded, let’s set the ChromeDriver executable path in the Environment variable.

  • Navigate to System Properties and open the Environment Variables window by clicking the Environment Variables button.

image

  • Select the Path variable and click on the Edit button.

image

  • Add the ChromeDriver.exe’s path in it.

image

  • Click on the OK button and close the Environment Variable window.

Perform browser automation testing platform on the most powerful cloud infrastructure. Leverage LambdaTest automation testing for faster, reliable and scalable experience on cloud.

The following automation test scripts are written in the SeleniumWebDriverManagerTest class.

@Test
public void testOnChromeBySettingEnvVariablePath() {
   WebDriver driver = new ChromeDriver();
   driver.get("https://ecommerce-playground.lambdatest.io/");

   driver.quit();
}
Enter fullscreen mode Exit fullscreen mode

testOnChromeBySettingEnvVariablePath() will open the Chrome browser using the ChromeDriver.exe set in the PATH variable, navigate to the LambdaTest eCommerce website and close the browser.

Note: This test will not run successfully and will fail, throwing the SessionNotCreatedException with a warning that the ChromeDriver.exe is incompatible.

Test Execution

The following test block is added in the testng.xml file that will help execute the test.

image

Here is the screenshot of the test execution showing the test failure with the warning and NoSessionCreatedException.

image

**Introduction to Chrome for Testing

**With the Selenium Manager’s 4.11.0 release, a major new feature, “Automated Browser Management,” has been introduced that manages the Chrome browser as well as its respective browser drivers for version 113 and above. This has been made possible due to the Chrome team’s latest release — Chrome for Testing (CfT).

The Google Chrome team recently announced the release of Chrome for Testing(CfT). It is a new flavor of Chrome that specifically targets web application testing and is created initially for web browser automation testing. It is not recommended for daily browsing.

A regular Chrome release is evergreen, meaning the browsers are automatically upgraded to future versions. However, Chrome for Testing is not evergreen. This is the key difference between a normal Chrome release and Chrome for Testing.

From Chrome version 114, the ChromeDriver team has stopped publishing the ChromeDriver releases and metadata on their traditional ChromeDriver download repository.

image

The portable binary releases from ChromeDriver version 115 for Chrome for Testing for Windows, Linux, and macOS for its different versions, including Stable, Beta, Dev, and Canary, can be programmatically checked using the CfT JSON endpoints.

image

**Automated Browser Management with Selenium Manager using Chrome for Testing

**In the earlier section of this blog, we learned how Selenium Manager automates driver management for modern browsers like Chrome, Firefox, and Edge. With the introduction of Chrome for Testing, Selenium Manager also implements automated browser management for Chrome browsers.

Below is the pictorial representation of Selenium Manager’s automated browser management for Chrome for Testing.

image

Selenium Manager now downloads the Chrome browser in case Chrome is not installed on the machine where automation tests are run. When a web automation test is targeted to run on Chrome browser’s latest version or any other specified version (v113 and above), use the following statement in the code:

Webdriver driver = new ChromeDriver();
Enter fullscreen mode Exit fullscreen mode

Selenium Manager will perform the following steps to perform automated browser management using Chrome for Testing.

*Step 1 — Chrome browser version discovery

**Selenium Manager checks the Chrome browser version installed on the machine where the web automation tests are executed. It uses the command *google-chrome –version
under the hood to check for the browser version.

*Step 2 — Chrome Driver version discovery

**On getting the browser version from Step 1, the *ChromeDriver
version is identified programmatically using CfT JSON endpoints.

*Step 3 — Chrome Driver download

**Using the *ChromeDriver
URL obtained from Step 2, the ChromeDriver artifact is downloaded. This artifact is uncompressed and stored locally.

*Step 4 — Chrome browser download CfT

**This step is a newly introduced feature in Selenium WebDriver from version 4.11.0. In this step, if the version of the Chrome browser is not installed on the machine on which the automated tests need to be executed, then the current stable CfT release is discovered, downloaded, and cached. In addition to the stable version, Selenium Manager also allows downloading the older version till v113.

In case the Chrome browser version specified in the test is lower than “113”, it will throw *NoSuchDriverException
with the following error (here, the Chrome browser version was provided as “112” in the tests):

image

In addition to the fixed browser versions, like 113, 114, etc., the following Chrome for Testing browser version labels can also be specified in the automated tests.

*Step 5 — Chrome driver cache

**All the Chrome for Testing releases are saved to the local cache *(~/.cache/selenium/chrome)
. This helps in re-using the same browser driver and executing the tests again if the tests are required to be run on the same browser/driver in the future.

Perform browser on the most powerful testing automation cloud infrastructure. Leverage LambdaTest automation testing for faster, reliable and scalable experience on cloud.

Running Tests on Chrome browser using Chrome for Testing with Selenium Manager

**Let’s now get our hands dirty by checking out the actual working of automated browser management using Chrome for Testing using Selenium Manager.

**Test Scenario 1

  1. Set the Chrome browser version to 113 (not installed on the machine).

  2. Open Chrome browser.

  3. Navigate to the LambdaTest eCommerce playground website.

**Implementation

**In this scenario, we will check that the Chrome browser(v113) that is targeted to run a web automated test is not installed on the machine where tests will be executed, so Selenium Manager should install and cache the Chrome browser(v113) using Chrome for Testing and run the test.

The following automation tests are written in the SeleniumWebDriverManagerTest class.

private WebDriver driver;

@Test
public void testOnChromeOldVersionWithSeleniumManager() {
final ChromeOptions options = new ChromeOptions();
options.setBrowserVersion("113");
this.driver = new ChromeDriver(options);
this.driver.get("https://ecommerce-playground.lambdatest.io/");
assertEquals(this.driver.getTitle(), "Your Store");
}

@AfterMethod(alwaysRun = true)
public void tearDown() {
this.driver.quit();
}
}
Enter fullscreen mode Exit fullscreen mode

The testOnChromeOldVersionWithSeleniumManager() is created for testing this scenario. Using ChromeOptions() class, we will set the Chrome browser version to 113 to run the tests. Once the browser is opened, it will navigate to LambdaTest’s eCommerce website and perform assertions to match the title.

Test Execution

Let’s create a testng.xml file and execute the test.

image

When the test is run, Selenium Manager checks for the Chrome browser version. As v113 was not installed on the machine, it downloads and saves the browser binary and its respective driver(v113) in the ~/.cache/selenium/chrome folder on the machine. Finally, the Chrome browser with v113 was run as specified in the test.

Here is the screenshot of the test execution showing the test passing successfully.

image

With this, it can be noted that using Selenium’s latest version, 4.11.0, we can run automated tests on any Chrome browser version starting from 113 and above, even if it is not installed on the machine. Using Chrome for Testing’s JSON Endpoints, the respective Chrome browser version will be downloaded and saved in the cache for running automated tests.

Test Scenario 2

In this scenario, we will be running the tests by setting the different Chrome versions using the Chrome for Testing version labels, namely, stable, beta, dev, and canary, which is supported in the Selenium WebDriver’s latest release version, 4.11.0

  1. Set the Chrome browser version using the following labels in the setBrowserVersion method of ChromeOptions class.

    a. stable

    b. beta

    c. dev

    d. canary

  2. Open Chrome browser.

  3. Navigate to the LambdaTest eCommerce playground website.

Implementation

We will use the @DataProvider annotation of TestNG by parameterizing the browser version. This will help in handling all 4 test cases of running tests on stable (v115.0.5790.170), beta(v116.0.5845.62), dev(117.0.5927.0), and canary(117.0.5936.0) in a single automation test.

The following two methods browserVersions() and testChromeVersionsWithSeleniumManager() are created in the SeleniumWebDriverManagerTests class.

private WebDriver driver;
@DataProvider
public Iterator<Object[]> browserVersions() {
final List<Object[]> versions = new ArrayList<>();
versions.add(new Object[]{"stable"});
versions.add(new Object[]{"beta"});
versions.add(new Object[]{"dev"});
versions.add(new Object[]{"canary"});
return versions.iterator();
}

@Test(dataProvider = "browserVersions")
public void testChromeVersionsWithSeleniumManager(final String browserVersion) throws InterruptedException {
final ChromeOptions options = new ChromeOptions();
options.setBrowserVersion(browserVersion);
this.driver = new ChromeDriver(options);

this.driver.get("https://ecommerce-playground.lambdatest.io/");

assertEquals(this.driver.getTitle(), "Your Store");
}

@AfterMethod(alwaysRun = true)
public void tearDown() {
this.driver.quit();
}
Enter fullscreen mode Exit fullscreen mode

browserVersions() method is created to pass the parameterized browser version values using the @DataProvider annotation.

testChromeVersionsWithSeleniumManager() test is created to run the automation tests on different versions of Chrome browser. The @DataProvider annotation will help iterate and pass on the browser version values in the setBrowserVersion() method of ChromeOptions class that is finally used for running the ChromeDriver. It will run the tests four times for four different versions of browsers as specified in the browserVersions() method.

Test Execution

The following test block is added in the testng.xml that will execute the test for this scenario.

image

The test on the stable version of Chrome runs successfully. However, the tests for beta, dev, and canary versions failed, for which an issue has already been reported on Selenium’s GitHub Repo.

image

The Chrome for Testing release binaries are saved to ~/.cache/selenium/chrome folder.

image

Below are the details of the test execution showing the test failures for the test run on beta, dev, and canary versions.

Error displayed in the logs for beta, dev, and canary versions

image

Test Execution (Chrome — 113.0, Test Status — Pass)

image

Test Execution (Chrome — beta(v116.0.5845.62), Test Status — Fail)

When the tests for the beta, dev, and canary versions run, the Chrome browser icon is displayed with a Test label on it.

image

image

Test Execution (Chrome — dev(v117.0.5927.0), Test Status — Fail)

image

Test Execution (Chrome — canary(v117.0.5930.0), Test Status — Failed)

image

This marks the completion of this scenario where we checked by running the tests using Chrome for Testing browser versions stable, beta, dev, and canary.

Test Scenario 3

In this scenario, we will be checking that when the Chrome browser version is set to 112 for running the automation tests and the ChromeDriver executable is not provided.

Here, Selenium Manager should not be automatically managing the Chrome browser and ChromeDriver for version 112 as Chrome for Testing has been introduced for Chrome version 113 and above, so Selenium Manager won’t be able to find the download endpoints for 112 and throw an error.

  1. Set the Chrome browser version to 112 (not installed on the local machine).

  2. Open Chrome browser.

  3. Navigate to the LambdaTest eCommerce playground website.

Implementation

Let’s run a web automation test by setting the Chrome browser version to 112. However, in this scenario, the local Chrome browser installed has version 115. This test should fail as Chrome for Testing is available for versions 113 and above.

The following test method, testOnChomeOldVersion112(), is available in the SeleniumWebDriverManagerTest class

@Test
public void testOnChromeOldVersion112() {
final ChromeOptions options = new ChromeOptions();
options.setBrowserVersion("112");

this.driver = new ChromeDriver(options);
this.driver.get("https://ecommerce-playground.lambdatest.io/");

assertEquals(this.driver.getTitle(), "Your Store");
}
Enter fullscreen mode Exit fullscreen mode

This test will set the browser version to 112 using the setBrowserVersion() method of ChromeOptions class, navigate to the LambdaTest eCommerce playground website and perform an assertion of the page title.

Test Execution

Here, the testng.xml file has been updated to run the test.

image

The test should fail with the following error: Chrome for Testing was introduced for Chrome version 113, and the current version of the installed browser on the local machine is 115.

image

Here is the screenshot of the test execution:

image

This shows that Chrome for Testing is only available with versions 113 and above and can not be used with versions below it.

This tutorial dive deep into web testing to help you understand its life cycle, elements, angles, the role of automation, and more.

Final Thoughts

With the release of Selenium WebDriver’s version 4.11.0, it has proved to be more powerful with the latest features of Selenium Manager, which includes the batteries and allows it to run the browsers seamlessly, performing the discovery, download, and cache of the browser drivers.

The release of Chrome for Testing (CfT) by the Chrome team has made the automation test engineer’s life easier as it will also allow testing the web application on Chrome’s upcoming versions like beta, dev, and canary

Also, testers don’t have to worry about the Chrome browser installation as well on the local machine as going ahead for version 113 and above, Selenium Manager will automatically perform the automated browser management allowing seamless execution of web automation tests.

With the ease of directly invoking browsers using Selenium Manager, it was a hassle-free experience to use Selenium WebDriver’s latest version, 4.11.0. I recommend using Selenium WebDriver’s latest version, which provides automated drivers and Chrome browser management and helps us perform automation testing seamlessly.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .