From fc89093142e1bad15885fc0dfb7b45f959c0fa5d Mon Sep 17 00:00:00 2001 From: "Angel A. Rodriguez" Date: Wed, 13 Jan 2021 10:12:27 +0100 Subject: [PATCH 1/4] Add XrmApp.IsTabVisible --- .../Pages/Entity.cs | 17 +++++++++++++++-- .../BrowserCommand.cs | 1 - 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Microsoft.Dynamics365.UIAutomation.Api/Pages/Entity.cs b/Microsoft.Dynamics365.UIAutomation.Api/Pages/Entity.cs index 0648cefe..0e4ef7f0 100644 --- a/Microsoft.Dynamics365.UIAutomation.Api/Pages/Entity.cs +++ b/Microsoft.Dynamics365.UIAutomation.Api/Pages/Entity.cs @@ -686,7 +686,7 @@ public BrowserCommandResult CollapseTab(string name, int thinkTime = Const return this.Execute(GetOptions($"Collapse Tab: {name}"), driver => { - if (!driver.HasElement(By.XPath(Elements.Xpath[Reference.Entity.Tab].Replace("[NAME]", name)))) + if (!IsTabVisible(driver, name)) { throw new InvalidOperationException($"Tab with name '{name}' does not exist."); } @@ -720,6 +720,12 @@ public BrowserCommandResult DismissAlertIfPresent(bool stay = false) }); } + + public BrowserCommandResult IsTabVisible(string name, int thinkTime = Constants.DefaultThinkTime) + { + Browser.ThinkTime(thinkTime); + return Execute(GetOptions($"Is Tab Visible: {name}"), driver => IsTabVisible(driver, name)); + } /// /// Expands the Tab on a CRM Entity form. /// @@ -732,7 +738,7 @@ public BrowserCommandResult ExpandTab(string name, int thinkTime = Constan return this.Execute(GetOptions($"Expand Tab: {name}"), driver => { - if (!driver.HasElement(By.XPath(Elements.Xpath[Reference.Entity.Tab].Replace("[NAME]", name)))) + if (IsTabVisible(driver, name)) { throw new InvalidOperationException($"Tab with name '{name}' does not exist."); } @@ -745,6 +751,12 @@ public BrowserCommandResult ExpandTab(string name, int thinkTime = Constan }); } + + private static bool IsTabVisible(IWebDriver driver, string name) + { + return driver.HasElement(By.XPath(Elements.Xpath[Reference.Entity.Tab].Replace("[NAME]", name))); + } + /// /// Gets the value of a Text/Description field on an Entity footer. /// @@ -1892,6 +1904,7 @@ public BrowserCommandResult SetHeaderValue(TwoOption option) !optionValue && selectedValue == options.FirstOrDefault(a => a.GetAttribute("value") == "1")?.GetAttribute("title")) { driver.ClickWhenAvailable(By.XPath(Elements.Xpath[Reference.Entity.CheckboxFieldContainer_Header].Replace("[NAME]", option.Name.ToLower()))); + driver.ClearFocus(); } } else if (hasCheckbox) diff --git a/Microsoft.Dynamics365.UIAutomation.Browser/BrowserCommand.cs b/Microsoft.Dynamics365.UIAutomation.Browser/BrowserCommand.cs index 3cd73951..23520e94 100644 --- a/Microsoft.Dynamics365.UIAutomation.Browser/BrowserCommand.cs +++ b/Microsoft.Dynamics365.UIAutomation.Browser/BrowserCommand.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.Linq; using System.Threading; -using System.Collections.Generic; namespace Microsoft.Dynamics365.UIAutomation.Browser { From 0256258eef3309f2439921473ef39e1edf34e58f Mon Sep 17 00:00:00 2001 From: "Angel A. Rodriguez" Date: Mon, 5 Jul 2021 23:13:19 +0200 Subject: [PATCH 2/4] Rollback #1089 #1142 & Add related Tests --- .../Properties/AssemblyInfo.cs | 4 +- .../WebClient.cs | 27 ++++------ .../AssemblyVersionInfo.cs | 15 ------ ...oft.Dynamics365.UIAutomation.Sample.csproj | 2 + .../UCI/Login/Login.cs | 5 -- .../UCI/Login/LoginWhitoutTestBase.cs | 52 +++++++++++++++++++ 6 files changed, 67 insertions(+), 38 deletions(-) delete mode 100644 Microsoft.Dynamics365.UIAutomation.Api/AssemblyVersionInfo.cs create mode 100644 Microsoft.Dynamics365.UIAutomation.Sample/UCI/Login/LoginWhitoutTestBase.cs diff --git a/Microsoft.Dynamics365.UIAutomation.Api.UCI/Properties/AssemblyInfo.cs b/Microsoft.Dynamics365.UIAutomation.Api.UCI/Properties/AssemblyInfo.cs index bde89d81..fc24f732 100644 --- a/Microsoft.Dynamics365.UIAutomation.Api.UCI/Properties/AssemblyInfo.cs +++ b/Microsoft.Dynamics365.UIAutomation.Api.UCI/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // 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.19282.1210")] -[assembly: AssemblyFileVersion("1.0.19282.1210")] +[assembly: AssemblyVersion("1.0.21186.2056")] +[assembly: AssemblyFileVersion("1.0.21186.2056")] diff --git a/Microsoft.Dynamics365.UIAutomation.Api.UCI/WebClient.cs b/Microsoft.Dynamics365.UIAutomation.Api.UCI/WebClient.cs index bbc55757..ad04ec53 100644 --- a/Microsoft.Dynamics365.UIAutomation.Api.UCI/WebClient.cs +++ b/Microsoft.Dynamics365.UIAutomation.Api.UCI/WebClient.cs @@ -157,21 +157,17 @@ private LoginResult Login(IWebDriver driver, Uri uri, SecureString username, Sec EnterPassword(driver, password); ThinkTime(1000); - success = ClickStaySignedIn(driver) || IsUserAlreadyLogged(); } int attempts = 0; - bool entered = false; - if (mfaSecretKey != null) + bool entered; + do { - do - { - entered = EnterOneTimeCode(driver, mfaSecretKey); - success = ClickStaySignedIn(driver) || IsUserAlreadyLogged(); - attempts++; - } - while (!success && attempts <= Constants.DefaultRetryAttempts); // retry to enter the otc-code, if its fail & it is requested again + entered = EnterOneTimeCode(driver, mfaSecretKey); + success = ClickStaySignedIn(driver) || IsUserAlreadyLogged(); + attempts++; } + while (!success && attempts <= Constants.DefaultRetryAttempts); // retry to enter the otc-code, if its fail & it is requested again if (entered && !success) throw new InvalidOperationException("Something went wrong entering the OTC. Please check the MFA-SecretKey in configuration."); @@ -181,17 +177,15 @@ private LoginResult Login(IWebDriver driver, Uri uri, SecureString username, Sec private bool IsUserAlreadyLogged() => WaitForMainPage(10.Seconds()); - private static string GenerateOneTimeCode(SecureString mfaSecretKey) + private static string GenerateOneTimeCode(string key) { // credits: // https://dev.to/j_sakamoto/selenium-testing---how-to-sign-in-to-two-factor-authentication-2joi // https://www.nuget.org/packages/Otp.NET/ - string key = mfaSecretKey?.ToUnsecureString(); // <- this 2FA secret key. - byte[] base32Bytes = Base32Encoding.ToBytes(key); var totp = new Totp(base32Bytes); - var result = totp.ComputeTotp(); // <- got 2FA coed at this time! + var result = totp.ComputeTotp(); // <- got 2FA code at this time! return result; } @@ -221,10 +215,11 @@ private bool EnterOneTimeCode(IWebDriver driver, SecureString mfaSecretKey) if (input == null) return true; - if (mfaSecretKey == null) + string key = mfaSecretKey?.ToUnsecureString(); // <- this 2FA secret key. + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("The application is wait for the OTC but your MFA-SecretKey is not set. Please check your configuration."); - var oneTimeCode = GenerateOneTimeCode(mfaSecretKey); + var oneTimeCode = GenerateOneTimeCode(key); SetInputValue(driver, input, oneTimeCode, 1.Seconds()); input.Submit(); return true; // input found & code was entered diff --git a/Microsoft.Dynamics365.UIAutomation.Api/AssemblyVersionInfo.cs b/Microsoft.Dynamics365.UIAutomation.Api/AssemblyVersionInfo.cs deleted file mode 100644 index 11492e1b..00000000 --- a/Microsoft.Dynamics365.UIAutomation.Api/AssemblyVersionInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Reflection; - -// 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("8.2.0.0")] -[assembly: AssemblyFileVersion("8.2.0.0")] -[assembly: AssemblyInformationalVersion("8.2.0.0")] \ No newline at end of file diff --git a/Microsoft.Dynamics365.UIAutomation.Sample/Microsoft.Dynamics365.UIAutomation.Sample.csproj b/Microsoft.Dynamics365.UIAutomation.Sample/Microsoft.Dynamics365.UIAutomation.Sample.csproj index 567e3c08..01948b9b 100644 --- a/Microsoft.Dynamics365.UIAutomation.Sample/Microsoft.Dynamics365.UIAutomation.Sample.csproj +++ b/Microsoft.Dynamics365.UIAutomation.Sample/Microsoft.Dynamics365.UIAutomation.Sample.csproj @@ -90,6 +90,7 @@ + @@ -106,6 +107,7 @@ + diff --git a/Microsoft.Dynamics365.UIAutomation.Sample/UCI/Login/Login.cs b/Microsoft.Dynamics365.UIAutomation.Sample/UCI/Login/Login.cs index 6901b3af..c37b60e6 100644 --- a/Microsoft.Dynamics365.UIAutomation.Sample/UCI/Login/Login.cs +++ b/Microsoft.Dynamics365.UIAutomation.Sample/UCI/Login/Login.cs @@ -17,11 +17,6 @@ public class Login : TestsBase public void MultiFactorLogin() { _xrmApp.Grid.SwitchView("All Accounts"); - - _xrmApp.CommandBar.ClickCommand("New"); - - _xrmApp.Entity.SetValue("name", "Test API Account" + TestSettings.GetRandomString(5,5) ); - _xrmApp.Entity.SetValue("telephone1", "555-555-5555"); } } } \ No newline at end of file diff --git a/Microsoft.Dynamics365.UIAutomation.Sample/UCI/Login/LoginWhitoutTestBase.cs b/Microsoft.Dynamics365.UIAutomation.Sample/UCI/Login/LoginWhitoutTestBase.cs new file mode 100644 index 00000000..703cc265 --- /dev/null +++ b/Microsoft.Dynamics365.UIAutomation.Sample/UCI/Login/LoginWhitoutTestBase.cs @@ -0,0 +1,52 @@ +using System; +using System.Configuration; +using System.Security; +using Microsoft.Dynamics365.UIAutomation.Api.UCI; +using Microsoft.Dynamics365.UIAutomation.Browser; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Dynamics365.UIAutomation.Sample.UCI +{ + [TestClass] + public class LoginWhitoutTestBase + { + private readonly SecureString _username = ConfigurationManager.AppSettings["OnlineUsername"]?.ToSecureString(); + private readonly SecureString _password = ConfigurationManager.AppSettings["OnlinePassword"]?.ToSecureString(); + private readonly SecureString _mfaSecretKey = ConfigurationManager.AppSettings["MfaSecretKey"]?.ToSecureString(); + private readonly Uri _xrmUri = new Uri(ConfigurationManager.AppSettings["OnlineCrmUrl"]); + + [TestMethod] + public void LoginFail_DontPassMfaSecretKey() + { + var requireMfa = ConfigurationManager.AppSettings["MfaSecretKey"] != null; + if(!requireMfa) + return; + + var client = new WebClient(TestSettings.Options); + using (var xrmApp = new XrmApp(client)) + { + try + { + xrmApp.OnlineLogin.Login(_xrmUri, _username, _password); + } + catch (InvalidOperationException e) + { + var errorMessage = "The application is wait for the OTC but your MFA-SecretKey is not set."; + Assert.IsTrue(e.Message.Contains(errorMessage)); + } + } + } + + [TestMethod] + public void LoginSuccess_PassMfaSecretKey() + { + var client = new WebClient(TestSettings.Options); + using (var xrmApp = new XrmApp(client)) + { + xrmApp.OnlineLogin.Login(_xrmUri, _username, _password, _mfaSecretKey); + + xrmApp.Navigation.OpenApp(UCIAppName.Sales); + } + } + } +} \ No newline at end of file From 01c6d76e1110c525bc2eb7a87464ad42f1421629 Mon Sep 17 00:00:00 2001 From: "Angel A. Rodriguez" Date: Mon, 5 Jul 2021 23:19:02 +0200 Subject: [PATCH 3/4] Rollback #1089 #1142 & Add related Tests --- .../Microsoft.Dynamics365.UIAutomation.Sample.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/Microsoft.Dynamics365.UIAutomation.Sample/Microsoft.Dynamics365.UIAutomation.Sample.csproj b/Microsoft.Dynamics365.UIAutomation.Sample/Microsoft.Dynamics365.UIAutomation.Sample.csproj index 01948b9b..6aa7f0e9 100644 --- a/Microsoft.Dynamics365.UIAutomation.Sample/Microsoft.Dynamics365.UIAutomation.Sample.csproj +++ b/Microsoft.Dynamics365.UIAutomation.Sample/Microsoft.Dynamics365.UIAutomation.Sample.csproj @@ -90,7 +90,6 @@ - From 506608801e56ab35df94f2f51a4915eb79bc213e Mon Sep 17 00:00:00 2001 From: "Angel A. Rodriguez" Date: Thu, 20 Jul 2023 02:08:43 +0200 Subject: [PATCH 4/4] Issue #1382 -> Add Option ExtraChromeArguments Isuue #958 -> Add Option TimeFactor If not use incognito mode, create a Temporal Chrome Profite in to avoid ask for login every time --- .../BrowserOptions.cs | 26 +++++++++++++------ .../UCI/Labs/TestsBase/1_Lab_TestsClass.cs | 3 +++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Microsoft.Dynamics365.UIAutomation.Browser/BrowserOptions.cs b/Microsoft.Dynamics365.UIAutomation.Browser/BrowserOptions.cs index 9dfd695d..7ce31d19 100644 --- a/Microsoft.Dynamics365.UIAutomation.Browser/BrowserOptions.cs +++ b/Microsoft.Dynamics365.UIAutomation.Browser/BrowserOptions.cs @@ -16,11 +16,12 @@ public class BrowserOptions { public BrowserOptions() { + this.TimeFactor = 1.0f; this.DriversPath = Path.Combine(Directory.GetCurrentDirectory()); //, @"Drivers\"); this.DownloadsPath = null; this.BrowserType = BrowserType.IE; - this.PageLoadTimeout = new TimeSpan(0, 3, 0); - this.CommandTimeout = TimeSpan.FromMinutes(20); + this.PageLoadTimeout = TimeSpan.FromMinutes(3); + this.CommandTimeout = TimeSpan.FromMinutes(5); this.StartMaximized = true; this.FireEvents = false; this.TraceSource = Constants.DefaultTraceSource; @@ -51,6 +52,7 @@ public BrowserOptions() this.CookieСontrolsMode = 0; } + public float TimeFactor { get; set; } public BrowserType RemoteBrowserType { get; set; } public Uri RemoteHubServer { get; set; } public BrowserType BrowserType { get; set; } @@ -60,7 +62,7 @@ public BrowserOptions() public bool PrivateMode { get; set; } public bool CleanSession { get; set; } public TimeSpan PageLoadTimeout { get; set; } - public TimeSpan CommandTimeout { get; set; } = TimeSpan.FromMinutes(20); + public TimeSpan CommandTimeout { get; set; } /// /// When the browser will open maximized at the highest supported resolution. /// @@ -104,6 +106,7 @@ public BrowserOptions() /// Please raise any issues with this TestMode being enabled to the Microsoft/EasyRepro community on GitHub for review. /// public bool UCITestMode { get; set; } + public string[] ExtraChromeArguments { get; set; } /// /// Gets or sets the Performance Mode to enable performance center telemetry. @@ -128,6 +131,11 @@ public virtual ChromeOptions ToChrome() { options.AddArgument("--incognito"); } + else + { + var tempPath = Path.GetTempPath(); + options.AddArguments($"--user-data-dir={tempPath}\\EasyRepro"); + } if (this.Headless) { @@ -215,8 +223,13 @@ public virtual ChromeOptions ToChrome() { options.AddUserProfilePreference("download.default_directory", DownloadsPath); } + + options.AddUserProfilePreference("profile.cookie_controls_mode", this.CookieСontrolsMode); + + if (ExtraChromeArguments != null) + foreach (var argument in ExtraChromeArguments) + options.AddArgument(argument); - options.AddUserProfilePreference("profile.cookie_controls_mode", this.CookieСontrolsMode); return options; } @@ -252,10 +265,7 @@ public virtual InternetExplorerOptions ToInternetExplorer() public virtual FirefoxOptions ToFireFox() { - var options = new FirefoxOptions() - { - - }; + var options = new FirefoxOptions(); if (!string.IsNullOrEmpty(DownloadsPath)) { diff --git a/Microsoft.Dynamics365.UIAutomation.Sample/UCI/Labs/TestsBase/1_Lab_TestsClass.cs b/Microsoft.Dynamics365.UIAutomation.Sample/UCI/Labs/TestsBase/1_Lab_TestsClass.cs index 8e382a6c..c561875c 100644 --- a/Microsoft.Dynamics365.UIAutomation.Sample/UCI/Labs/TestsBase/1_Lab_TestsClass.cs +++ b/Microsoft.Dynamics365.UIAutomation.Sample/UCI/Labs/TestsBase/1_Lab_TestsClass.cs @@ -21,6 +21,9 @@ public void NotUsing_TheBaseClass() { var options = TestSettings.Options; options.PrivateMode = true; + options.TimeFactor = 1.5f; + options.ExtraChromeArguments = new[] { "--disable-geolocation" }; + options.UCIPerformanceMode = false; // <= you can also change other settings here, for this tests only var client = new WebClient(options);