From 9eef71b865d770cbedfd7990817165031a118da9 Mon Sep 17 00:00:00 2001 From: Matthew Bongiovi Date: Wed, 25 Apr 2018 17:01:30 -0700 Subject: [PATCH] Infrastructure --- .gitignore | 3 + AdfsUITestManager/AdfsUITestManager.sln | 22 ++ .../AdfsUITestManager.csproj | 95 +++++++ .../BrowserStackLogReader.cs | 163 +++++++++++ .../AdfsUITestManager/DriverFactory.cs | 88 ++++++ .../AdfsUITestManager/Manager.cs | 52 ++++ .../Properties/AssemblyInfo.cs | 36 +++ .../ScreenShotRemoteWebDriver.cs | 25 ++ .../AdfsUITestManager/TaskConfiguration.cs | 255 ++++++++++++++++++ .../TaskConfigurationFactory.cs | 15 ++ .../Tasks/ExternalAuthTasks.cs | 36 +++ .../AdfsUITestManager/Tasks/FormsPageTasks.cs | 82 ++++++ .../AdfsUITestManager/Tasks/IdpPageTasks.cs | 41 +++ .../Tasks/OptionsPageTasks.cs | 52 ++++ .../Tasks/PaginatedFormsTasks.cs | 44 +++ .../Tasks/RelyingPartyTasks.cs | 28 ++ .../AdfsUITestManager/Tasks/TaskBase.cs | 22 ++ .../AdfsUITestManager/TestCases.cs | 237 ++++++++++++++++ .../AdfsUITestManager/TestContext.cs | 71 +++++ .../TestListConfiguration.cs | 47 ++++ .../AdfsUITestManager/config.json | 0 .../AdfsUITestManager/packages.config | 7 + README.md | 132 ++++++++- 23 files changed, 1551 insertions(+), 2 deletions(-) create mode 100644 AdfsUITestManager/AdfsUITestManager.sln create mode 100644 AdfsUITestManager/AdfsUITestManager/AdfsUITestManager.csproj create mode 100644 AdfsUITestManager/AdfsUITestManager/BrowserStackLogReader.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/DriverFactory.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/Manager.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/Properties/AssemblyInfo.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/ScreenShotRemoteWebDriver.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/TaskConfiguration.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/TaskConfigurationFactory.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/Tasks/ExternalAuthTasks.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/Tasks/FormsPageTasks.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/Tasks/IdpPageTasks.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/Tasks/OptionsPageTasks.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/Tasks/PaginatedFormsTasks.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/Tasks/RelyingPartyTasks.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/Tasks/TaskBase.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/TestCases.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/TestContext.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/TestListConfiguration.cs create mode 100644 AdfsUITestManager/AdfsUITestManager/config.json create mode 100644 AdfsUITestManager/AdfsUITestManager/packages.config diff --git a/.gitignore b/.gitignore index 940794e..f7ac18e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ *.userosscache *.sln.docstates +# ALWAYS ignore the credential configuration, so nobody checks in their API keys +App.config + # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/AdfsUITestManager/AdfsUITestManager.sln b/AdfsUITestManager/AdfsUITestManager.sln new file mode 100644 index 0000000..4ff348b --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdfsUITestManager", "AdfsUITestManager\AdfsUITestManager.csproj", "{540AFC72-5649-4ECF-B4D4-1273CE070504}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {540AFC72-5649-4ECF-B4D4-1273CE070504}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {540AFC72-5649-4ECF-B4D4-1273CE070504}.Debug|Any CPU.Build.0 = Debug|Any CPU + {540AFC72-5649-4ECF-B4D4-1273CE070504}.Release|Any CPU.ActiveCfg = Release|Any CPU + {540AFC72-5649-4ECF-B4D4-1273CE070504}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AdfsUITestManager/AdfsUITestManager/AdfsUITestManager.csproj b/AdfsUITestManager/AdfsUITestManager/AdfsUITestManager.csproj new file mode 100644 index 0000000..bdb345e --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/AdfsUITestManager.csproj @@ -0,0 +1,95 @@ + + + + + Debug + AnyCPU + {540AFC72-5649-4ECF-B4D4-1273CE070504} + Exe + Properties + AdfsUITestManager + AdfsUITestManager + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\Selenium.WebDriverBackedSelenium.3.11.0\lib\net45\Selenium.WebDriverBackedSelenium.dll + True + + + + + + + + + + + + + ..\packages\Selenium.WebDriver.3.11.0\lib\net45\WebDriver.dll + True + + + ..\packages\Selenium.Support.3.11.0\lib\net45\WebDriver.Support.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AdfsUITestManager/AdfsUITestManager/BrowserStackLogReader.cs b/AdfsUITestManager/AdfsUITestManager/BrowserStackLogReader.cs new file mode 100644 index 0000000..466d2e0 --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/BrowserStackLogReader.cs @@ -0,0 +1,163 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using System; +using System.Configuration; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Linq; + +namespace AdfsUITestManager +{ + public enum TestStatus + { + Passed, + Failed + } + + public class MarkTestData + { + public string status { get; set; } + } + + public enum BrowserStackDataType + { + Build, + Session + } + + class BrowserStackLogReader + { + public static NetworkCredential AuthCredential = new NetworkCredential( ConfigurationManager.AppSettings[ "BrowserStackUser" ], ConfigurationManager.AppSettings[ "BrowserStackKey" ] ); + + /// + /// BrowserStack API supports build and session queries for a given user. + /// This method retrieves all builds or sessions, and then searches for the ID of the matching session/build by name + /// + /// + /// + /// + /// + private static string GetIdFromName( string name, string url, BrowserStackDataType dataType ) + { + WebResponse response = null; + StreamReader reader = null; + // BrowserStack doesn't allow hyphens in remote names, and removes them silently + name = name.Replace( '-', ' ' ); + + try + { + WebRequest request = WebRequest.Create( url ); + request.Credentials = AuthCredential; + response = request.GetResponse(); + Stream dataStream = response.GetResponseStream(); + reader = new StreamReader( dataStream ); + string responseFromServer = reader.ReadToEnd(); + string data = "{\"data\": " + responseFromServer + "}"; + var jsonData = JObject.Parse( data ); + + // Iterate through builds until you find one that matches our build name + for ( int i = 0; i < jsonData[ "data" ].Count(); i++ ) + { + string automationElement = "automation_session"; + if ( dataType == BrowserStackDataType.Build ) + { + automationElement = "automation_build"; + } + var nameRemote = ( string )jsonData[ "data" ][ i ][ automationElement ][ "name" ]; + + if ( nameRemote.Equals( name ) ) + { + return ( string )jsonData[ "data" ][ i ][ automationElement ][ "hashed_id" ]; + } + } + } + catch ( Exception e ) + { + Console.WriteLine( "Error while trying to call BrowserStack." ); + Console.WriteLine( e.Message ); + Console.WriteLine( e.StackTrace ); + } + finally + { + reader?.Close(); + response?.Close(); + } + + return null; + } + + /// + /// Locates the BrowserStack session ID for a given build and session name. + /// + /// + /// + /// + public static string GetSessionId(string buildName, string sessionName) + { + var buildId = GetIdFromName( buildName, $"https://api.browserstack.com/automate/builds.json", BrowserStackDataType.Build ); + + if ( string.IsNullOrEmpty( buildId ) ) + { + Console.WriteLine("Error getting build ID. No matching build ID found."); + return null; + } + + var sessionId = GetIdFromName( sessionName, $"https://api.browserstack.com/automate/builds/{buildId}/sessions.json", BrowserStackDataType.Session ); + if ( string.IsNullOrEmpty( sessionId ) ) + { + Console.WriteLine( "Error getting session ID. No matching session ID found." ); + return null; + } + + return sessionId; + } + + /// + /// Marks a test corresponding to the given session ID with the test status supplied. + /// This method is used to mark tests as "pass" or "fail", based on what occurs in the ADFS environment. + /// + /// + /// + public static void MarkTest( string sessionId, TestStatus status) + { + try + { + string url = $"https://api.browserstack.com/"; + var handler = new HttpClientHandler { Credentials = AuthCredential }; + HttpClient client = new HttpClient( handler ); + client.BaseAddress = new Uri( url ); + + // Create the data to PUT + MarkTestData data = new MarkTestData(); + data.status = status.ToString(); + var output = JsonConvert.SerializeObject( data ); + + var inputMessage = new HttpRequestMessage + { + Content = new StringContent( output, Encoding.UTF8, "application/json" ) + }; + inputMessage.Headers.Accept.Add( new MediaTypeWithQualityHeaderValue( "application/json" ) ); + + HttpResponseMessage message = client.PutAsync( $"automate/sessions/{sessionId}.json", inputMessage.Content ).Result; + + if ( message.IsSuccessStatusCode ) + { + Console.WriteLine( "Test marked successfully" ); + } + } + catch ( Exception e ) + { + Console.WriteLine( $"Failed to mark test {sessionId} with status {status}." ); + Console.WriteLine( e.Message ); + Console.WriteLine( e.StackTrace ); + } + } + } +} diff --git a/AdfsUITestManager/AdfsUITestManager/DriverFactory.cs b/AdfsUITestManager/AdfsUITestManager/DriverFactory.cs new file mode 100644 index 0000000..47cff29 --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/DriverFactory.cs @@ -0,0 +1,88 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using OpenQA.Selenium.Remote; +using System.Configuration; + +namespace AdfsUITestManager +{ + using System; + + using OpenQA.Selenium; + + /// + /// Class responsible for creating web drivers to be used for UI testing + /// + class DriverFactory + { + static readonly string RemoteDriverUri = ConfigurationManager.AppSettings[ "RemoteDriverUri" ]; + + public static IWebDriver GenerateDriver( DesiredCapabilities capability ) + { + var driver = new RemoteWebDriver( + new Uri( RemoteDriverUri ), capability + ); + + driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds( 10 ); + + return driver; + } + + public static void AddCommonCapabilities( DesiredCapabilities capability ) + { + var user = ConfigurationManager.AppSettings[ "BrowserStackUser" ]; + var key = ConfigurationManager.AppSettings[ "BrowserStackKey" ]; + + var sessionName = $"AD FS {DateTime.UtcNow.ToFileTimeUtc()}"; + var buildName = $"AD FS Build {DateTime.UtcNow.ToString("yyyy-MM-dd")}"; + + capability.SetCapability( "browserstack.local", "true" ); + capability.SetCapability( "browserstack.user", user ); + capability.SetCapability( "browserstack.key", key ); + capability.SetCapability( "browserstack.debug", "true" ); + capability.SetCapability( "resolution", "1024x768" ); + capability.SetCapability( "build", buildName ); + capability.SetCapability( "name", sessionName ); + } + + public static DesiredCapabilities GetChromeDriverCapabilities() + { + DesiredCapabilities capability = new DesiredCapabilities(); + capability.SetCapability( "browser", "Chrome" ); + capability.SetCapability( "browser_version", "62.0" ); + capability.SetCapability( "os", "Windows" ); + capability.SetCapability( "os_version", "10" ); + capability.SetCapability( "ignore-certificate", true ); + + AddCommonCapabilities( capability ); + + return capability; + } + + public static DesiredCapabilities GetFirefoxDriverCapabilities() + { + DesiredCapabilities capability = new DesiredCapabilities(); + capability.SetCapability( "os", "Windows" ); + capability.SetCapability( "os_version", "10" ); + capability.SetCapability( "browser", "Firefox" ); + capability.SetCapability( "browser_version", "58.0" ); + capability.SetCapability( "acceptInsecureCerts", true ); + + AddCommonCapabilities( capability ); + return capability; + } + + public static DesiredCapabilities GetIEDriverCapabilities() + { + DesiredCapabilities capability = new DesiredCapabilities(); + capability.SetCapability( "os", "Windows" ); + capability.SetCapability( "os_version", "10" ); + capability.SetCapability( "browser", "IE" ); + capability.SetCapability( "browser_version", "11.0" ); + + AddCommonCapabilities( capability ); + return capability; + } + } +} diff --git a/AdfsUITestManager/AdfsUITestManager/Manager.cs b/AdfsUITestManager/AdfsUITestManager/Manager.cs new file mode 100644 index 0000000..6a8ec02 --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/Manager.cs @@ -0,0 +1,52 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Reflection; +using OpenQA.Selenium; +using OpenQA.Selenium.Remote; +namespace AdfsUITestManager +{ + class Manager + { + static void Main( string[] args ) + { + // Contract: + // .\BrowserStackTestManager.exe + + TestContext context = new TestContext(); + InvokeTests( context.DriversPerTestCase, context.Configuration ); + Console.WriteLine( "Done" ); + } + + private static void InvokeTests( Dictionary> driversPerTest, TaskConfiguration config ) + { + foreach ( var test in driversPerTest.Keys ) + { + foreach ( var driverData in driversPerTest[ test] ) + { + IWebDriver driver = DriverFactory.GenerateDriver( driverData ); + string sessionName = ( string )driverData.GetCapability( "name" ); + string buildName = ( string )driverData.GetCapability( "build" ); + + try + { + Console.WriteLine( $"Executing test: {test.Name}" ); + test.Invoke( null, new object[] { driver, config } ); + BrowserStackLogReader.MarkTest(BrowserStackLogReader.GetSessionId( buildName, sessionName ), TestStatus.Passed); + } + catch ( Exception e ) + { + Console.WriteLine("Exception thrown during test execution."); + Console.WriteLine( e.Message ); + Console.WriteLine( e.StackTrace ); + BrowserStackLogReader.MarkTest( BrowserStackLogReader.GetSessionId( buildName, sessionName ), TestStatus.Failed ); + driver.Quit(); + } + } + } + } + } +} diff --git a/AdfsUITestManager/AdfsUITestManager/Properties/AssemblyInfo.cs b/AdfsUITestManager/AdfsUITestManager/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9965ee6 --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle( "BrowserStackPaginatedUI" )] +[assembly: AssemblyDescription( "" )] +[assembly: AssemblyConfiguration( "" )] +[assembly: AssemblyCompany( "" )] +[assembly: AssemblyProduct( "BrowserStackPaginatedUI" )] +[assembly: AssemblyCopyright( "Copyright © 2018" )] +[assembly: AssemblyTrademark( "" )] +[assembly: AssemblyCulture( "" )] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible( false )] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid( "540afc72-5649-4ecf-b4d4-1273ce070504" )] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion( "1.0.0.0" )] +[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/AdfsUITestManager/AdfsUITestManager/ScreenShotRemoteWebDriver.cs b/AdfsUITestManager/AdfsUITestManager/ScreenShotRemoteWebDriver.cs new file mode 100644 index 0000000..ca5de06 --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/ScreenShotRemoteWebDriver.cs @@ -0,0 +1,25 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using System; +using OpenQA.Selenium; +using OpenQA.Selenium.Remote; + +namespace AdfsUITestManager +{ + public class ScreenShotRemoteWebDriver : RemoteWebDriver, ITakesScreenshot + { + public ScreenShotRemoteWebDriver( Uri uri, DesiredCapabilities dc ) + : base( uri, dc ) + { + } + + public Screenshot GetScreenshot() + { + Response screenshotResponse = this.Execute( DriverCommand.Screenshot, null ); + string base64 = screenshotResponse.Value.ToString(); + return new Screenshot( base64 ); + } + } +} diff --git a/AdfsUITestManager/AdfsUITestManager/TaskConfiguration.cs b/AdfsUITestManager/AdfsUITestManager/TaskConfiguration.cs new file mode 100644 index 0000000..1793674 --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/TaskConfiguration.cs @@ -0,0 +1,255 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using System.Configuration; +using System; +using System.Collections.Generic; + +namespace AdfsUITestManager +{ + public class TaskConfiguration : ConfigurationSection + { + [ConfigurationProperty( "farmData" )] + public FarmDataElement Farm + { + get + { + return ( FarmDataElement )this[ "farmData" ]; + } + set + { this[ "farmData" ] = value; } + } + + [ConfigurationProperty( "userData" )] + public UserDataElement User + { + get + { + return ( UserDataElement )this[ "userData" ]; + } + set + { this[ "userData" ] = value; } + } + + [ConfigurationProperty( "relyingPartyData" )] + public RelyingPartyDataElement RelyingPartyData + { + get + { + return ( RelyingPartyDataElement )this[ "relyingPartyData" ]; + } + set + { this[ "relyingPartyData" ] = value; } + } + + [ConfigurationProperty( "screenshot" )] + public ScreenshotDataElement ScreenshotData + { + get + { + return ( ScreenshotDataElement )this[ "screenshot" ]; + } + set + { this[ "screenshot" ] = value; } + } + + [ConfigurationProperty( "externalAuth" )] + public ExternalAuthDataElement ExternalAuth + { + get + { + return ( ExternalAuthDataElement )this[ "externalAuth" ]; + } + set + { this[ "externalAuth" ] = value; } + } + } + + public class ExternalAuthDataElement : ConfigurationElement + { + [ConfigurationProperty( "challengeAnswers", IsRequired = true )] + public string AnswersString + { + get + { + return ( String )this[ "challengeAnswers" ]; + } + } + + public List Answers + { + get + { + List answers = new List( AnswersString.Split( ',' ) ); + return answers; + } + } + } + + public class ScreenshotDataElement : ConfigurationElement + { + [ConfigurationProperty( "shouldScreenshot", IsRequired = true )] + public Boolean ShouldScreenshot + { + get + { + return ( Boolean )this[ "shouldScreenshot" ]; + } + set + { + this[ "shouldScreenshot" ] = value; + } + } + } + + public class RelyingPartyDataElement : ConfigurationElement + { + [ConfigurationProperty( "name", IsRequired = true )] + public String Name + { + get + { + return ( String )this[ "name" ]; + } + set + { + this[ "name" ] = value; + } + } + + [ConfigurationProperty( "wtrealm", IsRequired = true )] + public String Wtrealm + { + get + { + return ( String )this[ "wtrealm" ]; + } + set + { + this[ "wtrealm" ] = value; + } + } + + [ConfigurationProperty( "wreply", IsRequired = true )] + public String Wreply + { + get + { + return ( String )this[ "wreply" ]; + } + set + { + this[ "wreply" ] = value; + } + } + } + + public class FarmDataElement : ConfigurationElement + { + [ConfigurationProperty( "farmName", IsRequired = true )] + public String FarmName + { + get + { + return ( String )this[ "farmName" ]; + } + set + { + this[ "farmName" ] = value; + } + } + } + + public class UserDataElement : ConfigurationElement + { + [ConfigurationProperty( "misformattedUsername", IsRequired = true )] + public String MisformattedUsername + { + get + { + return ( String )this[ "misformattedUsername" ]; + } + set + { + this[ "misformattedUsername" ] = value; + } + } + + [ConfigurationProperty( "correctUsername", IsRequired = true )] + public String CorrectUsername + { + get + { + return ( String )this[ "correctUsername" ]; + } + set + { + this[ "correctUsername" ] = value; + } + } + + [ConfigurationProperty( "correctPassword", IsRequired = true )] + public String CorrectPassword + { + get + { + return ( String )this[ "correctPassword" ]; + } + set + { + this[ "correctPassword" ] = value; + } + } + [ConfigurationProperty( "badPassword", IsRequired = true )] + public String BadPassword + { + get + { + return ( String )this[ "badPassword" ]; + } + set + { + this[ "badPassword" ] = value; + } + } + [ConfigurationProperty( "externalAuthUsername", IsRequired = true )] + public String ExternalAuthUsername + { + get + { + return ( String )this[ "externalAuthUsername" ]; + } + set + { + this[ "externalAuthUsername" ] = value; + } + } + [ConfigurationProperty( "adminUsername", IsRequired = true )] + public String AdminUsername + { + get + { + return ( String )this[ "adminUsername" ]; + } + set + { + this[ "adminUsername" ] = value; + } + } + [ConfigurationProperty( "badUsername", IsRequired = true )] + public String BadUsername + { + get + { + return ( String )this[ "badUsername" ]; + } + set + { + this[ "badUsername" ] = value; + } + } + } + + +} diff --git a/AdfsUITestManager/AdfsUITestManager/TaskConfigurationFactory.cs b/AdfsUITestManager/AdfsUITestManager/TaskConfigurationFactory.cs new file mode 100644 index 0000000..e6e765f --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/TaskConfigurationFactory.cs @@ -0,0 +1,15 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace AdfsUITestManager +{ + class TaskConfigurationFactory + { + public static TaskConfiguration GetConfiguration() + { + TaskConfiguration config = ( TaskConfiguration ) System.Configuration.ConfigurationManager.GetSection( "taskConfigurationGroup/taskConfiguration" ); + return config; + } + } +} diff --git a/AdfsUITestManager/AdfsUITestManager/Tasks/ExternalAuthTasks.cs b/AdfsUITestManager/AdfsUITestManager/Tasks/ExternalAuthTasks.cs new file mode 100644 index 0000000..024f225 --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/Tasks/ExternalAuthTasks.cs @@ -0,0 +1,36 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using OpenQA.Selenium; + +namespace AdfsUITestManager +{ + /// + /// A class containing the tasks that can be performed on the External Authentication UI Page through AD FS + /// + class ExternalAuthTasks : TaskBase + { + /// + /// A task to enter an answer to a challenge question provided by an External Auth Provider + /// that prompts with challenge questions + /// + /// Note: This External Auth Adapter is based on a test auth adapter used by the AD FS Product Group + /// + /// + /// A web driver + /// Configuration data for this task + public static void EnterChallengeAnswers( IWebDriver driver, TaskConfiguration config ) + { + LogAndScreenshot( driver, config ); + + foreach ( string answer in config.ExternalAuth.Answers ) + { + IWebElement challengeField = driver.FindElement( By.Id( "challengeQuestionInput" ) ); + challengeField.SendKeys( $"{answer}" ); + challengeField.SendKeys( Keys.Return ); + LogAndScreenshot( driver, config ); + } + } + } +} diff --git a/AdfsUITestManager/AdfsUITestManager/Tasks/FormsPageTasks.cs b/AdfsUITestManager/AdfsUITestManager/Tasks/FormsPageTasks.cs new file mode 100644 index 0000000..d08627f --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/Tasks/FormsPageTasks.cs @@ -0,0 +1,82 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using OpenQA.Selenium; + +namespace AdfsUITestManager +{ + /// + /// A class containing the tasks that can be performed on the Username/Password Page through AD FS + /// + class FormsPageTasks : TaskBase + { + // TODO: Add tasks to use 'Next' button instead of Return key + + public static void EnterMisformattedUsername( IWebDriver driver, TaskConfiguration config ) + { + IWebElement usernameField = driver.FindElement( By.Id( "userNameInput" ) ); + usernameField.SendKeys( $"{config.User.MisformattedUsername}" ); + usernameField.SendKeys( Keys.Return ); + + // TODO: Validate that the error message appears + + LogAndScreenshot( driver, config ); + } + + public static void ClearUsername( IWebDriver driver, TaskConfiguration config ) + { + IWebElement usernameField = driver.FindElement( By.Id( "userNameInput" ) ); + usernameField.Clear(); + } + + public static void EnterBadUsername( IWebDriver driver, TaskConfiguration config ) + { + IWebElement usernameField = driver.FindElement( By.Id( "userNameInput" ) ); + usernameField.SendKeys( $"{config.User.BadUsername}" ); + + LogAndScreenshot( driver, config ); + + usernameField.SendKeys( Keys.Return ); + + // TODO: Validate that the error message appears + } + + public static void EnterCorrectUsername( IWebDriver driver, TaskConfiguration config ) + { + IWebElement usernameField = driver.FindElement( By.Id( "userNameInput" ) ); + usernameField.SendKeys( $"{config.User.CorrectUsername}" ); + LogAndScreenshot( driver, config ); + usernameField.SendKeys( Keys.Return ); + } + + public static void EnterCorrectUsernameAndTab( IWebDriver driver, TaskConfiguration config ) + { + IWebElement usernameField = driver.FindElement( By.Id( "userNameInput" ) ); + usernameField.SendKeys( $"{config.User.CorrectUsername}" ); + LogAndScreenshot( driver, config ); + usernameField.SendKeys( Keys.Tab ); + } + + public static void EnterBadPassword( IWebDriver driver, TaskConfiguration config ) + { + LogAndScreenshot( driver, config ); + + IWebElement passwordField = driver.FindElement( By.Id( "passwordInput" ) ); + passwordField.SendKeys( $"{config.User.BadPassword}" ); + passwordField.SendKeys( Keys.Return ); + + LogAndScreenshot( driver, config ); + } + + public static void EnterCorrectPassword( IWebDriver driver, TaskConfiguration config ) + { + LogAndScreenshot( driver, config ); + + IWebElement passwordField = driver.FindElement( By.Id( "passwordInput" ) ); + passwordField.SendKeys( $"{config.User.CorrectPassword}" ); + passwordField.SendKeys( Keys.Return ); + LogAndScreenshot( driver, config ); + } + } +} diff --git a/AdfsUITestManager/AdfsUITestManager/Tasks/IdpPageTasks.cs b/AdfsUITestManager/AdfsUITestManager/Tasks/IdpPageTasks.cs new file mode 100644 index 0000000..4d504b6 --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/Tasks/IdpPageTasks.cs @@ -0,0 +1,41 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using OpenQA.Selenium; + +namespace AdfsUITestManager +{ + /// + /// A class containing the tasks that can be performed on the IDP Initiated Sign On Page through AD FS + /// + class IdpPageTasks : TaskBase + { + public static void GoToIdpSignOnPage( IWebDriver driver, TaskConfiguration config ) + { + string url = $"https://{config.Farm.FarmName}/adfs/ls/idpinitiatedsignon"; + driver.Navigate().GoToUrl( url ); + LogAndScreenshot( driver, config ); + } + + public static void GoToIdpSignOnPageWithLoginHint( IWebDriver driver, TaskConfiguration config ) + { + driver.Navigate().GoToUrl( $"https://{config.Farm.FarmName}/adfs/ls/idpinitiatedsignon?login_hint={config.User.CorrectUsername}" ); + LogAndScreenshot( driver, config ); + } + + public static void ClickSignInOnIdpPage( IWebDriver driver, TaskConfiguration config ) + { + IWebElement signInButton = driver.FindElement( By.Id( "idp_SignInButton" ) ); + signInButton.Click(); + LogAndScreenshot( driver, config ); + } + + public static bool ValidateSignedIn( IWebDriver driver, TaskConfiguration config ) + { + IWebElement signOutButton = driver.FindElement( By.Id( "idp_SignOutPanel" ) ); + LogAndScreenshot( driver, config ); + return signOutButton != null; + } + } +} diff --git a/AdfsUITestManager/AdfsUITestManager/Tasks/OptionsPageTasks.cs b/AdfsUITestManager/AdfsUITestManager/Tasks/OptionsPageTasks.cs new file mode 100644 index 0000000..357ee8d --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/Tasks/OptionsPageTasks.cs @@ -0,0 +1,52 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using OpenQA.Selenium; + +namespace AdfsUITestManager +{ + /// + /// A class containing the tasks that can be performed on the Primary/Secondary Credential Options Page through AD FS + /// + class OptionsPageTasks : TaskBase + { + public static void SelectFormsOnOptionPage( IWebDriver driver, TaskConfiguration config ) + { + LogAndScreenshot( driver, config ); + + IWebElement passwordOption = driver.FindElement( By.Id( "FormsAuthentication" ) ); + passwordOption.Click(); + } + + public static void SelectCertOnOptionPage( IWebDriver driver, TaskConfiguration config ) + { + LogAndScreenshot( driver, config ); + + IWebElement certOption = driver.FindElement( By.Id( "CertificateAuthentication" ) ); + certOption.Click(); + + LogAndScreenshot( driver, config ); + } + + public static void SelectExternalOnOptionPage( IWebDriver driver, TaskConfiguration config ) + { + LogAndScreenshot( driver, config ); + + IWebElement externalOption = driver.FindElement( By.Id( "ExternalAuth" ) ); + externalOption.Click(); + + LogAndScreenshot( driver, config ); + } + + public static void SignInWithOtherOptions( IWebDriver driver, TaskConfiguration config ) + { + LogAndScreenshot( driver, config ); + + IWebElement otherOption = driver.FindElement( By.Id( "otherOptions" ) ); + otherOption.Click(); + + LogAndScreenshot( driver, config ); + } + } +} diff --git a/AdfsUITestManager/AdfsUITestManager/Tasks/PaginatedFormsTasks.cs b/AdfsUITestManager/AdfsUITestManager/Tasks/PaginatedFormsTasks.cs new file mode 100644 index 0000000..90babc9 --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/Tasks/PaginatedFormsTasks.cs @@ -0,0 +1,44 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using OpenQA.Selenium; + +namespace AdfsUITestManager +{ + /// + /// A class containing the tasks that can be performed on the Paginated Login Page through AD FS + /// + class PaginatedFormsTasks : TaskBase + { + public static void PasswordPageBackNavigate( IWebDriver driver, TaskConfiguration config ) + { + LogAndScreenshot( driver, config ); + + IWebElement backButton = driver.FindElement( By.Id( "backButton" ) ); + backButton.Click(); + + LogAndScreenshot( driver, config ); + } + + public static void OptionsPageBackNavigate( IWebDriver driver, TaskConfiguration config ) + { + LogAndScreenshot( driver, config ); + + IWebElement backButton = driver.FindElement( By.Id( "optionsBackButton" ) ); + backButton.Click(); + + LogAndScreenshot( driver, config ); + } + + public static void UsernamePageForwardNavigate( IWebDriver driver, TaskConfiguration config ) + { + LogAndScreenshot( driver, config ); + + IWebElement nextButton = driver.FindElement( By.Id( "nextButton" ) ); + nextButton.Click(); + + LogAndScreenshot( driver, config ); + } + } +} diff --git a/AdfsUITestManager/AdfsUITestManager/Tasks/RelyingPartyTasks.cs b/AdfsUITestManager/AdfsUITestManager/Tasks/RelyingPartyTasks.cs new file mode 100644 index 0000000..555947c --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/Tasks/RelyingPartyTasks.cs @@ -0,0 +1,28 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using System; +using OpenQA.Selenium; + +namespace AdfsUITestManager.Tasks +{ + /// + /// A class containing the tasks that can be performed when requesting a Relying Party through AD FS + /// + class RelyingPartyTasks : TaskBase + { + public static void GoToRpSignIn( IWebDriver driver, TaskConfiguration config ) + { + string url = $"https://{config.Farm.FarmName}/adfs/ls/?wa=wsignin1.0&wtrealm={config.RelyingPartyData.Wtrealm}&wreply={config.RelyingPartyData.Wreply}"; + driver.Navigate().GoToUrl( url ); + LogAndScreenshot( driver, config ); + } + + public static bool ValidateRpIsSignedIn( IWebDriver driver, TaskConfiguration config ) + { + LogAndScreenshot( driver, config ); + return string.Compare( driver.Url, config.RelyingPartyData.Wreply, StringComparison.InvariantCultureIgnoreCase ) == 0; + } + } +} diff --git a/AdfsUITestManager/AdfsUITestManager/Tasks/TaskBase.cs b/AdfsUITestManager/AdfsUITestManager/Tasks/TaskBase.cs new file mode 100644 index 0000000..abd99b1 --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/Tasks/TaskBase.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using System; +using OpenQA.Selenium; + +namespace AdfsUITestManager +{ + class TaskBase + { + public static void LogAndScreenshot( IWebDriver driver, TaskConfiguration config ) + { + Console.WriteLine( $"Current Page Title: {driver.Title}" ); + if ( config.ScreenshotData.ShouldScreenshot ) + { + Screenshot ss = ( ( ITakesScreenshot )driver ).GetScreenshot(); + ss.SaveAsFile( $"{ DateTime.Now:yyyy - MM - dd_hh - mm - ss - fff}.png", ScreenshotImageFormat.Png ); + } + } + } +} diff --git a/AdfsUITestManager/AdfsUITestManager/TestCases.cs b/AdfsUITestManager/AdfsUITestManager/TestCases.cs new file mode 100644 index 0000000..0f64d0d --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/TestCases.cs @@ -0,0 +1,237 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using OpenQA.Selenium; +using System.Diagnostics; + +namespace AdfsUITestManager +{ + using AdfsUITestManager.Tasks; + + public class TestCases + { + #region IDP Initiated Sign On Tests + /// + /// ENVIRONMENT REQUIREMENTS: + /// 1. Paginated UI feature enabled + /// 2. Multiple auth options available + /// + /// + static void BasicIdpSignOn_WithOptions_Password( IWebDriver driver, TaskConfiguration configuration ) + { + // Perform UI Operations + IdpPageTasks.GoToIdpSignOnPage( driver, configuration ); + IdpPageTasks.ClickSignInOnIdpPage( driver, configuration ); + FormsPageTasks.EnterMisformattedUsername( driver, configuration ); + FormsPageTasks.ClearUsername( driver, configuration ); + FormsPageTasks.EnterCorrectUsername( driver, configuration ); + OptionsPageTasks.SelectFormsOnOptionPage( driver, configuration ); + FormsPageTasks.EnterBadPassword( driver, configuration ); + OptionsPageTasks.SelectFormsOnOptionPage( driver, configuration ); + FormsPageTasks.EnterCorrectPassword( driver, configuration ); + + // Perform test validation + var success = IdpPageTasks.ValidateSignedIn( driver, configuration ); + Debug.Assert( success ); + + // Clean up + driver.Quit(); + } + + /// + /// ENVIRONMENT REQUIREMENTS: + /// 1. Paginated UI feature enabled + /// 2. Multiple auth options available (including certificate) + /// + /// + static void BasicIdpSignOn_WithOptions_CertThenForms( IWebDriver driver, TaskConfiguration configuration ) + { + // Perform UI Operations + IdpPageTasks.GoToIdpSignOnPage( driver, configuration ); + IdpPageTasks.ClickSignInOnIdpPage( driver, configuration ); + FormsPageTasks.EnterMisformattedUsername( driver, configuration ); + FormsPageTasks.EnterCorrectUsername( driver, configuration ); + OptionsPageTasks.SelectCertOnOptionPage( driver, configuration ); + OptionsPageTasks.SignInWithOtherOptions( driver, configuration ); + OptionsPageTasks.SelectFormsOnOptionPage( driver, configuration ); + FormsPageTasks.EnterCorrectPassword( driver, configuration ); + + // Perform test validation + var success = IdpPageTasks.ValidateSignedIn( driver, configuration ); + Debug.Assert( success ); + + // Cleanup + driver.Quit(); + } + + /// + /// ENVIRONMENT REQUIREMENTS: + /// 1. Paginated UI feature enabled + /// 2. Multiple auth options available (including external) + /// 3. User configured for external auth + /// + /// + static void BasicIdpSignOn_WithOptions_External( IWebDriver driver, TaskConfiguration configuration ) + { + // Perform UI Operations + IdpPageTasks.GoToIdpSignOnPage( driver, configuration ); + IdpPageTasks.ClickSignInOnIdpPage( driver, configuration ); + FormsPageTasks.EnterCorrectUsername( driver, configuration ); + OptionsPageTasks.SelectExternalOnOptionPage( driver, configuration ); + ExternalAuthTasks.EnterChallengeAnswers( driver, configuration ); + + // Perform test validation + var success = IdpPageTasks.ValidateSignedIn( driver, configuration ); + Debug.Assert( success ); + + // Cleanup + driver.Quit(); + } + + /// + /// ENVIRONMENT REQUIREMENTS: + /// 1. Paginated UI feature enabled + /// 2. Multiple auth options available (including external) + /// 3. User configured for external auth + /// + /// + static void HintIdpSignOn_WithOptions_External( IWebDriver driver, TaskConfiguration configuration ) + { + // Perform UI Operations + IdpPageTasks.GoToIdpSignOnPageWithLoginHint( driver, configuration ); + IdpPageTasks.ClickSignInOnIdpPage( driver, configuration ); + OptionsPageTasks.SelectExternalOnOptionPage( driver, configuration ); + ExternalAuthTasks.EnterChallengeAnswers( driver, configuration ); + + // Perform test validation + var success = IdpPageTasks.ValidateSignedIn( driver, configuration ); + Debug.Assert( success ); + + // Cleanup + driver.Quit(); + } + + /// + /// ENVIRONMENT REQUIREMENTS: + /// 1. Paginated UI feature enabled + /// 2. Multiple auth options available (including external) + /// + /// + static void HintIdpSignOn_WithOptions_Password_ChangeUser( IWebDriver driver, TaskConfiguration configuration ) + { + // Perform UI Operations + IdpPageTasks.GoToIdpSignOnPageWithLoginHint( driver, configuration ); + IdpPageTasks.ClickSignInOnIdpPage( driver, configuration ); + PaginatedFormsTasks.OptionsPageBackNavigate( driver, configuration ); + FormsPageTasks.ClearUsername( driver, configuration ); + FormsPageTasks.EnterCorrectUsername( driver, configuration ); + OptionsPageTasks.SelectFormsOnOptionPage( driver, configuration ); + FormsPageTasks.EnterCorrectPassword( driver, configuration ); + + // Perform test validation + var success = IdpPageTasks.ValidateSignedIn( driver, configuration ); + Debug.Assert( success ); + + // Cleanup + driver.Quit(); + } + + /// + /// ENVIRONMENT REQUIREMENTS: + /// 1. Paginated UI feature enabled + /// 2. Only forms auth options available + /// + /// + static void BasicIdpSignOn_NoOptions_Password( IWebDriver driver, TaskConfiguration configuration ) + { + // Perform UI Operations + IdpPageTasks.GoToIdpSignOnPage( driver, configuration ); + IdpPageTasks.ClickSignInOnIdpPage( driver, configuration ); + FormsPageTasks.EnterMisformattedUsername( driver, configuration ); + FormsPageTasks.EnterCorrectUsername( driver, configuration ); + PaginatedFormsTasks.PasswordPageBackNavigate( driver, configuration ); + PaginatedFormsTasks.UsernamePageForwardNavigate( driver, configuration ); + FormsPageTasks.EnterCorrectPassword( driver, configuration ); + + // Perform test validation + var success = IdpPageTasks.ValidateSignedIn( driver, configuration ); + Debug.Assert( success ); + + // Cleanup + driver.Quit(); + } + + /// + /// ENVIRONMENT REQUIREMENTS: + /// 1. Paginated UI feature disabled + /// + /// + static void BasicIdpSignOn_Password_Legacy( IWebDriver driver, TaskConfiguration configuration ) + { + // Perform UI Operations + IdpPageTasks.GoToIdpSignOnPage( driver, configuration ); + IdpPageTasks.ClickSignInOnIdpPage( driver, configuration ); + FormsPageTasks.EnterCorrectUsernameAndTab( driver, configuration ); + FormsPageTasks.EnterCorrectPassword( driver, configuration ); + + // Perform test validation + var success = IdpPageTasks.ValidateSignedIn( driver, configuration ); + Debug.Assert(success); + + // Clean up + driver.Quit(); + } + + /// + /// ENVIRONMENT REQUIREMENTS: + /// 1. Paginated UI feature disabled + /// + /// + static void BasicIdpSignOn_Password_Failure_Legacy( IWebDriver driver, TaskConfiguration configuration ) + { + // Perform UI Operations + IdpPageTasks.GoToIdpSignOnPage( driver, configuration ); + IdpPageTasks.ClickSignInOnIdpPage( driver, configuration ); + FormsPageTasks.EnterCorrectUsername( driver, configuration ); + FormsPageTasks.EnterBadPassword( driver, configuration ); + + // Perform test validation + var success = IdpPageTasks.ValidateSignedIn( driver, configuration ); + Debug.Assert( success ); + + // Cleanup + driver.Quit(); + } + + #endregion + + #region Basic RP Sign On Tests + /// + /// ENVIRONMENT REQUIREMENTS: + /// 1. Paginated UI feature enabled + /// 2. Multiple auth options available + /// 3. Relying party exists, and user has access to it + /// + /// + static void BasicRpSignOn_WithOptions_Password( IWebDriver driver, TaskConfiguration configuration ) + { + // Perform UI Operations + RelyingPartyTasks.GoToRpSignIn( driver, configuration ); + FormsPageTasks.EnterCorrectUsername( driver, configuration ); + OptionsPageTasks.SelectFormsOnOptionPage( driver, configuration ); + FormsPageTasks.EnterBadPassword( driver, configuration ); + OptionsPageTasks.SelectFormsOnOptionPage( driver, configuration ); + FormsPageTasks.EnterCorrectPassword( driver, configuration ); + + // Perform test validation + var success = RelyingPartyTasks.ValidateRpIsSignedIn( driver, configuration ); + Debug.Assert( success ); + + // Clean up + driver.Quit(); + } + + #endregion + } +} diff --git a/AdfsUITestManager/AdfsUITestManager/TestContext.cs b/AdfsUITestManager/AdfsUITestManager/TestContext.cs new file mode 100644 index 0000000..3821ed7 --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/TestContext.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using System.Collections.Generic; +using System.Reflection; +using OpenQA.Selenium.Remote; + +namespace AdfsUITestManager +{ + using System; + + class TestContext + { + public List TestCases; + //public List BrowserDrivers; + public TaskConfiguration Configuration; + public Dictionary> DriversPerTestCase; + + public TestContext() + { + TestListConfiguration testListConfig = ( TestListConfiguration )System.Configuration.ConfigurationManager.GetSection( "testListConfigurationGroup/testListConfiguration" ); + List testCases = new List( testListConfig.TestData.TestIds ); + this.TestCases = this.TestCases = ParseAndVerifyTestIds( testCases ); + + this.Configuration = TaskConfigurationFactory.GetConfiguration(); + + this.DriversPerTestCase = new Dictionary>(); + foreach ( var testcase in this.TestCases ) + { + this.DriversPerTestCase[testcase] = new List { DriverFactory.GetChromeDriverCapabilities() }; + } + } + + private List ParseAndVerifyTestIds( List testIdsRaw ) + { + if ( testIdsRaw.Count == 0 ) + { + throw new ArgumentNullException( $"Cannot find any test names in string {testIdsRaw}. Please check the string, and ensure that test names are comma-separated." ); + } + + List methods = new List(); + Type type = typeof( TestCases ); + + foreach ( var testLine in testIdsRaw ) + { + if ( string.IsNullOrEmpty( testLine ) ) + { + // Skip blank lines + continue; + } + + // Handle the case where tests are comma-separated + List testIds = new List( testLine.Split( ',' ) ); + foreach ( var test in testIds ) + { + MethodInfo methodInfo = type.GetMethod( test, BindingFlags.Static | BindingFlags.NonPublic ); + if ( methodInfo == null ) + { + throw new ArgumentNullException( $"Cannot find test name {test}. Please check BrowserStackTestManager.TestCases.cs to ensure your test case exists. Note: names are case-sensitive." ); + } + + methods.Add( methodInfo ); + } + + } + + return methods; + } + } +} diff --git a/AdfsUITestManager/AdfsUITestManager/TestListConfiguration.cs b/AdfsUITestManager/AdfsUITestManager/TestListConfiguration.cs new file mode 100644 index 0000000..5a73de3 --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/TestListConfiguration.cs @@ -0,0 +1,47 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Configuration; + +namespace AdfsUITestManager +{ + class TestListConfiguration : ConfigurationSection + { + [ConfigurationProperty( "testData" )] + public TestDataElement TestData + { + get + { + return ( TestDataElement )this[ "testData" ]; + } + set + { this[ "testData" ] = value; } + } + + public class TestDataElement : ConfigurationElement + { + [ConfigurationProperty( "testIds", IsRequired = true )] + public string TestIdsString + { + get + { + return ( String )this[ "testIds" ]; + } + } + + public List TestIds + { + get + { + List answers = new List( TestIdsString.Split( ',' ) ); + return answers; + } + } + } + + + } +} diff --git a/AdfsUITestManager/AdfsUITestManager/config.json b/AdfsUITestManager/AdfsUITestManager/config.json new file mode 100644 index 0000000..e69de29 diff --git a/AdfsUITestManager/AdfsUITestManager/packages.config b/AdfsUITestManager/AdfsUITestManager/packages.config new file mode 100644 index 0000000..42d521a --- /dev/null +++ b/AdfsUITestManager/AdfsUITestManager/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 72f1506..98d73d6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,133 @@ +# AD FS Automated User Interface Testing -# Contributing +## Overview + +This project provides a set of automated UI tests that can be performed to validate the user interface of an AD FS deployment. +Note that in order to execute these tests, you must have either your own Selenium infrastructure, or a subscription to a remote Selenium infrastructure. +This project is set up to support BrowserStack as the remote Selenium infrastructure. You can use a free BrowserStack subscription for 100 hours of testing, after which you will need a paid subscription. +For more details, see [BrowserStack](https://www.browserstack.com) + + +## Requirements + +1. Visual Studio + +2. BrowserStack subscription, free trial, or custom Selenium infrastructure + +3. Internet access from your AD FS Domain Controller + + +## Building the Project + +1. Open the `AdfsUITestManager.sln` solution in Visual Studio + +2. In the project directory containing `AdfsUITestManager.csproj`, add an application configuration (`App.config`) file. Your `App.config` file should contain the following: + + + + + +
+ + +
+ + + + + + + + /> + /> + + + + + /> + + correctPassword= + badPassword="badpassword" + externalAuthUsername= + adminUsername= + badUsername="badUser@farm.com" + misformattedUsername="wronguser" + /> + + wtrealm= + wreply= + /> + + /> + + + + + + + + + + +You can locate the BrowserStack settings [here](https://www.browserstack.com/automate/c-sharp) + +3. Update the list of test IDs under the `` element to include the comma-separated list of tests you wish to run against your environment. + The list of supported test cases is in `TestCases.cs`. If you wish to add more tests, please add them under `TestCases.cs`. + +4. Build the project using Visual Studio + + +## Setting Up Your Environment + +To execute the UI tests against your own AD FS environment, you must: + +**Deploy BrowserStack Test Agent** + +1. Install the [BrowserStack local testing agent](https://www.browserstack.com/browserstack-local/BrowserStackLocal-win32.zip) on your AD FS Domain Controller. +For more details on BrowserStack local testing, see [here](https://www.browserstack.com/local-testing) + +2. Determine your BrowserStack Automate Access Key, under ["Settings" > "Automate"](https://www.browserstack.com/accounts/settings) + +3. Execute the BrowerStackLocal agent by running the following in a command prompt on your AD FS Domain Controller: + `BrowserStackLocal.exe --key ` + + +## Running the Tests + +1. Decide what test cases you want to run. Note that different test cases have different environment requirements. You should only run the tests that match your environment. +All test cases, along with the environment requirements, are listed in `TestCases.cs`. + +2. Update the list of test IDs under the `` element to include the comma-separated list of tests you wish to run against your environment. + +3. Execute the test manager by running: + `.\AdfsUITestManager.exe` + +## Validating the Results + +You can find the BrowserStack results [here](https://www.browserstack.com/automate). These results will contain screenshots for all steps, along with a video you can review. +Each test case validates that the user scenario works correctly, but it does not validate the "look and feel" of any customizations, branding, etc. you have applied. +To ensure that the pages look and operate as you expect, we encourage you to manually examine the screenshots and video of the test runs. + +## Submitting AD FS UI Bugs + +If you find issues with the base AD FS User Interface, please open an [Issue](https://github.com/Microsoft/adfsUITests/issues) against this project. Please include the following information: + +1. Your AD FS Build Number and Behavior Level (major and minor, if applicable) + +2. A detailed description of the problem you see + +3. Screenshots of the issue you see + + +## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us @@ -11,4 +139,4 @@ provided by the bot. You will only need to do this once across all repos using o This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. \ No newline at end of file