// <copyright file="FrameSwitchingTest.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 FrameSwitchingTest : DriverTestFixture
{
    // ----------------------------------------------------------------------------------------------
    //
    // Tests that WebDriver doesn't do anything fishy when it navigates to a page with frames.
    //
    // ----------------------------------------------------------------------------------------------

    [Test]
    public void ShouldAlwaysFocusOnTheTopMostFrameAfterANavigationEvent()
    {
        driver.Url = framesetPage;
        IWebElement element = driver.FindElement(By.TagName("frameset"));
        Assert.That(element, Is.Not.Null);
    }

    [Test]
    public void ShouldNotAutomaticallySwitchFocusToAnIFrameWhenAPageContainingThemIsLoaded()
    {
        driver.Url = iframePage;
        driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(1);
        IWebElement element = driver.FindElement(By.Id("iframe_page_heading"));
        driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(0);
        Assert.That(element, Is.Not.Null);
    }

    [Test]
    public void ShouldOpenPageWithBrokenFrameset()
    {
        driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIs("framesetPage3.html");

        IWebElement frame1 = driver.FindElement(By.Id("first"));
        driver.SwitchTo().Frame(frame1);

        driver.SwitchTo().DefaultContent();

        IWebElement frame2 = driver.FindElement(By.Id("second"));

        try
        {
            driver.SwitchTo().Frame(frame2);
        }
        catch (WebDriverException)
        {
            // IE9 can not switch to this broken frame - it has no window.
        }
    }

    // ----------------------------------------------------------------------------------------------
    //
    // Tests that WebDriver can switch to frames as expected.
    //
    // ----------------------------------------------------------------------------------------------

    [Test]
    public void ShouldBeAbleToSwitchToAFrameByItsIndex()
    {
        driver.Url = framesetPage;
        driver.SwitchTo().Frame(1);

        Assert.That(driver.FindElement(By.Id("pageNumber")).Text, Is.EqualTo("2"));
    }

    [Test]
    public void ShouldBeAbleToSwitchToAnIframeByItsIndex()
    {
        driver.Url = iframePage;
        driver.SwitchTo().Frame(0);

        Assert.That(driver.FindElement(By.Name("id-name1")).GetAttribute("value"), Is.EqualTo("name"));
    }

    [Test]
    public void ShouldBeAbleToSwitchToAFrameByItsName()
    {
        driver.Url = framesetPage;
        driver.SwitchTo().Frame("fourth");
        Assert.That(driver.FindElement(By.TagName("frame")).GetAttribute("name"), Is.EqualTo("child1"));

    }

    [Test]
    public void ShouldBeAbleToSwitchToAnIframeByItsName()
    {
        driver.Url = iframePage;
        driver.SwitchTo().Frame("iframe1-name");
        Assert.That(driver.FindElement(By.Name("id-name1")).GetAttribute("value"), Is.EqualTo("name"));

    }

    [Test]
    public void ShouldBeAbleToSwitchToAFrameByItsID()
    {
        driver.Url = framesetPage;
        driver.SwitchTo().Frame("fifth");
        Assert.That(driver.FindElement(By.Name("windowOne")).Text, Is.EqualTo("Open new window"));

    }

    [Test]
    public void ShouldBeAbleToSwitchToAnIframeByItsID()
    {
        driver.Url = iframePage;
        driver.SwitchTo().Frame("iframe1");
        Assert.That(driver.FindElement(By.Name("id-name1")).GetAttribute("value"), Is.EqualTo("name"));
    }

    [Test]
    public void ShouldBeAbleToSwitchToFrameWithNameContainingDot()
    {
        driver.Url = framesetPage;
        driver.SwitchTo().Frame("sixth.iframe1");
        Assert.That(driver.FindElement(By.TagName("body")).Text, Does.Contain("Page number 3"));
    }

    [Test]
    public void ShouldBeAbleToSwitchToAFrameUsingAPreviouslyLocatedWebElement()
    {
        driver.Url = framesetPage;
        IWebElement frame = driver.FindElement(By.TagName("frame"));
        driver.SwitchTo().Frame(frame);
        Assert.That(driver.FindElement(By.Id("pageNumber")).Text, Is.EqualTo("1"));
    }

    [Test]
    public void ShouldBeAbleToSwitchToAnIFrameUsingAPreviouslyLocatedWebElement()
    {
        driver.Url = iframePage;
        IWebElement frame = driver.FindElement(By.TagName("iframe"));
        driver.SwitchTo().Frame(frame);
        Assert.That(driver.FindElement(By.Name("id-name1")).GetAttribute("value"), Is.EqualTo("name"));

    }

    [Test]
    public void ShouldEnsureElementIsAFrameBeforeSwitching()
    {
        driver.Url = framesetPage;
        IWebElement frame = driver.FindElement(By.TagName("frameset"));
        Assert.That(() => driver.SwitchTo().Frame(frame), Throws.InstanceOf<NoSuchFrameException>());
    }

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

        IWebElement frameElement = WaitFor(() => driver.FindElement(By.Name("second")), "did not find frame");
        driver.SwitchTo().Frame(frameElement);
        Assert.That(driver.FindElement(By.Id("pageNumber")).Text, Is.EqualTo("2"));

        Assert.That(
            () => driver.SwitchTo().Frame("third"),
            Throws.TypeOf<NoSuchFrameException>());

        driver.SwitchTo().DefaultContent();
        driver.SwitchTo().Frame("third");

        Assert.That(
            () => driver.SwitchTo().Frame("second"),
            Throws.TypeOf<NoSuchFrameException>());

        driver.SwitchTo().DefaultContent();
        driver.SwitchTo().Frame("second");
        Assert.That(driver.FindElement(By.Id("pageNumber")).Text, Is.EqualTo("2"));
    }

    [Test]
    public void ShouldSelectChildFramesByChainedCalls()
    {
        driver.Url = framesetPage;
        driver.SwitchTo().Frame("fourth").SwitchTo().Frame("child2");
        Assert.That(driver.FindElement(By.Id("pageNumber")).Text, Is.EqualTo("11"));
    }

    [Test]
    public void ShouldThrowFrameNotFoundExceptionLookingUpSubFramesWithSuperFrameNames()
    {
        driver.Url = framesetPage;
        driver.SwitchTo().Frame("fourth");
        Assert.That(
            () => driver.SwitchTo().Frame("second"),
            Throws.TypeOf<NoSuchFrameException>());

    }

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

        Assert.That(
            () => driver.SwitchTo().Frame("Nothing here"),
            Throws.TypeOf<NoSuchFrameException>());
    }

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

        Assert.That(
            () => driver.SwitchTo().Frame(27),
            Throws.TypeOf<NoSuchFrameException>());
    }

    [Test]
    public void ShouldBeAbleToSwitchToParentFrame()
    {
        driver.Url = framesetPage;
        driver.SwitchTo().Frame("fourth").SwitchTo().ParentFrame().SwitchTo().Frame("first");
        Assert.That(driver.FindElement(By.Id("pageNumber")).Text, Is.EqualTo("1"));
    }

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

        driver.SwitchTo().Frame("fourth").SwitchTo().Frame("child1").SwitchTo().ParentFrame().SwitchTo().Frame("child2");
        Assert.That(driver.FindElement(By.Id("pageNumber")).Text, Is.EqualTo("11"));
    }

    [Test]
    public void SwitchingToParentFrameFromDefaultContextIsNoOp()
    {
        driver.Url = xhtmlTestPage;
        driver.SwitchTo().ParentFrame();
        Assert.That(driver.Title, Is.EqualTo("XHTML Test Page"));
    }

    [Test]
    public void ShouldBeAbleToSwitchToParentFromAnIframe()
    {
        driver.Url = iframePage;
        driver.SwitchTo().Frame(0);

        driver.SwitchTo().ParentFrame();
        driver.FindElement(By.Id("iframe_page_heading"));
    }

    // ----------------------------------------------------------------------------------------------
    //
    // General frame handling behavior tests
    //
    // ----------------------------------------------------------------------------------------------
    [Test]
    public void ShouldContinueToReferToTheSameFrameOnceItHasBeenSelected()
    {
        driver.Url = framesetPage;

        driver.SwitchTo().Frame(2);
        IWebElement checkbox = driver.FindElement(By.XPath("//input[@name='checky']"));
        checkbox.Click();
        checkbox.Submit();
        WaitFor(() => driver.FindElement(By.XPath("//p")).Text == "Success!", "result element not found");
    }

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

        driver.SwitchTo().Frame(0);
        driver.FindElement(By.LinkText("top")).Click();

        WaitFor(() => { return driver.Title == "XHTML Test Page"; }, "Browser title was not 'XHTML Test Page'");
        Assert.That(driver.Title, Is.EqualTo("XHTML Test Page"));
    }

    [Test]
    public void ShouldAllowAUserToSwitchFromAnIframeBackToTheMainContentOfThePage()
    {
        driver.Url = iframePage;
        driver.SwitchTo().Frame(0);

        driver.SwitchTo().DefaultContent();
        driver.FindElement(By.Id("iframe_page_heading"));
    }


    [Test]
    public void ShouldAllowTheUserToSwitchToAnIFrameAndRemainFocusedOnIt()
    {
        driver.Url = iframePage;
        driver.SwitchTo().Frame(0);

        driver.FindElement(By.Id("submitButton")).Click();

        string hello = GetTextOfGreetingElement();
        Assert.That(hello, Is.EqualTo("Success!"));
    }

    [Test]
    public void ShouldBeAbleToClickInAFrame()
    {
        driver.Url = framesetPage;
        driver.SwitchTo().Frame("third");

        // This should replace frame "third" ...
        driver.FindElement(By.Id("submitButton")).Click();

        // driver should still be focused on frame "third" ...
        Assert.That(GetTextOfGreetingElement(), Is.EqualTo("Success!"));

        // Make sure it was really frame "third" which was replaced ...
        driver.SwitchTo().DefaultContent().SwitchTo().Frame("third");
        Assert.That(GetTextOfGreetingElement(), Is.EqualTo("Success!"));
    }

    [Test]
    public void ShouldBeAbleToClickInAFrameThatRewritesTopWindowLocation()
    {
        driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIs("click_tests/issue5237.html");
        driver.SwitchTo().Frame("search");
        driver.FindElement(By.Id("submit")).Click();
        driver.SwitchTo().DefaultContent();
        WaitFor(() => { return driver.Title == "Target page for issue 5237"; }, "Browser title was not 'Target page for issue 5237'");
    }

    [Test]
    public void ShouldBeAbleToClickInASubFrame()
    {
        driver.Url = framesetPage;
        driver.SwitchTo().Frame("sixth").SwitchTo().Frame("iframe1");

        // This should replaxe frame "iframe1" inside frame "sixth" ...
        driver.FindElement(By.Id("submitButton")).Click();

        // driver should still be focused on frame "iframe1" inside frame "sixth" ...
        Assert.That(GetTextOfGreetingElement(), Is.EqualTo("Success!"));

        // Make sure it was really frame "iframe1" inside frame "sixth" which was replaced ...
        driver.SwitchTo().DefaultContent().SwitchTo().Frame("sixth").SwitchTo().Frame("iframe1");
        Assert.That(driver.FindElement(By.Id("greeting")).Text, Is.EqualTo("Success!"));
    }

    [Test]
    public void ShouldBeAbleToFindElementsInIframesByXPath()
    {
        driver.Url = iframePage;

        driver.SwitchTo().Frame("iframe1");

        IWebElement element = driver.FindElement(By.XPath("//*[@id = 'changeme']"));

        Assert.That(element, Is.Not.Null);
    }

    [Test]
    public void GetCurrentUrlShouldReturnTopLevelBrowsingContextUrl()
    {
        driver.Url = framesetPage;
        Assert.That(driver.Url, Is.EqualTo(framesetPage));

        driver.SwitchTo().Frame("second");
        Assert.That(driver.Url, Is.EqualTo(framesetPage));
    }

    [Test]
    public void GetCurrentUrlShouldReturnTopLevelBrowsingContextUrlForIframes()
    {
        driver.Url = iframePage;
        Assert.That(driver.Url, Is.EqualTo(iframePage));


        driver.SwitchTo().Frame("iframe1");
        Assert.That(driver.Url, Is.EqualTo(iframePage));
    }

    [Test]
    public void ShouldBeAbleToSwitchToTheTopIfTheFrameIsDeletedFromUnderUs()
    {
        driver.Url = deletingFrame;
        driver.SwitchTo().Frame("iframe1");

        IWebElement killIframe = driver.FindElement(By.Id("killIframe"));
        killIframe.Click();
        driver.SwitchTo().DefaultContent();

        AssertFrameNotPresent("iframe1");

        IWebElement addIFrame = driver.FindElement(By.Id("addBackFrame"));
        addIFrame.Click();

        WaitFor(() => driver.FindElement(By.Id("iframe1")), "Did not find frame element");

        driver.SwitchTo().Frame("iframe1");
        WaitFor(() => driver.FindElement(By.Id("success")), "Did not find element in frame");
    }

    [Test]
    public void ShouldBeAbleToSwitchToTheTopIfTheFrameIsDeletedFromUnderUsWithFrameIndex()
    {
        driver.Url = deletingFrame;
        int iframe = 0;
        WaitFor(() => FrameExistsAndSwitchedTo(iframe), "Did not switch to frame");

        // we should be in the frame now
        IWebElement killIframe = driver.FindElement(By.Id("killIframe"));
        killIframe.Click();
        driver.SwitchTo().DefaultContent();

        IWebElement addIFrame = driver.FindElement(By.Id("addBackFrame"));
        addIFrame.Click();
        WaitFor(() => FrameExistsAndSwitchedTo(iframe), "Did not switch to frame");

        WaitFor(() => driver.FindElement(By.Id("success")), "Did not find element in frame");
    }

    [Test]
    public void ShouldBeAbleToSwitchToTheTopIfTheFrameIsDeletedFromUnderUsWithWebelement()
    {
        driver.Url = deletingFrame;
        IWebElement iframe = driver.FindElement(By.Id("iframe1"));
        WaitFor(() => FrameExistsAndSwitchedTo(iframe), "Did not switch to frame");

        // we should be in the frame now
        IWebElement killIframe = driver.FindElement(By.Id("killIframe"));
        killIframe.Click();
        driver.SwitchTo().DefaultContent();

        IWebElement addIFrame = driver.FindElement(By.Id("addBackFrame"));
        addIFrame.Click();

        iframe = driver.FindElement(By.Id("iframe1"));
        WaitFor(() => FrameExistsAndSwitchedTo(iframe), "Did not switch to frame");
        WaitFor(() => driver.FindElement(By.Id("success")), "Did not find element in frame");
    }

    [Test]
    [IgnoreBrowser(Browser.Chrome, "Chrome driver throws NoSuchElementException")]
    [IgnoreBrowser(Browser.Edge, "Edge driver throws NoSuchElementException")]
    [IgnoreBrowser(Browser.IE, "IE driver throws NoSuchElementException")]
    public void ShouldNotBeAbleToDoAnythingTheFrameIsDeletedFromUnderUs()
    {
        driver.Url = deletingFrame;
        driver.SwitchTo().Frame("iframe1");

        IWebElement killIframe = driver.FindElement(By.Id("killIframe"));
        killIframe.Click();

        Assert.That(
            () => driver.FindElement(By.Id("killIframe")),
            Throws.TypeOf<NoSuchWindowException>());
    }

    [Test]
    public void ShouldReturnWindowTitleInAFrameset()
    {
        driver.Url = framesetPage;
        driver.SwitchTo().Frame("third");
        Assert.That(driver.Title, Is.EqualTo("Unique title"));
    }

    [Test]
    public void JavaScriptShouldExecuteInTheContextOfTheCurrentFrame()
    {
        IJavaScriptExecutor executor = driver as IJavaScriptExecutor;

        driver.Url = framesetPage;
        Assert.That((bool)executor.ExecuteScript("return window == window.top"), Is.True);

        driver.SwitchTo().Frame("third");
        Assert.That((bool)executor.ExecuteScript("return window != window.top"), Is.True);
    }

    [Test]
    public void ShouldNotSwitchMagicallyToTheTopWindow()
    {
        string baseUrl = EnvironmentManager.Instance.UrlBuilder.WhereIs("frame_switching_tests/");
        driver.Url = baseUrl + "bug4876.html";
        driver.SwitchTo().Frame(0);
        WaitFor(() => driver.FindElement(By.Id("inputText")), "Could not find element");

        for (int i = 0; i < 20; i++)
        {
            try
            {
                IWebElement input = WaitFor(() => driver.FindElement(By.Id("inputText")), "Did not find element");
                IWebElement submit = WaitFor(() => driver.FindElement(By.Id("submitButton")), "Did not find input element");
                input.Clear();
                input.SendKeys("rand" + new Random().Next());
                submit.Click();
            }
            finally
            {
                System.Threading.Thread.Sleep(100);
                string url = (string)((IJavaScriptExecutor)driver).ExecuteScript("return window.location.href");
                // IE6 and Chrome add "?"-symbol to the end of the URL
                if (url.EndsWith("?"))
                {
                    url = url.Substring(0, url.Length - 1);
                }
                Assert.That(url, Is.EqualTo(baseUrl + "bug4876_iframe.html"));
            }
        }
    }

    [Test]
    [NeedsFreshDriver(IsCreatedAfterTest = true)]
    public void GetShouldSwitchToDefaultContext()
    {
        driver.Url = iframePage;
        driver.SwitchTo().Frame(driver.FindElement(By.Id("iframe1")));
        driver.FindElement(By.Id("cheese")); // Found on formPage.html but not on iframes.html.

        driver.Url = iframePage; // This must effectively switchTo().defaultContent(), too.
        driver.FindElement(By.Id("iframe1"));
    }

    // ----------------------------------------------------------------------------------------------
    //
    // Frame handling behavior tests not included in Java tests
    //
    // ----------------------------------------------------------------------------------------------

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

        driver.SwitchTo().Frame("fifth");
        driver.FindElement(By.Id("username"));
    }

    [Test]
    [IgnoreBrowser(Browser.Firefox)]
    public void ShouldBeAbleToSelectAFrameByName()
    {
        driver.Url = framesetPage;

        driver.SwitchTo().Frame("second");
        Assert.That(driver.FindElement(By.Id("pageNumber")).Text, Is.EqualTo("2"));

        driver.SwitchTo().DefaultContent().SwitchTo().Frame("third");
        driver.FindElement(By.Id("changeme")).Click();

        driver.SwitchTo().DefaultContent().SwitchTo().Frame("second");
        Assert.That(driver.FindElement(By.Id("pageNumber")).Text, Is.EqualTo("2"));
    }

    [Test]
    public void ShouldBeAbleToFindElementsInIframesByName()
    {
        driver.Url = iframePage;

        driver.SwitchTo().Frame("iframe1");
        IWebElement element = driver.FindElement(By.Name("id-name1"));

        Assert.That(element, Is.Not.Null);
    }

    private string GetTextOfGreetingElement()
    {
        string text = string.Empty;
        DateTime end = DateTime.Now.Add(TimeSpan.FromMilliseconds(3000));
        while (DateTime.Now < end)
        {
            try
            {
                IWebElement element = driver.FindElement(By.Id("greeting"));
                text = element.Text;
                break;
            }
            catch (NoSuchElementException)
            {
            }
        }

        return text;
    }

    private void AssertFrameNotPresent(string locator)
    {
        driver.SwitchTo().DefaultContent();
        WaitFor(() => !FrameExistsAndSwitchedTo(locator), "Frame still present after timeout");
        driver.SwitchTo().DefaultContent();
    }

    private bool FrameExistsAndSwitchedTo(string locator)
    {
        try
        {
            driver.SwitchTo().Frame(locator);
            return true;
        }
        catch (NoSuchFrameException)
        {
            return false;
        }
    }

    private bool FrameExistsAndSwitchedTo(int index)
    {
        try
        {
            driver.SwitchTo().Frame(index);
            return true;
        }
        catch (NoSuchFrameException)
        {
            return false;
        }
    }

    private bool FrameExistsAndSwitchedTo(IWebElement frameElement)
    {
        try
        {
            driver.SwitchTo().Frame(frameElement);
            return true;
        }
        catch (NoSuchFrameException)
        {
            return false;
        }
    }
}
