// <copyright file="PageLoadingTest.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 System;

namespace OpenQA.Selenium;

[TestFixture]
public class PageLoadingTest : DriverTestFixture
{
    private IWebDriver localDriver;

    [SetUp]
    public void RestartOriginalDriver()
    {
        driver = EnvironmentManager.Instance.GetCurrentDriver();
    }

    [TearDown]
    public void QuitAdditionalDriver()
    {
        if (localDriver != null)
        {
            localDriver.Quit();
            localDriver = null;
        }
    }

    [Test]
    public void NoneStrategyShouldNotWaitForPageToLoad()
    {
        InitLocalDriver(PageLoadStrategy.None);

        string slowPage = EnvironmentManager.Instance.UrlBuilder.WhereIs("sleep?time=5");

        DateTime start = DateTime.Now;
        localDriver.Url = slowPage;
        DateTime end = DateTime.Now;

        TimeSpan duration = end - start;
        // The slow loading resource on that page takes 6 seconds to return,
        // but with 'none' page loading strategy 'get' operation should not wait.
        Assert.That(duration.TotalMilliseconds, Is.LessThan(1000), "Took too long to load page: " + duration.TotalMilliseconds);
    }


    [Test]
    public void NoneStrategyShouldNotWaitForPageToRefresh()
    {
        InitLocalDriver(PageLoadStrategy.None);

        string slowPage = EnvironmentManager.Instance.UrlBuilder.WhereIs("sleep?time=5");

        // We discard the element, but want a check to make sure the page is loaded
        WaitFor(() => localDriver.FindElement(By.TagName("body")), TimeSpan.FromSeconds(10), "did not find body");

        DateTime start = DateTime.Now;
        localDriver.Navigate().Refresh();
        DateTime end = DateTime.Now;

        TimeSpan duration = end - start;
        // The slow loading resource on that page takes 6 seconds to return,
        // but with 'none' page loading strategy 'refresh' operation should not wait.
        Assert.That(duration.TotalMilliseconds, Is.LessThan(1000), "Took too long to load page: " + duration.TotalMilliseconds);
    }

    [Test]
    public void EagerStrategyShouldNotWaitForResources()
    {
        InitLocalDriver(PageLoadStrategy.Eager);

        string slowPage = EnvironmentManager.Instance.UrlBuilder.WhereIs("slowLoadingResourcePage.html");

        DateTime start = DateTime.Now;
        localDriver.Url = slowPage;
        // We discard the element, but want a check to make sure the GET actually
        // completed.
        WaitFor(() => localDriver.FindElement(By.Id("peas")), TimeSpan.FromSeconds(10), "did not find element");
        DateTime end = DateTime.Now;

        // The slow loading resource on that page takes 6 seconds to return. If we
        // waited for it, our load time should be over 6 seconds.
        TimeSpan duration = end - start;
        Assert.That(duration.TotalMilliseconds, Is.LessThan(5 * 1000), "Took too long to load page: " + duration.TotalMilliseconds);
    }

    [Test]
    public void EagerStrategyShouldNotWaitForResourcesOnRefresh()
    {
        InitLocalDriver(PageLoadStrategy.Eager);

        string slowPage = EnvironmentManager.Instance.UrlBuilder.WhereIs("slowLoadingResourcePage.html");
        localDriver.Url = slowPage;

        // We discard the element, but want a check to make sure the GET actually
        // completed.
        WaitFor(() => localDriver.FindElement(By.Id("peas")), TimeSpan.FromSeconds(10), "did not find element");

        DateTime start = DateTime.Now;
        localDriver.Navigate().Refresh();
        // We discard the element, but want a check to make sure the GET actually
        // completed.
        WaitFor(() => localDriver.FindElement(By.Id("peas")), TimeSpan.FromSeconds(10), "did not find element");
        DateTime end = DateTime.Now;

        // The slow loading resource on that page takes 6 seconds to return. If we
        // waited for it, our load time should be over 6 seconds.
        TimeSpan duration = end - start;
        Assert.That(duration.TotalMilliseconds, Is.LessThan(5 * 1000), "Took too long to load page: " + duration.TotalMilliseconds);
    }

    [Test]
    public void EagerStrategyShouldWaitForDocumentToBeLoaded()
    {
        InitLocalDriver(PageLoadStrategy.Eager);

        string slowPage = EnvironmentManager.Instance.UrlBuilder.WhereIs("sleep?time=3");

        localDriver.Url = slowPage;

        // We discard the element, but want a check to make sure the GET actually completed.
        WaitFor(() => localDriver.FindElement(By.TagName("body")), TimeSpan.FromSeconds(10), "did not find body");
    }

    [Test]
    public void NormalStrategyShouldWaitForDocumentToBeLoaded()
    {
        driver.Url = simpleTestPage;

        Assert.That(driver.Title, Is.EqualTo("Hello WebDriver"));
    }

    [Test]
    [IgnoreBrowser(Browser.All, "Server not properly redirecting")]
    public void ShouldFollowRedirectsSentInTheHttpResponseHeaders()
    {
        driver.Url = redirectPage;
        Assert.That(driver.Title, Is.EqualTo("We Arrive Here"));
    }

    [Test]
    public void ShouldFollowMetaRedirects()
    {
        driver.Url = metaRedirectPage;
        WaitFor(() => { return driver.Title == "We Arrive Here"; }, "Browser title was not 'We Arrive Here'");
        Assert.That(driver.Title, Is.EqualTo("We Arrive Here"));
    }

    [Test]
    [IgnoreBrowser(Browser.Firefox, "Browser doesn't see subsequent navigation to a fragment as a new navigation.")]
    public void ShouldBeAbleToGetAFragmentOnTheCurrentPage()
    {
        driver.Url = xhtmlTestPage;
        driver.Url = xhtmlTestPage + "#text";
        driver.FindElement(By.Id("id1"));
    }

    [Test]
    [NeedsFreshDriver(IsCreatedBeforeTest = true)]
    public void ShouldThrowIfUrlIsMalformed()
    {
        Assert.That(() => driver.Url = "www.test.com", Throws.InstanceOf<WebDriverException>());
    }

    [Test]
    [NeedsFreshDriver(IsCreatedBeforeTest = true)]
    public void ShouldThrowIfUrlIsMalformedInPortPart()
    {
        Assert.That(() => driver.Url = "http://localhost:30001bla", Throws.InstanceOf<WebDriverException>());
    }

    [Test]
    public void ShouldReturnUrlOnNotExistedPage()
    {
        string url = EnvironmentManager.Instance.UrlBuilder.WhereIs("not_existed_page.html");
        driver.Url = url;
        Assert.That(driver.Url, Is.EqualTo(url));
    }

    [Test]
    public void ShouldBeAbleToLoadAPageWithFramesetsAndWaitUntilAllFramesAreLoaded()
    {
        driver.Url = framesetPage;

        driver.SwitchTo().Frame(0);
        IWebElement pageNumber = driver.FindElement(By.XPath("//span[@id='pageNumber']"));
        Assert.That(pageNumber.Text.Trim(), Is.EqualTo("1"));

        driver.SwitchTo().DefaultContent().SwitchTo().Frame(1);
        pageNumber = driver.FindElement(By.XPath("//span[@id='pageNumber']"));
        Assert.That(pageNumber.Text.Trim(), Is.EqualTo("2"));
    }

    [Test]
    [NeedsFreshDriver(IsCreatedBeforeTest = true)]
    public void ShouldDoNothingIfThereIsNothingToGoBackTo()
    {
        string originalTitle = driver.Title;
        driver.Url = formsPage;

        driver.Navigate().Back();
        // We may have returned to the browser's home page
        string currentTitle = driver.Title;
        Assert.That(currentTitle, Is.EqualTo(originalTitle).Or.EqualTo("We Leave From Here"));
        if (driver.Title == originalTitle)
        {
            driver.Navigate().Back();
            Assert.That(driver.Title, Is.EqualTo(originalTitle));
        }
    }

    [Test]
    public void ShouldBeAbleToNavigateBackInTheBrowserHistory()
    {
        driver.Url = formsPage;

        driver.FindElement(By.Id("imageButton")).Submit();
        WaitFor(TitleToBeEqualTo("We Arrive Here"), "Browser title was not 'We Arrive Here'");
        Assert.That(driver.Title, Is.EqualTo("We Arrive Here"));

        driver.Navigate().Back();
        WaitFor(TitleToBeEqualTo("We Leave From Here"), "Browser title was not 'We Leave From Here'");
        Assert.That(driver.Title, Is.EqualTo("We Leave From Here"));
    }

    [Test]
    public void ShouldBeAbleToNavigateBackInTheBrowserHistoryInPresenceOfIframes()
    {
        driver.Url = xhtmlTestPage;

        driver.FindElement(By.Name("sameWindow")).Click();
        WaitFor(TitleToBeEqualTo("This page has iframes"), "Browser title was not 'This page has iframes'");
        Assert.That(driver.Title, Is.EqualTo("This page has iframes"));

        driver.Navigate().Back();
        WaitFor(TitleToBeEqualTo("XHTML Test Page"), "Browser title was not 'XHTML Test Page'");
        Assert.That(driver.Title, Is.EqualTo("XHTML Test Page"));
    }

    [Test]
    public void ShouldBeAbleToNavigateForwardsInTheBrowserHistory()
    {
        driver.Url = formsPage;

        driver.FindElement(By.Id("imageButton")).Submit();
        WaitFor(TitleToBeEqualTo("We Arrive Here"), "Browser title was not 'We Arrive Here'");
        Assert.That(driver.Title, Is.EqualTo("We Arrive Here"));

        driver.Navigate().Back();
        WaitFor(TitleToBeEqualTo("We Leave From Here"), "Browser title was not 'We Leave From Here'");
        Assert.That(driver.Title, Is.EqualTo("We Leave From Here"));

        driver.Navigate().Forward();
        WaitFor(TitleToBeEqualTo("We Arrive Here"), "Browser title was not 'We Arrive Here'");
        Assert.That(driver.Title, Is.EqualTo("We Arrive Here"));
    }

    [Test]
    [Ignore("Unable to open secure url")]
    [IgnoreBrowser(Browser.IE, "Browser does not support using insecure SSL certs")]
    [IgnoreBrowser(Browser.Safari, "Browser does not support using insecure SSL certs")]
    public void ShouldBeAbleToAccessPagesWithAnInsecureSslCertificate()
    {
        String url = EnvironmentManager.Instance.UrlBuilder.WhereIsSecure("simpleTest.html");
        driver.Url = url;

        // This should work
        Assert.That(driver.Title, Is.EqualTo("Hello WebDriver"));
    }

    [Test]
    public void ShouldBeAbleToRefreshAPage()
    {
        driver.Url = xhtmlTestPage;

        driver.Navigate().Refresh();

        Assert.That(driver.Title, Is.EqualTo("XHTML Test Page"));
    }

    /// <summary>
    /// see <a href="http://code.google.com/p/selenium/issues/detail?id=208">Issue 208</a>
    /// </summary>
    [Test]
    [IgnoreBrowser(Browser.IE, "Browser does, in fact, hang in this case.")]
    [IgnoreBrowser(Browser.Firefox, "Browser does, in fact, hang in this case.")]
    public void ShouldNotHangIfDocumentOpenCallIsNeverFollowedByDocumentCloseCall()
    {
        driver.Url = documentWrite;

        // If this command succeeds, then all is well.
        driver.FindElement(By.XPath("//body"));
    }

    [Test]
    [NeedsFreshDriver(IsCreatedAfterTest = true)]
    public void PageLoadTimeoutCanBeChanged()
    {
        TestPageLoadTimeoutIsEnforced(2);
        TestPageLoadTimeoutIsEnforced(3);
    }

    [Test]
    [NeedsFreshDriver(IsCreatedAfterTest = true)]
    public void CanHandleSequentialPageLoadTimeouts()
    {
        long pageLoadTimeout = 2;
        long pageLoadTimeBuffer = 10;
        string slowLoadingPageUrl = EnvironmentManager.Instance.UrlBuilder.WhereIs("sleep?time=" + (pageLoadTimeout + pageLoadTimeBuffer));
        driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(2);
        AssertPageLoadTimeoutIsEnforced(() => driver.Url = slowLoadingPageUrl, pageLoadTimeout, pageLoadTimeBuffer);
        AssertPageLoadTimeoutIsEnforced(() => driver.Url = slowLoadingPageUrl, pageLoadTimeout, pageLoadTimeBuffer);
    }

    [Test]
    [NeedsFreshDriver(IsCreatedAfterTest = true)]
    public void ShouldTimeoutIfAPageTakesTooLongToLoad()
    {
        try
        {
            TestPageLoadTimeoutIsEnforced(2);
        }
        finally
        {
            driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(300);
        }

        // Load another page after get() timed out but before test HTTP server served previous page.
        driver.Url = xhtmlTestPage;
        WaitFor(TitleToBeEqualTo("XHTML Test Page"), "Title was not expected value");
    }

    [Test]
    [NeedsFreshDriver(IsCreatedAfterTest = true)]
    public void ShouldTimeoutIfAPageTakesTooLongToLoadAfterClick()
    {
        driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(2);

        driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIs("page_with_link_to_slow_loading_page.html");
        IWebElement link = WaitFor(() => driver.FindElement(By.Id("link-to-slow-loading-page")), "Could not find link");

        try
        {
            AssertPageLoadTimeoutIsEnforced(() => link.Click(), 2, 3);
        }
        finally
        {
            driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(300);
        }

        // Load another page after get() timed out but before test HTTP server served previous page.
        driver.Url = xhtmlTestPage;
        WaitFor(TitleToBeEqualTo("XHTML Test Page"), "Title was not expected value");
    }

    [Test]
    [NeedsFreshDriver(IsCreatedAfterTest = true)]
    public void ShouldTimeoutIfAPageTakesTooLongToRefresh()
    {
        // Get the sleeping servlet with a pause of 5 seconds
        long pageLoadTimeout = 2;
        long pageLoadTimeBuffer = 0;
        string slowLoadingPageUrl = EnvironmentManager.Instance.UrlBuilder.WhereIs("sleep?time=" + (pageLoadTimeout + pageLoadTimeBuffer));
        driver.Url = slowLoadingPageUrl;

        driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(2);

        try
        {
            AssertPageLoadTimeoutIsEnforced(() => driver.Navigate().Refresh(), 2, 4);
        }
        finally
        {
            driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(300);
        }

        // Load another page after get() timed out but before test HTTP server served previous page.
        driver.Url = xhtmlTestPage;
        WaitFor(TitleToBeEqualTo("XHTML Test Page"), "Title was not expected value");
    }

    [Test]
    [IgnoreBrowser(Browser.Chrome, "Chrome driver does, in fact, stop loading page after a timeout.")]
    [IgnoreBrowser(Browser.Edge, "Edge driver does, in fact, stop loading page after a timeout.")]
    [NeedsFreshDriver(IsCreatedAfterTest = true)]
    public void ShouldNotStopLoadingPageAfterTimeout()
    {
        try
        {
            TestPageLoadTimeoutIsEnforced(1);
        }
        finally
        {
            driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(300);
        }

        WaitFor(() =>
        {
            try
            {
                string text = driver.FindElement(By.TagName("body")).Text;
                return text.Contains("Slept for 11s");
            }
            catch (NoSuchElementException)
            {
            }
            catch (StaleElementReferenceException)
            {
            }
            return false;
        }, TimeSpan.FromSeconds(30), "Did not find expected text");
    }

    private Func<bool> TitleToBeEqualTo(string expectedTitle)
    {
        return () => { return driver.Title == expectedTitle; };
    }

    /**
     * Sets given pageLoadTimeout to the {@link #driver} and asserts that attempt to navigate to a
     * page that takes much longer (10 seconds longer) to load results in a TimeoutException.
     * <p>
     * Side effects: 1) {@link #driver} is configured to use given pageLoadTimeout,
     * 2) test HTTP server still didn't serve the page to browser (some browsers may still
     * be waiting for the page to load despite the fact that driver responded with the timeout).
     * </p>
     */
    private void TestPageLoadTimeoutIsEnforced(long webDriverPageLoadTimeoutInSeconds)
    {
        // Test page will load this many seconds longer than WD pageLoadTimeout.
        long pageLoadTimeBufferInSeconds = 10;
        string slowLoadingPageUrl = EnvironmentManager.Instance.UrlBuilder.WhereIs("sleep?time=" + (webDriverPageLoadTimeoutInSeconds + pageLoadTimeBufferInSeconds));
        driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(webDriverPageLoadTimeoutInSeconds);
        AssertPageLoadTimeoutIsEnforced(() => driver.Url = slowLoadingPageUrl, webDriverPageLoadTimeoutInSeconds, pageLoadTimeBufferInSeconds);
    }

    private void AssertPageLoadTimeoutIsEnforced(TestDelegate delegateToTest, long webDriverPageLoadTimeoutInSeconds, long pageLoadTimeBufferInSeconds)
    {
        DateTime start = DateTime.Now;
        Assert.That(delegateToTest, Throws.InstanceOf<WebDriverTimeoutException>(), "I should have timed out after " + webDriverPageLoadTimeoutInSeconds + " seconds");
        DateTime end = DateTime.Now;
        TimeSpan duration = end - start;
        Assert.That(duration.TotalSeconds, Is.GreaterThan(webDriverPageLoadTimeoutInSeconds));
        Assert.That(duration.TotalSeconds, Is.LessThan(webDriverPageLoadTimeoutInSeconds + pageLoadTimeBufferInSeconds));
    }

    private void InitLocalDriver(PageLoadStrategy strategy)
    {
        EnvironmentManager.Instance.CloseCurrentDriver();
        if (localDriver != null)
        {
            localDriver.Quit();
        }

        PageLoadStrategyOptions options = new PageLoadStrategyOptions();
        options.PageLoadStrategy = strategy;
        localDriver = EnvironmentManager.Instance.CreateDriverInstance(options);
    }

    private class PageLoadStrategyOptions : DriverOptions
    {
        public override void AddAdditionalOption(string capabilityName, object capabilityValue)
        {
        }

        public override ICapabilities ToCapabilities()
        {
            return null;
        }
    }
}
