From 075c2da0bb05d17680ef8c8920cc255365e64a17 Mon Sep 17 00:00:00 2001 From: knysh Date: Tue, 14 Apr 2020 16:44:53 +0300 Subject: [PATCH] Browser tab management +semver: feature (#172) * add BrowserTabNavigation with unuit tests * move js scripts to files add possibility to open url in new tab with test * #168 fix review issues * #168 fix review comments * #168 SwitchToTab -> SwitchToTab * #168 SwitchToTab -> SwitchToLastTab --- .../Aquality.Selenium.csproj | 2 + .../src/Aquality.Selenium/Browsers/Browser.cs | 9 + .../Browsers/BrowserTabNavigation.cs | 95 ++++++++++ .../Browsers/IBrowserTabNavigation.cs | 59 +++++++ .../Aquality.Selenium/Browsers/JavaScript.cs | 4 +- .../Resources/JavaScripts/OpenInNewTab.js | 1 + .../Resources/JavaScripts/OpenNewTab.js | 1 + .../Resources/Localization/en.json | 9 +- .../Resources/Localization/ru.json | 9 +- .../Integration/BrowserTabsTests.cs | 163 ++++++++++++++++++ .../TheInternet/Forms/TheInternetForm.cs | 7 + .../Resources/settings.local.json | 2 +- 12 files changed, 357 insertions(+), 4 deletions(-) create mode 100644 Aquality.Selenium/src/Aquality.Selenium/Browsers/BrowserTabNavigation.cs create mode 100644 Aquality.Selenium/src/Aquality.Selenium/Browsers/IBrowserTabNavigation.cs create mode 100644 Aquality.Selenium/src/Aquality.Selenium/Resources/JavaScripts/OpenInNewTab.js create mode 100644 Aquality.Selenium/src/Aquality.Selenium/Resources/JavaScripts/OpenNewTab.js create mode 100644 Aquality.Selenium/tests/Aquality.Selenium.Tests/Integration/BrowserTabsTests.cs diff --git a/Aquality.Selenium/src/Aquality.Selenium/Aquality.Selenium.csproj b/Aquality.Selenium/src/Aquality.Selenium/Aquality.Selenium.csproj index 25dd8f80..45a478d1 100644 --- a/Aquality.Selenium/src/Aquality.Selenium/Aquality.Selenium.csproj +++ b/Aquality.Selenium/src/Aquality.Selenium/Aquality.Selenium.csproj @@ -47,6 +47,7 @@ + @@ -56,6 +57,7 @@ + diff --git a/Aquality.Selenium/src/Aquality.Selenium/Browsers/Browser.cs b/Aquality.Selenium/src/Aquality.Selenium/Browsers/Browser.cs index 550f5f58..0f700c47 100644 --- a/Aquality.Selenium/src/Aquality.Selenium/Browsers/Browser.cs +++ b/Aquality.Selenium/src/Aquality.Selenium/Browsers/Browser.cs @@ -173,6 +173,15 @@ private INavigation Navigate() return new BrowserNavigation(Driver); } + /// + /// Provide interface to manage of browser tabs. + /// + /// instance of IBrowserTabNavigation. + public IBrowserTabNavigation Tabs() + { + return new BrowserTabNavigation(Driver); + } + /// /// Handles alert. /// diff --git a/Aquality.Selenium/src/Aquality.Selenium/Browsers/BrowserTabNavigation.cs b/Aquality.Selenium/src/Aquality.Selenium/Browsers/BrowserTabNavigation.cs new file mode 100644 index 00000000..24e0faa3 --- /dev/null +++ b/Aquality.Selenium/src/Aquality.Selenium/Browsers/BrowserTabNavigation.cs @@ -0,0 +1,95 @@ +using Aquality.Selenium.Core.Localization; +using OpenQA.Selenium.Remote; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Aquality.Selenium.Browsers +{ + public class BrowserTabNavigation : IBrowserTabNavigation + { + private readonly RemoteWebDriver driver; + + internal BrowserTabNavigation(RemoteWebDriver driver) + { + this.driver = driver; + } + + private ILocalizedLogger Logger => AqualityServices.LocalizedLogger; + + public string CurrentTabHandle + { + get + { + Logger.Info("loc.browser.get.tab.handle"); + return driver.CurrentWindowHandle; + } + } + + public IList TabHandles + { + get + { + Logger.Info("loc.browser.get.tab.handles"); + return driver.WindowHandles; + } + } + + public void CloseTab() + { + Logger.Info("loc.browser.tab.close"); + driver.Close(); + } + + public void OpenNewTab(bool switchToNew = true) + { + Logger.Info("loc.browser.tab.open.new"); + AqualityServices.Browser.ExecuteScript(JavaScript.OpenNewTab); + if (switchToNew) + { + SwitchToLastTab(); + } + } + + public void OpenInNewTab(string url) + { + AqualityServices.Browser.ExecuteScript(JavaScript.OpenInNewTab, url); + } + + public void SwitchToLastTab(bool closeCurrent = false) + { + Logger.Info("loc.browser.switch.to.new.tab"); + CloseAndSwitch(TabHandles.Last(), closeCurrent); + } + + public void SwitchToTab(string handle, bool closeCurrent = false) + { + Logger.Info("loc.browser.switch.to.tab.handle", handle); + CloseAndSwitch(handle, closeCurrent); + } + + public void SwitchToTab(int index, bool closeCurrent = false) + { + Logger.Info("loc.browser.switch.to.tab.index", index); + var names = TabHandles; + if (index < 0 || names.Count <= index) + { + throw new IndexOutOfRangeException( + $"Index of browser tab '{index}' you provided is out of range {0}..{names.Count}"); + } + + var newTab = names.ElementAt(index); + CloseAndSwitch(newTab, closeCurrent); + } + + private void CloseAndSwitch(string name, bool closeCurrent) + { + if (closeCurrent) + { + CloseTab(); + } + + driver.SwitchTo().Window(name); + } + } +} diff --git a/Aquality.Selenium/src/Aquality.Selenium/Browsers/IBrowserTabNavigation.cs b/Aquality.Selenium/src/Aquality.Selenium/Browsers/IBrowserTabNavigation.cs new file mode 100644 index 00000000..3d112918 --- /dev/null +++ b/Aquality.Selenium/src/Aquality.Selenium/Browsers/IBrowserTabNavigation.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; + +namespace Aquality.Selenium.Browsers +{ + /// + /// Provides functionality to work with browser tab navigation. + /// + public interface IBrowserTabNavigation + { + /// + /// Gets current tab handle. + /// + /// Current tab handle. + string CurrentTabHandle { get; } + + /// + /// Gets opened tab handles. + /// + /// List of tab handles. + IList TabHandles { get; } + + /// + /// Switches to tab. + /// + /// Tab handle. + /// Close current tab if true and leave it otherwise. + void SwitchToTab(string tabHandle, bool closeCurrent = false); + + /// + /// Switches to tab. + /// + /// Tab index. + /// Close current tab if true and leave it otherwise. + void SwitchToTab(int index, bool closeCurrent = false); + + /// + /// Switches to the last tab. + /// + /// Close current tab if true and leave it otherwise. + void SwitchToLastTab(bool closeCurrent = false); + + /// + /// Closes curent tab. + /// + void CloseTab(); + + /// + /// Opens new tab. + /// + /// Switches to new tab if true and stays at current otherwise. + void OpenNewTab(bool switchToNew = true); + + /// + /// Navigates to desired url in new tab. + /// + /// String representation of URL. + void OpenInNewTab(string url); + } +} diff --git a/Aquality.Selenium/src/Aquality.Selenium/Browsers/JavaScript.cs b/Aquality.Selenium/src/Aquality.Selenium/Browsers/JavaScript.cs index 8fc4c7b9..e1f08583 100644 --- a/Aquality.Selenium/src/Aquality.Selenium/Browsers/JavaScript.cs +++ b/Aquality.Selenium/src/Aquality.Selenium/Browsers/JavaScript.cs @@ -31,7 +31,9 @@ public enum JavaScript SetFocus, SetInnerHTML, SetValue, - GetViewPortCoordinates + GetViewPortCoordinates, + OpenNewTab, + OpenInNewTab } /// diff --git a/Aquality.Selenium/src/Aquality.Selenium/Resources/JavaScripts/OpenInNewTab.js b/Aquality.Selenium/src/Aquality.Selenium/Resources/JavaScripts/OpenInNewTab.js new file mode 100644 index 00000000..8462fe6e --- /dev/null +++ b/Aquality.Selenium/src/Aquality.Selenium/Resources/JavaScripts/OpenInNewTab.js @@ -0,0 +1 @@ +window.open(arguments[0]); \ No newline at end of file diff --git a/Aquality.Selenium/src/Aquality.Selenium/Resources/JavaScripts/OpenNewTab.js b/Aquality.Selenium/src/Aquality.Selenium/Resources/JavaScripts/OpenNewTab.js new file mode 100644 index 00000000..89c08337 --- /dev/null +++ b/Aquality.Selenium/src/Aquality.Selenium/Resources/JavaScripts/OpenNewTab.js @@ -0,0 +1 @@ +window.open(); diff --git a/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/en.json b/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/en.json index 38544c81..d1f5c9fa 100644 --- a/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/en.json +++ b/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/en.json @@ -57,5 +57,12 @@ "loc.waitinvisible": "Wait until element is not visible", "loc.waitnotexists": "Wait until element does not exist in DOM during {0} seconds", "loc.no.elements.found.in.state": "No elements with locator '{0}' found in {1} state", - "loc.elements.were.found.but.not.in.state": "Elements were found by locator '{0}'. But {1}" + "loc.elements.were.found.but.not.in.state": "Elements were found by locator '{0}'. But {1}", + "loc.browser.switch.to.tab.handle": "Switching to tab by handle '{0}'", + "loc.browser.switch.to.tab.index": "Switching to tab by index '{0}'", + "loc.browser.switch.to.new.tab": "Switching to new tab", + "loc.browser.get.tab.handles": "Getting tab handles", + "loc.browser.get.tab.handle": "Getting current tab handle", + "loc.browser.tab.open.new": "Opening new tab", + "loc.browser.tab.close": "Closing tab" } diff --git a/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/ru.json b/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/ru.json index 25e7be8b..83361fc4 100644 --- a/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/ru.json +++ b/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/ru.json @@ -56,5 +56,12 @@ "loc.waitinvisible": "Îæèäàåì ïîêà ýëåìåíò èñ÷åçíåò", "loc.waitnotexists": "Îæèäàåì èñ÷åçíîâåíèÿ ýëåìåíòà èç DOM â òå÷åíèè {0}", "loc.no.elements.found.in.state": "Íå óäàëîñü íàéòè ýëåìåíòîâ ïî ëîêàòîðó '{0}' â {1} ñîñòîÿíèè", - "loc.elements.were.found.but.not.in.state": "Óäàëîñü íàéòè ýëåìåíòû ïî ëîêàòîðó '{0}'. Íî {1}" + "loc.elements.were.found.but.not.in.state": "Óäàëîñü íàéòè ýëåìåíòû ïî ëîêàòîðó '{0}'. Íî {1}", + "loc.browser.switch.to.tab.handle": "Ïåðåêëþ÷åíèå íà íîâóþ âêëàäêó ïî äåñêðèïòîðó '{0}'", + "loc.browser.switch.to.tab.index": "Ïåðåêëþ÷åíèå íà íîâóþ âêëàäêó ïî èíäåêñó '{0}'", + "loc.browser.switch.to.new.tab": "Ïåðåêëþ÷åíèå íà íîâóþ âêëàäêó", + "loc.browser.get.tab.handles": "Ïîëó÷åíèå ñïèñêà äåñêðèïòîðîâ îòêðûòûõ âêëàäîê", + "loc.browser.get.tab.handle": "Ïîëó÷åíèå äåñêðèïòîðà òåêóùåé âêëàäêè", + "loc.browser.tab.open.new": "Îòêðûòèå íîâîé âêëàäêè", + "loc.browser.tab.close": "Çàêðûòèå âêëàäêè" } \ No newline at end of file diff --git a/Aquality.Selenium/tests/Aquality.Selenium.Tests/Integration/BrowserTabsTests.cs b/Aquality.Selenium/tests/Aquality.Selenium.Tests/Integration/BrowserTabsTests.cs new file mode 100644 index 00000000..53dc3393 --- /dev/null +++ b/Aquality.Selenium/tests/Aquality.Selenium.Tests/Integration/BrowserTabsTests.cs @@ -0,0 +1,163 @@ +using System; +using Aquality.Selenium.Browsers; +using Aquality.Selenium.Tests.Integration.TestApp.TheInternet.Forms; +using NUnit.Framework; +using System.Linq; + +namespace Aquality.Selenium.Tests.Integration +{ + internal class BrowserTabsTests : UITest + { + private readonly WelcomeForm WelcomeForm = new WelcomeForm(); + + [SetUp] + public void Before() + { + AqualityServices.Browser.GoTo(WelcomeForm.Url); + } + + [Test] + public void Should_BePossibleTo_OpenUrlInNewTab() + { + var url = new WelcomeForm().Url; + var browser = AqualityServices.Browser; + browser.Tabs().OpenInNewTab(url); + browser.Tabs().SwitchToLastTab(); + Assert.AreEqual(2, browser.Tabs().TabHandles.Count); + Assert.AreEqual(browser.Driver.Url, url); + } + + [Test] + public void Should_BePossibleTo_HandleTab() + { + var browser = AqualityServices.Browser; + var tabHandle = browser.Tabs().CurrentTabHandle; + Assert.IsNotEmpty(tabHandle, "Tab name should not be empty"); + } + + [Test] + public void Should_BePossibleTo_GetTabHandles() + { + var browser = AqualityServices.Browser; + var tabHandles = browser.Tabs().TabHandles; + Assert.AreEqual(1, tabHandles.Count, "Tab number should be correct"); + Assert.IsNotEmpty(tabHandles.First(), "Tab handle should not be empty"); + } + + [Test] + public void Should_BePossibleTo_OpenNewTab() + { + var browser = AqualityServices.Browser; + var tabHandle = browser.Tabs().CurrentTabHandle; + + browser.Tabs().OpenNewTab(); + var newTabHandle = browser.Tabs().CurrentTabHandle; + Assert.AreEqual(2, browser.Tabs().TabHandles.Count, "New tab should be opened"); + Assert.AreNotEqual(tabHandle, newTabHandle, "Browser should be switched to new tab"); + + browser.Tabs().OpenNewTab(false); + Assert.AreEqual(3, browser.Tabs().TabHandles.Count, "New tab should be opened"); + Assert.AreEqual(newTabHandle, browser.Tabs().CurrentTabHandle, "Browser should not be switched to new tab"); + } + + [Test] + public void Should_BePossibleTo_CloseTab() + { + var browser = AqualityServices.Browser; + WelcomeForm.ClickElementalSelenium(); + Assert.AreEqual(2, browser.Tabs().TabHandles.Count, "New tab should be opened"); + browser.Tabs().CloseTab(); + Assert.AreEqual(1, browser.Tabs().TabHandles.Count, "New tab should be closed"); + } + + [Test] + public void Should_BePossibleTo_SwitchToNewTab() + { + CheckSwitchingBy(2, () => + { + AqualityServices.Browser.Tabs().SwitchToLastTab(); + }); + } + + [Test] + public void Should_BePossibleTo_SwitchToNewTab_AndClose() + { + CheckSwitchingBy(1, () => + { + AqualityServices.Browser.Tabs().SwitchToLastTab(true); + }); + } + + [Test] + public void Should_BePossibleTo_SwitchToNewTabByHandle() + { + CheckSwitchingBy(3, () => + { + var browser = AqualityServices.Browser; + var tabHandle = browser.Tabs().TabHandles.Last(); + browser.Tabs().OpenNewTab(false); + browser.Tabs().SwitchToTab(tabHandle); + }); + } + + [Test] + public void Should_BePossibleTo_SwitchToNewTabByHandle_AndClose() + { + CheckSwitchingBy(2, () => + { + var browser = AqualityServices.Browser; + var tabHandle = browser.Tabs().TabHandles.Last(); + browser.Tabs().OpenNewTab(false); + browser.Tabs().SwitchToTab(tabHandle, true); + }); + } + + [Test] + public void Should_BePossibleTo_SwitchToNewTabByIndex() + { + CheckSwitchingBy(3, () => + { + AqualityServices.Browser.Tabs().OpenNewTab(false); + AqualityServices.Browser.Tabs().SwitchToTab(1); + }); + } + + [Test] + public void Should_BePossibleTo_SwitchToNewTabByIndex_AndClose() + { + CheckSwitchingBy(2, () => + { + AqualityServices.Browser.Tabs().OpenNewTab(false); + AqualityServices.Browser.Tabs().SwitchToTab(1, true); + }); + } + + [Test] + public void Should_BeThrow_IfSwitchToNewTab_ByIncorrectIndex() + { + Assert.Throws(typeof(IndexOutOfRangeException), () => { AqualityServices.Browser.Tabs().SwitchToTab(10, true); }); + } + + private void CheckSwitchingBy(int expectedTabCount, Action switchMethod) + { + var browser = AqualityServices.Browser; + var tabHandle = browser.Tabs().CurrentTabHandle; + WelcomeForm.ClickElementalSelenium(); + var newTabHandle = browser.Tabs().TabHandles.Last(); + switchMethod.Invoke(); + Assert.AreEqual(newTabHandle, browser.Tabs().CurrentTabHandle, "Browser should be switched to correct tab"); + Assert.AreEqual(expectedTabCount, browser.Tabs().TabHandles.Count, "Number of tabs should be correct"); + } + + private void CheckSwitching(int expectedTabCount, Action switchMethod) + { + var browser = AqualityServices.Browser; + var tabHandle = browser.Tabs().CurrentTabHandle; + WelcomeForm.ClickElementalSelenium(); + switchMethod.Invoke(); + var newTabHandle = browser.Tabs().CurrentTabHandle; + Assert.AreNotEqual(tabHandle, newTabHandle, "Browser should be switched to new tab"); + Assert.AreEqual(expectedTabCount, browser.Tabs().TabHandles.Count, "Number of tabs should be correct"); + } + } +} diff --git a/Aquality.Selenium/tests/Aquality.Selenium.Tests/Integration/TestApp/TheInternet/Forms/TheInternetForm.cs b/Aquality.Selenium/tests/Aquality.Selenium.Tests/Integration/TestApp/TheInternet/Forms/TheInternetForm.cs index 376ea707..73f88eab 100644 --- a/Aquality.Selenium/tests/Aquality.Selenium.Tests/Integration/TestApp/TheInternet/Forms/TheInternetForm.cs +++ b/Aquality.Selenium/tests/Aquality.Selenium.Tests/Integration/TestApp/TheInternet/Forms/TheInternetForm.cs @@ -1,4 +1,5 @@ using Aquality.Selenium.Browsers; +using Aquality.Selenium.Elements.Interfaces; using Aquality.Selenium.Forms; using OpenQA.Selenium; @@ -6,6 +7,7 @@ namespace Aquality.Selenium.Tests.Integration.TestApp.TheInternet.Forms { internal abstract class TheInternetForm : Form { + private ILink ElementalSeleniumLink => ElementFactory.GetLink(By.XPath("//a[contains(@href,'elementalselenium')]"), "Elemental Selenium"); private const string BaseUrl = "http://the-internet.herokuapp.com/"; protected TheInternetForm(By locator, string name) : base(locator, name) @@ -21,5 +23,10 @@ public void Open() AqualityServices.Browser.GoTo(Url); AqualityServices.Browser.WaitForPageToLoad(); } + + public void ClickElementalSelenium() + { + ElementalSeleniumLink.Click(); + } } } diff --git a/Aquality.Selenium/tests/Aquality.Selenium.Tests/Resources/settings.local.json b/Aquality.Selenium/tests/Aquality.Selenium.Tests/Resources/settings.local.json index 8eb1febb..7d87faec 100644 --- a/Aquality.Selenium/tests/Aquality.Selenium.Tests/Resources/settings.local.json +++ b/Aquality.Selenium/tests/Aquality.Selenium.Tests/Resources/settings.local.json @@ -6,7 +6,7 @@ "driverSettings": { "chrome": { - "webDriverVersion": "Latest", + "webDriverVersion": "80.0.3987.106", "capabilities": { "enableVNC": true },