diff --git a/Microsoft.Dynamics365.UIAutomation.Api.UCI/Elements/Dialog.cs b/Microsoft.Dynamics365.UIAutomation.Api.UCI/Elements/Dialog.cs index 4844d809..fe59c3fa 100644 --- a/Microsoft.Dynamics365.UIAutomation.Api.UCI/Elements/Dialog.cs +++ b/Microsoft.Dynamics365.UIAutomation.Api.UCI/Elements/Dialog.cs @@ -58,7 +58,7 @@ public void CloseOpportunity(double revenue, DateTime closeDate, string descript /// /// Enum used to assign record to user or team /// Name of the user or team to assign to - public void Assign(Dialogs.AssignTo to, string userOrTeamName = "") + public void Assign(AssignTo to, string userOrTeamName = null) { _client.AssignDialog(to, userOrTeamName); } diff --git a/Microsoft.Dynamics365.UIAutomation.Api.UCI/WebClient.cs b/Microsoft.Dynamics365.UIAutomation.Api.UCI/WebClient.cs index 2ef226b9..98ba8473 100644 --- a/Microsoft.Dynamics365.UIAutomation.Api.UCI/WebClient.cs +++ b/Microsoft.Dynamics365.UIAutomation.Api.UCI/WebClient.cs @@ -12,6 +12,7 @@ using System.Security; using System.Threading; using System.Web; +using OpenQA.Selenium.Interactions; using OtpNet; namespace Microsoft.Dynamics365.UIAutomation.Api.UCI @@ -235,6 +236,7 @@ private void EnterOneTimeCode(IWebDriver driver, SecureString mfaSecrectKey) if (attempts >= Constants.DefaultRetryAttempts) throw; } + attempts++; ThinkTime(Constants.DefaultRetryDelay); } @@ -901,56 +903,47 @@ internal BrowserCommandResult ConfirmationDialog(bool ClickConfirmButton) }); } - internal BrowserCommandResult AssignDialog(Dialogs.AssignTo to, string userOrTeamName) + internal BrowserCommandResult AssignDialog(Dialogs.AssignTo to, string userOrTeamName = null) { + userOrTeamName = userOrTeamName?.Trim() ?? string.Empty; return this.Execute(GetOptions($"Assign to User or Team Dialog"), driver => { var inlineDialog = this.SwitchToDialog(); - if (inlineDialog) - { - if (to != Dialogs.AssignTo.Me) - { - //Click the Option to Assign to User Or Team - driver.WaitUntilClickable(By.XPath(AppElements.Xpath[AppReference.Dialogs.AssignDialogToggle])); - - var toggleButton = driver.WaitUntilAvailable(By.XPath(AppElements.Xpath[AppReference.Dialogs.AssignDialogToggle]), "Me/UserTeam toggle button unavailable"); - if (toggleButton.Text == "Me") - toggleButton.Click(); - - //Set the User Or Team - var userOrTeamField = driver.WaitUntilAvailable(By.XPath(AppElements.Xpath[AppReference.Entity.TextFieldLookup]), "User field unavailable"); - - if (userOrTeamField.FindElements(By.TagName("input")).Count > 0) - { - var input = userOrTeamField.FindElement(By.TagName("input")); - if (input != null) - { - input.Click(); - - driver.WaitForTransaction(); - - input.SendKeys(userOrTeamName, true); - } - } - - //Pick the User from the list - driver.WaitUntilVisible(By.XPath(AppElements.Xpath[AppReference.Dialogs.AssignDialogUserTeamLookupResults])); + if (!inlineDialog) + return false; - driver.WaitForTransaction(); + //Click the Option to Assign to User Or Team + var xpathToToggleButton = By.XPath(AppElements.Xpath[AppReference.Dialogs.AssignDialogToggle]); + var toggleButton = driver.WaitUntilClickable(xpathToToggleButton, "Me/UserTeam toggle button unavailable"); - var container = driver.FindElement(By.XPath(AppElements.Xpath[AppReference.Dialogs.AssignDialogUserTeamLookupResults])); - var records = container.FindElements(By.TagName("li")); - foreach (var record in records) - { - if (record.Text.StartsWith(userOrTeamName, StringComparison.OrdinalIgnoreCase)) - record.Click(true); - } - } - - //Click Assign - var okButton = driver.FindElement(By.XPath(AppElements.Xpath[AppReference.Dialogs.AssignDialogOKButton])); - okButton.Click(true); + if (to == Dialogs.AssignTo.Me) + { + if (toggleButton.Text != "Me") + toggleButton.Click(); } + else + { + if (toggleButton.Text == "Me") + toggleButton.Click(); + + //Set the User Or Team + var userOrTeamField = driver.WaitUntilAvailable(By.XPath(AppElements.Xpath[AppReference.Entity.TextFieldLookup]), "User field unavailable"); + var input = userOrTeamField.ClickWhenAvailable(By.TagName("input"), "User field unavailable"); + input.SendKeys(userOrTeamName, true); + + ThinkTime(2000); + + //Pick the User from the list + var container = driver.WaitUntilVisible(By.XPath(AppElements.Xpath[AppReference.Dialogs.AssignDialogUserTeamLookupResults])); + container.WaitUntil( + c => c.FindElements(By.TagName("li")).FirstOrDefault(r => r.Text.StartsWith(userOrTeamName, StringComparison.OrdinalIgnoreCase)), + successCallback: e => e.Click(true), + failureCallback: () => throw new InvalidOperationException($"None {to} found which match with '{userOrTeamName}'")); + } + + //Click Assign + driver.ClickWhenAvailable(By.XPath(AppElements.Xpath[AppReference.Dialogs.AssignDialogOKButton]), TimeSpan.FromSeconds(5), + "Unable to click the OK button in the assign dialog"); return true; }); @@ -1311,27 +1304,26 @@ internal BrowserCommandResult SwitchView(string viewName, int thinkTime = internal BrowserCommandResult OpenRecord(int index, int thinkTime = Constants.DefaultThinkTime, bool checkRecord = false) { ThinkTime(thinkTime); - return Execute(GetOptions("Open Grid Record"), driver => { - IWebElement control = driver.WaitUntilAvailable(By.XPath(AppElements.Xpath[AppReference.Grid.Container])); - - var xpathToFind = checkRecord - ? $"//div[@data-id='cell-{index}-1']" - : $"//div[contains(@data-id, 'cell-{index}')]//a"; - control.ClickWhenAvailable(By.XPath(xpathToFind), "An error occur trying to open the record at position {index}"); - - // Logic equivalent to fix #746 (by @rswafford) - //var xpathToFind = $"//div[@data-id='cell-{index}-1']"; - //control.WaitUntilClickable(By.XPath(xpathToFind), - // e => - // { - // e.Click(); - // if (!checkRecord) - // driver.DoubleClick(e); - // }, - // $"An error occur trying to open the record at position {index}" - // ); + var xpathToGrid = By.XPath(AppElements.Xpath[AppReference.Grid.Container]); + IWebElement control = driver.WaitUntilAvailable(xpathToGrid); + + Func action; + if (checkRecord) + action = e => e.Click(); + else + action = e => e.DoubleClick(); + + var xpathToCell = By.XPath($".//div[@data-id='cell-{index}-1']"); + control.WaitUntilClickable(xpathToCell, + cell => + { + var emptyDiv = cell.FindElement(By.TagName("div")); + driver.Perform(action, cell, cell.LeftTo(emptyDiv)); + }, + $"An error occur trying to open the record at position {index}" + ); driver.WaitForTransaction(); return true; @@ -2223,6 +2215,7 @@ private void TrySetDateValue(IWebDriver driver, IWebElement dateField, string da failureCallback: () => throw new InvalidOperationException($"Timeout after 10 seconds. Expected: {date}. Actual: {dateField.GetAttribute("value")}") ); } + private void ClearFieldValue(IWebElement field) { if (field.GetAttribute("value").Length > 0) @@ -2230,6 +2223,7 @@ private void ClearFieldValue(IWebElement field) field.SendKeys(Keys.Control + "a"); field.SendKeys(Keys.Backspace); } + ThinkTime(500); } @@ -2637,7 +2631,7 @@ internal BrowserCommandResult GetValue(MultiValueOptionSet expandCollapseButtons.First().Click(true); } - var returnValue = new MultiValueOptionSet { Name = option.Name }; + var returnValue = new MultiValueOptionSet {Name = option.Name}; xpath = AppElements.Xpath[AppReference.MultiSelect.SelectedRecordLabel].Replace("[NAME]", Elements.ElementId[option.Name]); var labelItems = driver.FindElements(By.XPath(xpath)); @@ -2909,7 +2903,7 @@ internal BrowserCommandResult GetHeaderValue(BooleanItem control) { var xpathToContainer = AppElements.Xpath[AppReference.Entity.Header.DateTimeFieldContainer].Replace("[NAME]", control.Name); return Execute(GetOptions($"Get Header DateTime Value {control.Name}"), - driver => ExecuteInHeaderContainer(driver, xpathToContainer, + driver => ExecuteInHeaderContainer(driver, xpathToContainer, container => TryGetValue(driver, container, control))); } diff --git a/Microsoft.Dynamics365.UIAutomation.Browser/Extensions/RelativePositions.cs b/Microsoft.Dynamics365.UIAutomation.Browser/Extensions/RelativePositions.cs new file mode 100644 index 00000000..0865300b --- /dev/null +++ b/Microsoft.Dynamics365.UIAutomation.Browser/Extensions/RelativePositions.cs @@ -0,0 +1,47 @@ +using System; +using System.Drawing; +using OpenQA.Selenium; + +namespace Microsoft.Dynamics365.UIAutomation.Browser +{ + public static class RelativePositions + { + public static Func Above(this IWebElement outer, IWebElement inner) => + () => + { + int x = outer.Size.Width / 2; + int y = (inner.Location.Y - outer.Location.Y) / 2; + return new Point(x, y); + }; + + public static Func Below(this IWebElement outer, IWebElement inner) => + () => + { + int x = outer.Size.Width / 2; + var outerEnd = outer.Location + outer.Size; + var innerEnd = inner.Location + inner.Size; + int dBelow = outerEnd.Y - innerEnd.Y; + int y = innerEnd.Y + dBelow / 2; + return new Point(x, y); + }; + + public static Func LeftTo(this IWebElement outer, IWebElement inner) => + () => + { + int x = (inner.Location.X - outer.Location.X) / 2; + int y = outer.Size.Height / 2; + return new Point(x, y); + }; + + public static Func RightTo(this IWebElement outer, IWebElement inner) => + () => + { + var outerEnd = outer.Location + outer.Size; + var innerEnd = inner.Location + inner.Size; + int dRight = outerEnd.X - innerEnd.X; + int x = innerEnd.X + dRight / 2; + int y = outer.Size.Height / 2; + return new Point(x, y); + }; + } +} \ No newline at end of file diff --git a/Microsoft.Dynamics365.UIAutomation.Browser/Extensions/SeleniumExtensions.cs b/Microsoft.Dynamics365.UIAutomation.Browser/Extensions/SeleniumExtensions.cs index 27ea225b..198dad61 100644 --- a/Microsoft.Dynamics365.UIAutomation.Browser/Extensions/SeleniumExtensions.cs +++ b/Microsoft.Dynamics365.UIAutomation.Browser/Extensions/SeleniumExtensions.cs @@ -7,6 +7,7 @@ using OpenQA.Selenium.Support.Events; using OpenQA.Selenium.Support.UI; using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; @@ -59,7 +60,7 @@ public static void Hover(this IWebElement element, IWebDriver driver, bool ignor try { Actions action = new Actions(driver); - action.MoveToElement(element).Build().Perform(); + action.MoveToElement(element).Perform(); } catch (StaleElementReferenceException) { @@ -70,14 +71,27 @@ public static void Hover(this IWebElement element, IWebDriver driver, bool ignor #endregion Click + public static void Click(this IWebDriver driver, IWebElement element, Func offsetFunc = null, bool ignoreStaleElementException = true) + => driver.Perform(a => a.Click(), element, offsetFunc, ignoreStaleElementException); + #region Double Click - public static void DoubleClick(this IWebDriver driver, IWebElement element, bool ignoreStaleElementException = false) + public static void DoubleClick(this IWebDriver driver, IWebElement element, Func offsetFunc = null, bool ignoreStaleElementException = true) + => driver.Perform(a => a.DoubleClick(), element, offsetFunc, ignoreStaleElementException); + + public static void Perform(this IWebDriver driver, Func action, IWebElement element, Func offsetFunc = null, bool ignoreStaleElementException = true) { try { - Actions actions = new Actions(driver); - actions.DoubleClick(element).Build().Perform(); + var actions = new Actions(driver); + if (offsetFunc == null) + actions = actions.MoveToElement(element); + else + { + var offset = offsetFunc(); + actions = actions.MoveToElement(element, offset.X, offset.Y); + } + action(actions).Perform(); } catch (StaleElementReferenceException) { @@ -86,12 +100,7 @@ public static void DoubleClick(this IWebDriver driver, IWebElement element, bool } } - public static void DoubleClick(this IWebDriver driver, By by, bool ignoreStaleElementException = false) - { - var element = driver.FindElement(by); - driver.DoubleClick(element, ignoreStaleElementException); - } - + #endregion #region Script Execution diff --git a/Microsoft.Dynamics365.UIAutomation.Browser/Extensions/TimeExtensions.cs b/Microsoft.Dynamics365.UIAutomation.Browser/Extensions/TimeExtensions.cs new file mode 100644 index 00000000..78fdafd0 --- /dev/null +++ b/Microsoft.Dynamics365.UIAutomation.Browser/Extensions/TimeExtensions.cs @@ -0,0 +1,13 @@ +// Created by: Rodriguez Mustelier Angel (rodang) +// Modify On: 2020-02-09 14:05 + +using System; + +namespace Microsoft.Dynamics365.UIAutomation.Browser +{ + public static class TimeExtensions + { + public static TimeSpan Seconds(this int value) + => TimeSpan.FromSeconds(value); + } +} \ No newline at end of file diff --git a/Microsoft.Dynamics365.UIAutomation.Browser/Microsoft.Dynamics365.UIAutomation.Browser.csproj b/Microsoft.Dynamics365.UIAutomation.Browser/Microsoft.Dynamics365.UIAutomation.Browser.csproj index ee96443d..d601f616 100644 --- a/Microsoft.Dynamics365.UIAutomation.Browser/Microsoft.Dynamics365.UIAutomation.Browser.csproj +++ b/Microsoft.Dynamics365.UIAutomation.Browser/Microsoft.Dynamics365.UIAutomation.Browser.csproj @@ -78,8 +78,10 @@ + + diff --git a/Microsoft.Dynamics365.UIAutomation.Sample/UCI/CommandBar/AssignAccount.cs b/Microsoft.Dynamics365.UIAutomation.Sample/UCI/CommandBar/AssignAccount.cs index ed719eaf..ccd325a2 100644 --- a/Microsoft.Dynamics365.UIAutomation.Sample/UCI/CommandBar/AssignAccount.cs +++ b/Microsoft.Dynamics365.UIAutomation.Sample/UCI/CommandBar/AssignAccount.cs @@ -3,6 +3,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.Dynamics365.UIAutomation.Api.UCI; +using Microsoft.Dynamics365.UIAutomation.Browser; namespace Microsoft.Dynamics365.UIAutomation.Sample.UCI @@ -25,9 +26,22 @@ public void UCITestAssignAccount() _xrmApp.ThinkTime(2000); + string name = _xrmApp.Entity.GetHeaderValue(new LookupItem{ Name = "ownerid" }); + Assert.IsNotNull(name); + _xrmApp.CommandBar.ClickCommand("Assign"); + _xrmApp.Dialogs.Assign(Dialogs.AssignTo.User, name); + } - _xrmApp.Dialogs.Assign(Dialogs.AssignTo.User, "Grant"); + [TestMethod] + public void UCITestAssignAccount_ToMe() + { + _xrmApp.Grid.OpenRecord(0); + + _xrmApp.ThinkTime(2000); + + _xrmApp.CommandBar.ClickCommand("Assign"); + _xrmApp.Dialogs.Assign(Dialogs.AssignTo.Me); } } }