// <copyright file="CookieImplementationTest.cs" company="Selenium Committers">
// 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.
// </copyright>

using NUnit.Framework;
using OpenQA.Selenium.Environment;
using OpenQA.Selenium.Internal;
using System;
using System.Collections.ObjectModel;
using System.Text;
using System.Text.RegularExpressions;

namespace OpenQA.Selenium;

[TestFixture]
public class CookieImplementationTest : DriverTestFixture
{
    private Random random = new Random();
    private bool isOnAlternativeHostName;
    private string hostname;


    [SetUp]
    public void GoToSimplePageAndDeleteCookies()
    {
        GotoValidDomainAndClearCookies("animals");
        AssertNoCookiesArePresent();
    }

    [Test]
    public void ShouldGetCookieByName()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        string key = string.Format("key_{0}", new Random().Next());
        ((IJavaScriptExecutor)driver).ExecuteScript("document.cookie = arguments[0] + '=set';", key);

        Cookie cookie = driver.Manage().Cookies.GetCookieNamed(key);
        Assert.That(cookie.Value, Is.EqualTo("set"));
    }

    [Test]
    public void ShouldBeAbleToAddCookie()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        string key = GenerateUniqueKey();
        string value = "foo";
        Cookie cookie = new Cookie(key, value);
        AssertCookieIsNotPresentWithName(key);

        driver.Manage().Cookies.AddCookie(cookie);

        AssertCookieHasValue(key, value);
        Assert.That(driver.Manage().Cookies.AllCookies, Does.Contain(cookie), "Cookie was not added successfully");
    }

    [Test]
    public void GetAllCookies()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        string key1 = GenerateUniqueKey();
        string key2 = GenerateUniqueKey();

        AssertCookieIsNotPresentWithName(key1);
        AssertCookieIsNotPresentWithName(key2);

        ReadOnlyCollection<Cookie> cookies = driver.Manage().Cookies.AllCookies;
        int count = cookies.Count;

        Cookie one = new Cookie(key1, "value");
        Cookie two = new Cookie(key2, "value");

        driver.Manage().Cookies.AddCookie(one);
        driver.Manage().Cookies.AddCookie(two);

        driver.Url = simpleTestPage;
        cookies = driver.Manage().Cookies.AllCookies;
        Assert.That(cookies, Has.Count.EqualTo(count + 2));

        Assert.That(cookies, Does.Contain(one));
        Assert.That(cookies, Does.Contain(two));
    }

    [Test]
    public void DeleteAllCookies()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        ((IJavaScriptExecutor)driver).ExecuteScript("document.cookie = 'foo=set';");
        AssertSomeCookiesArePresent();

        driver.Manage().Cookies.DeleteAllCookies();

        AssertNoCookiesArePresent();
    }

    [Test]
    public void DeleteCookieWithName()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        string key1 = GenerateUniqueKey();
        string key2 = GenerateUniqueKey();

        ((IJavaScriptExecutor)driver).ExecuteScript("document.cookie = arguments[0] + '=set';", key1);
        ((IJavaScriptExecutor)driver).ExecuteScript("document.cookie = arguments[0] + '=set';", key2);

        AssertCookieIsPresentWithName(key1);
        AssertCookieIsPresentWithName(key2);

        driver.Manage().Cookies.DeleteCookieNamed(key1);

        AssertCookieIsNotPresentWithName(key1);
        AssertCookieIsPresentWithName(key2);
    }

    [Test]
    public void ShouldNotDeleteCookiesWithASimilarName()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        string cookieOneName = "fish";
        Cookie cookie1 = new Cookie(cookieOneName, "cod");
        Cookie cookie2 = new Cookie(cookieOneName + "x", "earth");
        IOptions options = driver.Manage();
        AssertCookieIsNotPresentWithName(cookie1.Name);

        options.Cookies.AddCookie(cookie1);
        options.Cookies.AddCookie(cookie2);

        AssertCookieIsPresentWithName(cookie1.Name);

        options.Cookies.DeleteCookieNamed(cookieOneName);

        Assert.That(driver.Manage().Cookies.AllCookies, Does.Not.Contain(cookie1));
        Assert.That(driver.Manage().Cookies.AllCookies, Does.Contain(cookie2));
    }

    [Test]
    public void AddCookiesWithDifferentPathsThatAreRelatedToOurs()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        string basePath = EnvironmentManager.Instance.UrlBuilder.Path;

        Cookie cookie1 = new Cookie("fish", "cod", "/" + basePath + "/animals");
        Cookie cookie2 = new Cookie("planet", "earth", "/" + basePath + "/");
        IOptions options = driver.Manage();
        options.Cookies.AddCookie(cookie1);
        options.Cookies.AddCookie(cookie2);

        UrlBuilder builder = EnvironmentManager.Instance.UrlBuilder;
        driver.Url = builder.WhereIs("animals");

        ReadOnlyCollection<Cookie> cookies = options.Cookies.AllCookies;
        AssertCookieIsPresentWithName(cookie1.Name);
        AssertCookieIsPresentWithName(cookie2.Name);

        driver.Url = builder.WhereIs("simpleTest.html");
        AssertCookieIsNotPresentWithName(cookie1.Name);
    }

    [Test]
    [IgnoreBrowser(Browser.Firefox, "https://github.com/mozilla/geckodriver/issues/1104")]
    public void GetCookiesInAFrame()
    {
        driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIs("animals");
        Cookie cookie1 = new Cookie("fish", "cod", "/common/animals");
        driver.Manage().Cookies.AddCookie(cookie1);

        driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIs("frameWithAnimals.html");
        AssertCookieIsNotPresentWithName(cookie1.Name);

        driver.SwitchTo().Frame("iframe1");
        AssertCookieIsPresentWithName(cookie1.Name);
    }

    [Test]
    public void CannotGetCookiesWithPathDifferingOnlyInCase()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        string cookieName = "fish";
        driver.Manage().Cookies.AddCookie(new Cookie(cookieName, "cod", "/Common/animals"));

        driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIs("animals");
        Assert.That(driver.Manage().Cookies.GetCookieNamed(cookieName), Is.Null);
    }

    [Test]
    public void ShouldNotGetCookieOnDifferentDomain()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        string cookieName = "fish";
        driver.Manage().Cookies.AddCookie(new Cookie(cookieName, "cod"));
        AssertCookieIsPresentWithName(cookieName);

        driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereElseIs("simpleTest.html");

        AssertCookieIsNotPresentWithName(cookieName);
    }

    [Test]
    public void ShouldBeAbleToAddToADomainWhichIsRelatedToTheCurrentDomain()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        // Cookies cannot be set on domain names with less than 2 dots, so
        // localhost is out. If we are in that boat, bail the test.
        string hostName = EnvironmentManager.Instance.UrlBuilder.HostName;
        string[] hostNameParts = hostName.Split(new char[] { '.' });
        if (hostNameParts.Length < 3)
        {
            Assert.Ignore("Skipping test: Cookies can only be set on fully-qualified domain names.");
        }

        AssertCookieIsNotPresentWithName("name");

        Regex replaceRegex = new Regex(".*?\\.");
        string shorter = replaceRegex.Replace(this.hostname, ".", 1);
        Cookie cookie = new Cookie("name", "value", shorter, "/", GetTimeInTheFuture());

        driver.Manage().Cookies.AddCookie(cookie);

        AssertCookieIsPresentWithName("name");
    }

    [Test]
    [IgnoreBrowser(Browser.IE, "IE does not want to set cookie")]
    public void ShouldNotGetCookiesRelatedToCurrentDomainWithoutLeadingPeriod()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        string cookieName = "name";
        AssertCookieIsNotPresentWithName(cookieName);

        Regex replaceRegex = new Regex(".*?\\.");
        string subdomain = replaceRegex.Replace(this.hostname, "subdomain.", 1);
        Cookie cookie = new Cookie(cookieName, "value", subdomain, "/", GetTimeInTheFuture());

        string originalUrl = driver.Url;
        string subdomainUrl = originalUrl.Replace(this.hostname, subdomain);
        driver.Url = subdomainUrl;
        driver.Manage().Cookies.AddCookie(cookie);

        driver.Url = originalUrl;
        AssertCookieIsNotPresentWithName(cookieName);
    }

    [Test]
    public void ShouldBeAbleToIncludeLeadingPeriodInDomainName()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        // Cookies cannot be set on domain names with less than 2 dots, so
        // localhost is out. If we are in that boat, bail the test.
        string hostName = EnvironmentManager.Instance.UrlBuilder.HostName;
        string[] hostNameParts = hostName.Split(new char[] { '.' });
        if (hostNameParts.Length < 3)
        {
            Assert.Ignore("Skipping test: Cookies can only be set on fully-qualified domain names.");
        }

        AssertCookieIsNotPresentWithName("name");

        // Replace the first part of the name with a period
        Regex replaceRegex = new Regex(".*?\\.");
        string shorter = replaceRegex.Replace(this.hostname, ".", 1);
        Cookie cookie = new Cookie("name", "value", shorter, "/", DateTime.Now.AddSeconds(100000));

        driver.Manage().Cookies.AddCookie(cookie);

        AssertCookieIsPresentWithName("name");
    }

    [Test]
    public void ShouldBeAbleToSetDomainToTheCurrentDomain()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        // Cookies cannot be set on domain names with less than 2 dots, so
        // localhost is out. If we are in that boat, bail the test.
        string hostName = EnvironmentManager.Instance.UrlBuilder.HostName;
        string[] hostNameParts = hostName.Split(new char[] { '.' });
        if (hostNameParts.Length < 3)
        {
            Assert.Ignore("Skipping test: Cookies can only be set on fully-qualified domain names.");
        }

        Uri url = new Uri(driver.Url);
        String host = url.Host + ":" + url.Port.ToString();

        Cookie cookie1 = new Cookie("fish", "cod", host, "/", null);
        IOptions options = driver.Manage();
        options.Cookies.AddCookie(cookie1);

        driver.Url = javascriptPage;
        ReadOnlyCollection<Cookie> cookies = options.Cookies.AllCookies;
        Assert.That(cookies, Does.Contain(cookie1));
    }

    [Test]
    public void ShouldWalkThePathToDeleteACookie()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        string basePath = EnvironmentManager.Instance.UrlBuilder.Path;

        Cookie cookie1 = new Cookie("fish", "cod");
        driver.Manage().Cookies.AddCookie(cookie1);
        int count = driver.Manage().Cookies.AllCookies.Count;

        driver.Url = childPage;
        Cookie cookie2 = new Cookie("rodent", "hamster", "/" + basePath + "/child");
        driver.Manage().Cookies.AddCookie(cookie2);
        count = driver.Manage().Cookies.AllCookies.Count;

        driver.Url = grandchildPage;
        Cookie cookie3 = new Cookie("dog", "dalmation", "/" + basePath + "/child/grandchild/");
        driver.Manage().Cookies.AddCookie(cookie3);
        count = driver.Manage().Cookies.AllCookies.Count;

        driver.Url = (EnvironmentManager.Instance.UrlBuilder.WhereIs("child/grandchild"));
        driver.Manage().Cookies.DeleteCookieNamed("rodent");
        count = driver.Manage().Cookies.AllCookies.Count;

        Assert.That(driver.Manage().Cookies.GetCookieNamed("rodent"), Is.Null);

        ReadOnlyCollection<Cookie> cookies = driver.Manage().Cookies.AllCookies;
        Assert.That(cookies, Has.Exactly(2).Items);
        Assert.That(cookies, Does.Contain(cookie1));
        Assert.That(cookies, Does.Contain(cookie3));

        driver.Manage().Cookies.DeleteAllCookies();
        driver.Url = grandchildPage;
        AssertNoCookiesArePresent();
    }

    [Test]
    public void ShouldIgnoreThePortNumberOfTheHostWhenSettingTheCookie()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        // Cookies cannot be set on domain names with less than 2 dots, so
        // localhost is out. If we are in that boat, bail the test.
        string hostName = EnvironmentManager.Instance.UrlBuilder.HostName;
        string[] hostNameParts = hostName.Split(new char[] { '.' });
        if (hostNameParts.Length < 3)
        {
            Assert.Ignore("Skipping test: Cookies can only be set on fully-qualified domain names.");
        }

        Uri uri = new Uri(driver.Url);
        string host = string.Format("{0}:{1}", uri.Host, uri.Port);
        string cookieName = "name";
        AssertCookieIsNotPresentWithName(cookieName);
        Cookie cookie = new Cookie(cookieName, "value", host, "/", null);
        driver.Manage().Cookies.AddCookie(cookie);
        AssertCookieIsPresentWithName(cookieName);
    }

    [Test]
    public void CookieEqualityAfterSetAndGet()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        DateTime time = DateTime.Now.AddDays(1);
        Cookie cookie1 = new Cookie("fish", "cod", null, "/common/animals", time);
        IOptions options = driver.Manage();
        options.Cookies.AddCookie(cookie1);

        ReadOnlyCollection<Cookie> cookies = options.Cookies.AllCookies;
        Cookie retrievedCookie = null;
        foreach (Cookie tempCookie in cookies)
        {
            if (cookie1.Equals(tempCookie))
            {
                retrievedCookie = tempCookie;
                break;
            }
        }

        Assert.That(retrievedCookie, Is.Not.Null);
        //Cookie.equals only compares name, domain and path
        Assert.That(retrievedCookie, Is.EqualTo(cookie1));
    }

    [Test]
    public void ShouldRetainCookieExpiry()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        // DateTime.Now contains milliseconds; the returned cookie expire date
        // will not. So we need to truncate the milliseconds.
        DateTime current = DateTime.Now;
        DateTime expireDate = new DateTime(current.Year, current.Month, current.Day, current.Hour, current.Minute, current.Second, DateTimeKind.Local).AddDays(1);

        Cookie addCookie = new Cookie("fish", "cod", "/common/animals", expireDate);
        IOptions options = driver.Manage();
        options.Cookies.AddCookie(addCookie);

        Cookie retrieved = options.Cookies.GetCookieNamed("fish");
        Assert.That(retrieved, Is.Not.Null);
        Assert.That(retrieved.Expiry, Is.EqualTo(addCookie.Expiry), "Cookies are not equal");
    }

    [Test]
    [Ignore("Unable to open secure url")]
    [IgnoreBrowser(Browser.IE, "Browser does not handle untrusted SSL certificates.")]
    public void CanHandleSecureCookie()
    {
        driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIsSecure("animals");

        Cookie addedCookie = new ReturnedCookie("fish", "cod", null, "/common/animals", null, true, false, null);
        driver.Manage().Cookies.AddCookie(addedCookie);

        driver.Navigate().Refresh();

        Cookie retrieved = driver.Manage().Cookies.GetCookieNamed("fish");
        Assert.That(retrieved, Is.Not.Null);
    }

    [Test]
    [Ignore("Unable to open secure url")]
    [IgnoreBrowser(Browser.IE, "Browser does not handle untrusted SSL certificates.")]
    public void ShouldRetainCookieSecure()
    {
        driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIsSecure("animals");

        ReturnedCookie addedCookie = new ReturnedCookie("fish", "cod", string.Empty, "/common/animals", null, true, false, null);

        driver.Manage().Cookies.AddCookie(addedCookie);

        driver.Navigate().Refresh();

        Cookie retrieved = driver.Manage().Cookies.GetCookieNamed("fish");
        Assert.That(retrieved, Is.Not.Null);
        Assert.That(retrieved.Secure, "Secure attribute not set to true");
    }

    [Test]
    public void CanHandleHttpOnlyCookie()
    {
        StringBuilder url = new StringBuilder(EnvironmentManager.Instance.UrlBuilder.WhereIs("cookie"));
        url.Append("?action=add");
        url.Append("&name=").Append("fish");
        url.Append("&value=").Append("cod");
        url.Append("&path=").Append("/common/animals");
        url.Append("&httpOnly=").Append("true");

        driver.Url = url.ToString();

        driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIs("animals");
        Cookie retrieved = driver.Manage().Cookies.GetCookieNamed("fish");
        Assert.That(retrieved, Is.Not.Null);
    }

    [Test]
    public void ShouldRetainHttpOnlyFlag()
    {
        StringBuilder url = new StringBuilder(EnvironmentManager.Instance.UrlBuilder.WhereElseIs("cookie"));
        url.Append("?action=add");
        url.Append("&name=").Append("fish");
        url.Append("&value=").Append("cod");
        url.Append("&path=").Append("/common/animals");
        url.Append("&httpOnly=").Append("true");

        driver.Url = url.ToString();

        driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereElseIs("animals");

        Cookie retrieved = driver.Manage().Cookies.GetCookieNamed("fish");
        Assert.That(retrieved, Is.Not.Null);
        Assert.That(retrieved.IsHttpOnly, "HttpOnly attribute not set to true");
    }

    [Test]
    public void SettingACookieThatExpiredInThePast()
    {
        DateTime expires = DateTime.Now.AddSeconds(-1000);
        Cookie cookie = new Cookie("expired", "yes", "/common/animals", expires);
        IOptions options = driver.Manage();
        options.Cookies.AddCookie(cookie);

        cookie = options.Cookies.GetCookieNamed("expired");
        Assert.That(cookie, Is.Null, "Cookie expired before it was set, so nothing should be returned: " + cookie);
    }

    [Test]
    public void CanSetCookieWithoutOptionalFieldsSet()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        string key = GenerateUniqueKey();
        string value = "foo";
        Cookie cookie = new Cookie(key, value);
        AssertCookieIsNotPresentWithName(key);

        driver.Manage().Cookies.AddCookie(cookie);

        AssertCookieHasValue(key, value);
    }

    [Test]
    public void DeleteNotExistedCookie()
    {
        String key = GenerateUniqueKey();
        AssertCookieIsNotPresentWithName(key);

        driver.Manage().Cookies.DeleteCookieNamed(key);
    }

    [Test]
    [IgnoreBrowser(Browser.IE, "IE does not want to set cookie")]
    public void DeleteAllCookiesDifferentUrls()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        Cookie cookie1 = new Cookie("fish1", "cod", EnvironmentManager.Instance.UrlBuilder.HostName, null, null);
        Cookie cookie2 = new Cookie("fish2", "tune", EnvironmentManager.Instance.UrlBuilder.AlternateHostName, null, null);

        string url1 = EnvironmentManager.Instance.UrlBuilder.WhereIs("");
        string url2 = EnvironmentManager.Instance.UrlBuilder.WhereElseIs("");

        IOptions options = driver.Manage();

        options.Cookies.AddCookie(cookie1);
        AssertCookieIsPresentWithName(cookie1.Name);

        driver.Url = url2;
        options.Cookies.AddCookie(cookie2);
        AssertCookieIsNotPresentWithName(cookie1.Name);
        AssertCookieIsPresentWithName(cookie2.Name);

        driver.Url = url1;
        AssertCookieIsPresentWithName(cookie1.Name);
        AssertCookieIsNotPresentWithName(cookie2.Name);

        options.Cookies.DeleteAllCookies();
        AssertCookieIsNotPresentWithName(cookie1.Name);

        driver.Url = url2;
        AssertCookieIsPresentWithName(cookie2.Name);
    }

    [Test]
    [TestCase(null)]
    [TestCase("")]
    [TestCase("   ")]
    public void ShouldThrowWhenGetInvalidCookieByName(string cookieName)
    {
        var getCookieAction = () => driver.Manage().Cookies.GetCookieNamed(cookieName);

        Assert.That(getCookieAction, Throws.ArgumentException);
    }

    [Test]
    [TestCase(null)]
    [TestCase("")]
    [TestCase("   ")]
    public void ShouldThrowWhenDeleteInvalidCookieByName(string cookieName)
    {
        var deleteCookieAction = () => driver.Manage().Cookies.DeleteCookieNamed(cookieName);

        Assert.That(deleteCookieAction, Throws.ArgumentException);
    }

    //------------------------------------------------------------------
    // Tests below here are not included in the Java test suite
    //------------------------------------------------------------------
    [Test]
    public void CanSetCookiesOnADifferentPathOfTheSameHost()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        string basePath = EnvironmentManager.Instance.UrlBuilder.Path;
        Cookie cookie1 = new Cookie("fish", "cod", "/" + basePath + "/animals");
        Cookie cookie2 = new Cookie("planet", "earth", "/" + basePath + "/galaxy");

        IOptions options = driver.Manage();
        ReadOnlyCollection<Cookie> count = options.Cookies.AllCookies;

        options.Cookies.AddCookie(cookie1);
        options.Cookies.AddCookie(cookie2);

        string url = EnvironmentManager.Instance.UrlBuilder.WhereIs("animals");
        driver.Url = url;
        ReadOnlyCollection<Cookie> cookies = options.Cookies.AllCookies;

        Assert.That(cookies, Does.Contain(cookie1));
        Assert.That(cookies, Does.Not.Contain(cookie2));

        driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIs("galaxy");
        cookies = options.Cookies.AllCookies;
        Assert.That(cookies, Does.Not.Contain(cookie1));
        Assert.That(cookies, Does.Contain(cookie2));
    }

    [Test]
    public void ShouldNotBeAbleToSetDomainToSomethingThatIsUnrelatedToTheCurrentDomain()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        Cookie cookie1 = new Cookie("fish", "cod");
        IOptions options = driver.Manage();
        options.Cookies.AddCookie(cookie1);

        string url = EnvironmentManager.Instance.UrlBuilder.WhereElseIs("simpleTest.html");
        driver.Url = url;

        Assert.That(options.Cookies.GetCookieNamed("fish"), Is.Null);
    }

    [Test]
    public void GetCookieDoesNotRetrieveBeyondCurrentDomain()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        Cookie cookie1 = new Cookie("fish", "cod");
        IOptions options = driver.Manage();
        options.Cookies.AddCookie(cookie1);

        String url = EnvironmentManager.Instance.UrlBuilder.WhereElseIs("");
        driver.Url = url;

        ReadOnlyCollection<Cookie> cookies = options.Cookies.AllCookies;
        Assert.That(cookies, Does.Not.Contain(cookie1));
    }

    [Test]
    public void ShouldAddCookieToCurrentDomainAndPath()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        // Cookies cannot be set on domain names with less than 2 dots, so
        // localhost is out. If we are in that boat, bail the test.
        string hostName = EnvironmentManager.Instance.UrlBuilder.HostName;
        string[] hostNameParts = hostName.Split(new char[] { '.' });
        if (hostNameParts.Length < 3)
        {
            Assert.Ignore("Skipping test: Cookies can only be set on fully-qualified domain names.");
        }

        driver.Url = macbethPage;
        IOptions options = driver.Manage();
        Cookie cookie = new Cookie("Homer", "Simpson", this.hostname, "/" + EnvironmentManager.Instance.UrlBuilder.Path, null);
        options.Cookies.AddCookie(cookie);
        ReadOnlyCollection<Cookie> cookies = options.Cookies.AllCookies;
        Assert.That(cookies, Does.Contain(cookie), "Valid cookie was not returned");
    }

    [Test]
    public void ShouldNotShowCookieAddedToDifferentDomain()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            Assert.Ignore("Not on a standard domain for cookies (localhost doesn't count).");
        }

        driver.Url = macbethPage;
        IOptions options = driver.Manage();
        Cookie cookie = new Cookie("Bart", "Simpson", EnvironmentManager.Instance.UrlBuilder.HostName + ".com", EnvironmentManager.Instance.UrlBuilder.Path, null);
        Assert.That(
            () => options.Cookies.AddCookie(cookie),
            Throws.InstanceOf<WebDriverException>().Or.InstanceOf<InvalidOperationException>());

        ReadOnlyCollection<Cookie> cookies = options.Cookies.AllCookies;
        Assert.That(cookies, Does.Not.Contain(cookie), "Invalid cookie was returned");
    }

    [Test]
    public void ShouldNotShowCookieAddedToDifferentPath()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        // Cookies cannot be set on domain names with less than 2 dots, so
        // localhost is out. If we are in that boat, bail the test.
        string hostName = EnvironmentManager.Instance.UrlBuilder.HostName;
        string[] hostNameParts = hostName.Split(new char[] { '.' });
        if (hostNameParts.Length < 3)
        {
            Assert.Ignore("Skipping test: Cookies can only be set on fully-qualified domain names.");
        }

        driver.Url = macbethPage;
        IOptions options = driver.Manage();
        Cookie cookie = new Cookie("Lisa", "Simpson", EnvironmentManager.Instance.UrlBuilder.HostName, "/" + EnvironmentManager.Instance.UrlBuilder.Path + "IDoNotExist", null);
        options.Cookies.AddCookie(cookie);
        ReadOnlyCollection<Cookie> cookies = options.Cookies.AllCookies;
        Assert.That(cookies, Does.Not.Contain(cookie), "Invalid cookie was returned");
    }

    [Test]
    public void ShouldThrowExceptionWhenAddingCookieToCookieAverseDocument()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        // URLs using a non-network scheme (like "about:" or "data:") are
        // averse to cookies, and should throw an InvalidCookieDomainException.
        driver.Url = "about:blank";

        IOptions options = driver.Manage();
        Cookie cookie = new Cookie("question", "dunno");
        Assert.That(
            () => options.Cookies.AddCookie(cookie),
            Throws.InstanceOf<InvalidCookieDomainException>().Or.InstanceOf<InvalidOperationException>());
    }

    [Test]
    public void ShouldReturnNullBecauseCookieRetainsExpiry()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        Cookie addCookie = new Cookie("fish", "cod", "/common/animals", DateTime.Now.AddHours(-1));
        IOptions options = driver.Manage();
        options.Cookies.AddCookie(addCookie);

        Cookie retrieved = options.Cookies.GetCookieNamed("fish");
        Assert.That(retrieved, Is.Null);
    }

    [Test]
    public void ShouldAddCookieToCurrentDomain()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        driver.Url = macbethPage;
        IOptions options = driver.Manage();
        Cookie cookie = new Cookie("Marge", "Simpson", "/");
        options.Cookies.AddCookie(cookie);
        ReadOnlyCollection<Cookie> cookies = options.Cookies.AllCookies;
        Assert.That(cookies, Does.Contain(cookie), "Valid cookie was not returned");
    }

    [Test]
    public void ShouldDeleteCookie()
    {
        if (!CheckIsOnValidHostNameForCookieTests())
        {
            return;
        }

        driver.Url = macbethPage;
        IOptions options = driver.Manage();
        Cookie cookieToDelete = new Cookie("answer", "42");
        Cookie cookieToKeep = new Cookie("canIHaz", "Cheeseburguer");
        options.Cookies.AddCookie(cookieToDelete);
        options.Cookies.AddCookie(cookieToKeep);
        ReadOnlyCollection<Cookie> cookies = options.Cookies.AllCookies;
        options.Cookies.DeleteCookie(cookieToDelete);
        ReadOnlyCollection<Cookie> cookies2 = options.Cookies.AllCookies;
        Assert.That(cookies2, Does.Not.Contain(cookieToDelete), "Cookie was not deleted successfully");
        Assert.That(cookies2, Does.Contain(cookieToKeep), "Valid cookie was not returned");
    }

    //////////////////////////////////////////////
    // Support functions
    //////////////////////////////////////////////

    private void GotoValidDomainAndClearCookies(string page)
    {
        this.hostname = null;
        String hostname = EnvironmentManager.Instance.UrlBuilder.HostName;
        if (IsValidHostNameForCookieTests(hostname))
        {
            this.isOnAlternativeHostName = false;
            this.hostname = hostname;
        }

        hostname = EnvironmentManager.Instance.UrlBuilder.AlternateHostName;
        if (this.hostname == null && IsValidHostNameForCookieTests(hostname))
        {
            this.isOnAlternativeHostName = true;
            this.hostname = hostname;
        }

        GoToPage(page);

        driver.Manage().Cookies.DeleteAllCookies();
        if (driver.Manage().Cookies.AllCookies.Count != 0)
        {
            // If cookies are still present, restart the driver and try again.
            // This may mask some errors, where DeleteAllCookies doesn't fully
            // delete all it should, but that's a tradeoff we need to be willing
            // to make.
            driver = EnvironmentManager.Instance.CreateFreshDriver();
            GoToPage(page);
        }
    }

    private bool CheckIsOnValidHostNameForCookieTests()
    {
        bool correct = this.hostname != null && IsValidHostNameForCookieTests(this.hostname);
        if (!correct)
        {
            System.Console.WriteLine("Skipping test: unable to find domain name to use");
        }

        return correct;
    }

    private void GoToPage(String pageName)
    {
        driver.Url = this.isOnAlternativeHostName ? EnvironmentManager.Instance.UrlBuilder.WhereElseIs(pageName) : EnvironmentManager.Instance.UrlBuilder.WhereIs(pageName);
    }

    private void GoToOtherPage(String pageName)
    {
        driver.Url = this.isOnAlternativeHostName ? EnvironmentManager.Instance.UrlBuilder.WhereIs(pageName) : EnvironmentManager.Instance.UrlBuilder.WhereElseIs(pageName);
    }

    private bool IsValidHostNameForCookieTests(string hostname)
    {
        // TODO(JimEvan): Some coverage is better than none, so we
        // need to ignore the fact that localhost cookies are problematic.
        // Reenable this when we have a better solution per DanielWagnerHall.
        // ChromeDriver2 has trouble with localhost. IE and Firefox don't.
        // return !IsIpv4Address(hostname) && "localhost" != hostname;
        bool isLocalHostOkay = !("localhost" == hostname && !TestUtilities.IsInternetExplorer(driver));

        return !IsIpv4Address(hostname) && isLocalHostOkay;
    }

    private static bool IsIpv4Address(string addrString)
    {
        return Regex.IsMatch(addrString, "\\d{1,3}(?:\\.\\d{1,3}){3}");
    }

    private string GenerateUniqueKey()
    {
        return string.Format("key_{0}", random.Next());
    }

    private string GetDocumentCookieOrNull()
    {
        IJavaScriptExecutor jsDriver = driver as IJavaScriptExecutor;
        if (jsDriver == null)
        {
            return null;
        }
        try
        {
            return (string)jsDriver.ExecuteScript("return document.cookie");
        }
        catch (InvalidOperationException)
        {
            return null;
        }
    }

    private void AssertNoCookiesArePresent()
    {
        Assert.That(driver.Manage().Cookies.AllCookies, Is.Empty, "Cookies were not empty");
        string documentCookie = GetDocumentCookieOrNull();
        if (documentCookie != null)
        {
            Assert.That(documentCookie, Is.Empty, "Cookies were not empty");
        }
    }

    private void AssertSomeCookiesArePresent()
    {
        Assert.That(driver.Manage().Cookies.AllCookies, Is.Not.Empty, "Cookies were empty");
        String documentCookie = GetDocumentCookieOrNull();
        if (documentCookie != null)
        {
            Assert.That(documentCookie, Is.Not.Empty, "Cookies were empty");
        }
    }

    private void AssertCookieIsNotPresentWithName(string key)
    {
        Assert.That(driver.Manage().Cookies.GetCookieNamed(key), Is.Null, "Cookie was present with name " + key);
        string documentCookie = GetDocumentCookieOrNull();
        if (documentCookie != null)
        {
            Assert.That(documentCookie, Does.Not.Contain(key + "="));
        }
    }

    private void AssertCookieIsPresentWithName(string key)
    {
        Assert.That(driver.Manage().Cookies.GetCookieNamed(key), Is.Not.Null, "Cookie was present with name " + key);
        string documentCookie = GetDocumentCookieOrNull();
        if (documentCookie != null)
        {
            Assert.That(documentCookie, Does.Contain(key + "="));
        }
    }

    private void AssertCookieHasValue(string key, string value)
    {
        Assert.That(driver.Manage().Cookies.GetCookieNamed(key).Value, Is.EqualTo(value), "Cookie had wrong value");
        string documentCookie = GetDocumentCookieOrNull();
        if (documentCookie != null)
        {
            Assert.That(documentCookie, Does.Contain(key + "=" + value));
        }
    }

    private DateTime GetTimeInTheFuture()
    {
        return DateTime.Now.Add(TimeSpan.FromMilliseconds(100000));
    }
}
