diff --git a/src/Jackett.IntegrationTests/DashboardTests.cs b/src/Jackett.IntegrationTests/DashboardTests.cs new file mode 100644 index 000000000..4a9586613 --- /dev/null +++ b/src/Jackett.IntegrationTests/DashboardTests.cs @@ -0,0 +1,110 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenQA.Selenium; +using OpenQA.Selenium.Support.UI; + +namespace Jackett.IntegrationTests +{ + [TestClass] + public class DashboardTests + { + [TestMethod] + public void CheckTitle() + { + string pageTitle = _webDriver.Title; + Assert.AreEqual("Jackett", pageTitle); + } + + [TestMethod] + public void DefaultPortShown() + { + string port = _webDriver.FindElement(By.CssSelector("#jackett-port")).GetAttribute("value"); + Assert.AreEqual("9117", port); + } + + [TestMethod] + public void IndexerTableIsPresent() + { + IWebElement table = _webDriver.FindElement(By.CssSelector("#configured-indexer-datatable")); + Assert.IsNotNull(table); + } + + [TestMethod] + public void AddIndexerOpens() + { + _webDriver.FindElement(By.CssSelector("#jackett-add-indexer")).Click(); + WaitUntilModalIsDisplayed("#select-indexer-modal h4"); + string modalHeading = _webDriver.FindElement(By.CssSelector("#select-indexer-modal h4")).Text; + Assert.AreEqual("Select an indexer to setup", modalHeading); + } + + [TestMethod] + public void CheckIndexersAreAvailableToAdd() + { + _webDriver.FindElement(By.CssSelector("#jackett-add-indexer")).Click(); + WaitUntilModalIsDisplayed("#select-indexer-modal h4"); + int indexerCount = _webDriver.FindElements(By.CssSelector("#unconfigured-indexer-datatable tbody tr")).Count; + Assert.IsTrue(indexerCount > 400); + } + + [TestMethod] + public void ManualSearchOpens() + { + _webDriver.FindElement(By.CssSelector("#jackett-show-search")).Click(); + WaitUntilModalIsDisplayed("#select-indexer-modal div.modal-body p"); + string modalDescription = _webDriver.FindElement(By.CssSelector("#select-indexer-modal div.modal-body p")).Text; + Assert.AreEqual("You can search all configured indexers from this screen.", modalDescription); + } + + + [ClassInitialize] + public static void StartBrowser(TestContext testContext) + { + _webDriver = WebDriverFactory.CreateFromEnvironmentVariableSettings(); + _webDriver.Manage().Timeouts().AsynchronousJavaScript = TimeSpan.FromSeconds(20); + _webDriver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(20); + _webDriver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(20); + } + + [ClassCleanup] + public static void StopBrowser() + { + _webDriver?.Quit(); + } + + [TestInitialize] + public void LoadDashboard() + { + string url = "http://localhost:9117/UI/Dashboard"; + Console.WriteLine($"Url for test: {url}"); + _webDriver.Navigate().GoToUrl(url); + + WebDriverWait wait = new WebDriverWait(_webDriver, TimeSpan.FromSeconds(20)); + IWebElement element = wait.Until(x => x.FindElement(By.CssSelector("#configured-indexer-datatable"))); + } + + private bool WaitUntilModalIsDisplayed(string cssSelector) + { + WebDriverWait wait = new WebDriverWait(_webDriver, TimeSpan.FromSeconds(5)); + var element = wait.Until(condition => + { + try + { + var elementToBeDisplayed = _webDriver.FindElement(By.CssSelector(cssSelector)); + return elementToBeDisplayed.Displayed; + } + catch (StaleElementReferenceException) + { + return false; + } + catch (NoSuchElementException) + { + return false; + } + }); + return false; + } + + private static IWebDriver _webDriver; + } +} diff --git a/src/Jackett.IntegrationTests/Jackett.IntegrationTests.csproj b/src/Jackett.IntegrationTests/Jackett.IntegrationTests.csproj new file mode 100644 index 000000000..135cc561a --- /dev/null +++ b/src/Jackett.IntegrationTests/Jackett.IntegrationTests.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp3.1 + false + + + + + + + + + + + + diff --git a/src/Jackett.IntegrationTests/WebDriverFactory.cs b/src/Jackett.IntegrationTests/WebDriverFactory.cs new file mode 100644 index 000000000..2013da0e1 --- /dev/null +++ b/src/Jackett.IntegrationTests/WebDriverFactory.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using OpenQA.Selenium; +using OpenQA.Selenium.Chrome; +using System; + +namespace Jackett.IntegrationTests +{ + // This factory class is just one example of how you might initialize Selenium. If your project already has its own means + // of initializing Selenium, you can keep using that as-is and ignore this sample file; skip ahead to SamplePageTests.cs. + public static class WebDriverFactory + { + public static IWebDriver CreateFromEnvironmentVariableSettings() { + // This environment variable gets set by our Azure Pipelines build definition in ./azure-pipelines.yml. + // That file uses a matrix strategy to run multiple different build jobs for different combinations of OS/browser. + // Each job sets this environment variable accordingly, and we use it to decide which browser the tests will use. + const string BROWSER_ENVIRONMENT_VARIABLE = "SELENIUM_BROWSER"; + var browserEnvVar = Environment.GetEnvironmentVariable(BROWSER_ENVIRONMENT_VARIABLE); + + // It's convenient to have a default to make it easier for a developer to run a plain "dotnet test" command. + // In our CI builds in Azure Pipelines, we'll always specify the browser explicitly instead of using this. + const string DEFAULT_BROWSER = "chrome"; + + // Headless browsers generally use small window sizes by default. Some of the axe checks require + // elements to be present in the viewport to be assessable, so using a viewport size based on your + // most common usage is a good idea to prevent axe from having to do extra work to scroll items into + // view and avoid issues with elements not fitting into the viewport. + const int windowWidth = 1920; + const int windowHeight = 1080; + + switch (browserEnvVar ?? DEFAULT_BROWSER) + { + case "chrome": + // The ChromeWebDriver environment variable comes pre-set in the Azure Pipelines VM Image we + // specify in azure-pipelines.yml. ChromeDriver requires that you use *matching* versions of Chrome and + // the Chrome WebDriver; in the build agent VMs, using this environment variable will make sure that we use + // the pre-installed version of ChromeDriver that matches the pre-installed version of Chrome. + // + // See https://docs.microsoft.com/en-us/azure/devops/pipelines/test/continuous-test-selenium + // + // Environment.CurrentDirectory is where the Selenium.Webdriver.ChromeDriver NuGet package will place the + // version of the Chrome WebDriver that it comes with. We fall back to this so that if a developer working on + // the project hasn't separately installed ChromeDriver, the test will still be able to run on their machine. + var chromeDriverDirectory = Environment.GetEnvironmentVariable("CHROMEWEBDRIVER") ?? Environment.CurrentDirectory; + + // The tests will work fine in non-headless mode; we recommend using --headless for performance and + // because it makes it easier to run the tests in non-graphical environments (eg, most Docker containers) + var chromeOptions = new ChromeOptions(); + chromeOptions.AddArgument("--headless"); + chromeOptions.AddArgument($"--window-size={windowWidth},{windowHeight}"); + + return new ChromeDriver(chromeDriverDirectory, chromeOptions); + + default: + throw new ArgumentException($"Unknown browser type '{browserEnvVar}' specified in '{BROWSER_ENVIRONMENT_VARIABLE}' environment variable"); + } + } + } +} diff --git a/src/Jackett.sln b/src/Jackett.sln index ae58a2ad8..5ba97758d 100644 --- a/src/Jackett.sln +++ b/src/Jackett.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2008 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29519.181 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BE7B0C8A-6144-47CD-821E-B09BA1B7BADE}" ProjectSection(SolutionItems) = preProject @@ -11,9 +11,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\README.md = ..\README.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jackett.Service", "Jackett.Service\Jackett.Service.csproj", "{BF611F7B-4658-4CB8-AA9E-0736FADAA3BA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jackett.Service", "Jackett.Service\Jackett.Service.csproj", "{BF611F7B-4658-4CB8-AA9E-0736FADAA3BA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jackett.Tray", "Jackett.Tray\Jackett.Tray.csproj", "{FF9025B1-EC14-4AA9-8081-9F69C5E35B63}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jackett.Tray", "Jackett.Tray\Jackett.Tray.csproj", "{FF9025B1-EC14-4AA9-8081-9F69C5E35B63}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jackett.Updater", "Jackett.Updater\Jackett.Updater.csproj", "{A61E311A-6F8B-4497-B5E4-2EA8994C7BD8}" EndProject @@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jackett.Common", "Jackett.C EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jackett.Server", "Jackett.Server\Jackett.Server.csproj", "{84182782-EDBC-4342-ADA6-72B7694D0862}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jackett.IntegrationTests", "Jackett.IntegrationTests\Jackett.IntegrationTests.csproj", "{0250DEAA-ED2E-4F72-BE76-D92D80B40080}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -59,6 +61,10 @@ Global {84182782-EDBC-4342-ADA6-72B7694D0862}.Debug|Any CPU.Build.0 = Debug|Any CPU {84182782-EDBC-4342-ADA6-72B7694D0862}.Release|Any CPU.ActiveCfg = Release|Any CPU {84182782-EDBC-4342-ADA6-72B7694D0862}.Release|Any CPU.Build.0 = Release|Any CPU + {0250DEAA-ED2E-4F72-BE76-D92D80B40080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0250DEAA-ED2E-4F72-BE76-D92D80B40080}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0250DEAA-ED2E-4F72-BE76-D92D80B40080}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0250DEAA-ED2E-4F72-BE76-D92D80B40080}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE