Skip to content

Commit

Permalink
fix: related record creation not impersonated (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
ewingjm authored Feb 24, 2021
1 parent 941bdae commit 9e4df85
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
namespace Capgemini.PowerApps.SpecFlowBindings.Extensions
{
using System;
using System.Linq;
using Microsoft.Dynamics365.UIAutomation.Api.UCI;
using Microsoft.Dynamics365.UIAutomation.Browser;
using OpenQA.Selenium;

/// <summary>
/// Extensions to the <see cref="WebClient"/> class.
/// </summary>
public static class WebClientExtensions
{
/// <summary>
/// Temporary workaround until https://github.com/microsoft/EasyRepro/issues/1087 is resolved.
/// </summary>
/// <param name="webClient">The <see cref="WebClient"/>.</param>
/// <param name="subGridName">The name of the subgrid.</param>
/// <param name="name">The name of the command.</param>
/// <param name="subName">The name of the sub-command.</param>
/// <param name="subSecondName">The name of the second sub-command.</param>
/// <returns>The <see cref="BrowserCommandResult{TReturn}"/>.</returns>
public static BrowserCommandResult<bool> ClickSubGridCommandV2(this WebClient webClient, string subGridName, string name, string subName = null, string subSecondName = null)
{
return webClient.Execute(GetOptions("Click SubGrid Command"), driver =>
{
var subGrid = driver.FindElement(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridContents].Replace("[NAME]", subGridName)));
if (subGrid.TryFindElement(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridCommandBar].Replace("[NAME]", subGridName)), out var subGridCommandBar))
{
var items = subGridCommandBar.FindElements(By.TagName("button"));
if (items.Any(x => x.GetAttribute("aria-label").Equals(name, StringComparison.OrdinalIgnoreCase)))
{
items.FirstOrDefault(x => x.GetAttribute("aria-label").Equals(name, StringComparison.OrdinalIgnoreCase)).Click(true);
driver.WaitForTransaction();
}
else
{
if (items.Any(x => x.GetAttribute("aria-label").Contains("More Commands", StringComparison.OrdinalIgnoreCase)))
{
items.FirstOrDefault(x => x.GetAttribute("aria-label").Contains("More Commands", StringComparison.OrdinalIgnoreCase)).Click(true);
driver.WaitForTransaction();
var overflowContainer = driver.FindElement(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridOverflowContainer]));
if (overflowContainer.HasElement(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridOverflowButton].Replace("[NAME]", name))))
{
overflowContainer.FindElement(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridOverflowButton].Replace("[NAME]", name))).Click(true);
driver.WaitForTransaction();
}
else
{
throw new InvalidOperationException($"No command with the name '{name}' exists inside of {subGridName} Commandbar.");
}
}
else
{
throw new InvalidOperationException($"No command with the name '{name}' exists inside of {subGridName} CommandBar.");
}
}
if (subName != null)
{
var overflowContainer = driver.FindElement(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridOverflowContainer]));
if (overflowContainer.HasElement(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridOverflowButton].Replace("[NAME]", subName))))
{
overflowContainer.FindElement(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridOverflowButton].Replace("[NAME]", subName))).Click(true);
driver.WaitForTransaction();
}
else
{
throw new InvalidOperationException($"No command with the name '{subName}' exists under the {name} command inside of {subGridName} Commandbar.");
}
if (subSecondName != null)
{
overflowContainer = driver.FindElement(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridOverflowContainer]));
if (overflowContainer.HasElement(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridOverflowButton].Replace("[NAME]", subSecondName))))
{
overflowContainer.FindElement(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridOverflowButton].Replace("[NAME]", subSecondName))).Click(true);
driver.WaitForTransaction();
}
else
{
throw new InvalidOperationException($"No command with the name '{subSecondName}' exists under the {subName} command inside of {name} on the {subGridName} SubGrid Commandbar.");
}
}
}
}
else
{
throw new InvalidOperationException($"Unable to locate the Commandbar for the {subGrid} SubGrid.");
}
return true;
});
}

/// <summary>
/// Temporary workaround until https://github.com/microsoft/EasyRepro/issues/1087 is resolved.
/// </summary>
/// <param name="webClient">The <see cref="WebClient"/>.</param>
/// <param name="name">The name of the command.</param>
/// <param name="subname">The name of the sub-command.</param>
/// <param name="subSecondName">The name of the second sub-command.</param>
/// <param name="thinkTime">The think time.</param>
/// <returns>The <see cref="BrowserCommandResult{TReturn}"/>.</returns>
public static BrowserCommandResult<bool> ClickCommandV2(this WebClient webClient, string name, string subname = null, string subSecondName = null, int thinkTime = Constants.DefaultThinkTime)
{
return webClient.Execute(GetOptions($"Click Command"), driver =>
{
var ribbon = driver.WaitUntilAvailable(
By.XPath(AppElements.Xpath[AppReference.CommandBar.Container]),
TimeSpan.FromSeconds(5));
if (ribbon == null)
{
ribbon = driver.WaitUntilAvailable(
By.XPath(AppElements.Xpath[AppReference.CommandBar.ContainerGrid]),
TimeSpan.FromSeconds(5),
"Unable to find the ribbon.");
}
var items = ribbon.FindElements(By.TagName("button"));
if (items.Any(x => x.GetAttribute("aria-label").Equals(name, StringComparison.OrdinalIgnoreCase)))
{
items.FirstOrDefault(x => x.GetAttribute("aria-label").Equals(name, StringComparison.OrdinalIgnoreCase)).Click(true);
driver.WaitForTransaction();
}
else
{
if (items.Any(x => x.GetAttribute("aria-label").Contains("More Commands", StringComparison.OrdinalIgnoreCase)))
{
items.FirstOrDefault(x => x.GetAttribute("aria-label").Contains("More Commands", StringComparison.OrdinalIgnoreCase)).Click(true);
driver.WaitForTransaction();
if (driver.HasElement(By.XPath(AppElements.Xpath[AppReference.CommandBar.Button].Replace("[NAME]", name))))
{
driver.FindElement(By.XPath(AppElements.Xpath[AppReference.CommandBar.Button].Replace("[NAME]", name))).Click(true);
driver.WaitForTransaction();
}
else
{
throw new InvalidOperationException($"No command with the name '{name}' exists inside of Commandbar.");
}
}
else
{
throw new InvalidOperationException($"No command with the name '{name}' exists inside of Commandbar.");
}
}
if (!string.IsNullOrEmpty(subname))
{
var submenu = driver.WaitUntilAvailable(By.XPath(AppElements.Xpath[AppReference.CommandBar.MoreCommandsMenu]));
var subbutton = submenu.FindElements(By.TagName("button")).FirstOrDefault(x => x.Text == subname);
if (subbutton != null)
{
subbutton.Click(true);
}
else
{
throw new InvalidOperationException($"No sub command with the name '{subname}' exists inside of Commandbar.");
}
if (!string.IsNullOrEmpty(subSecondName))
{
var subSecondmenu = driver.WaitUntilAvailable(By.XPath(AppElements.Xpath[AppReference.CommandBar.MoreCommandsMenu]));
var subSecondbutton = subSecondmenu.FindElements(By.TagName("button")).FirstOrDefault(x => x.Text == subSecondName);
if (subSecondbutton != null)
{
subSecondbutton.Click(true);
}
else
{
throw new InvalidOperationException($"No sub command with the name '{subSecondName}' exists inside of Commandbar.");
}
}
}
driver.WaitForTransaction();
return true;
});
}

private static BrowserCommandOptions GetOptions(string commandName)
{
return new BrowserCommandOptions(
Constants.DefaultTraceSource,
commandName,
Constants.DefaultRetryAttempts,
Constants.DefaultRetryDelay,
null,
true,
typeof(NoSuchElementException),
typeof(StaleElementReferenceException));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Capgemini.PowerApps.SpecFlowBindings.Steps
{
using Capgemini.PowerApps.SpecFlowBindings.Extensions;
using FluentAssertions;
using TechTalk.SpecFlow;

Expand All @@ -16,7 +17,9 @@ public class CommandBarSteps : PowerAppsStepDefiner
[When("I select the '(.*)' command")]
public static void WhenISelectTheCommand(string commandName)
{
XrmApp.CommandBar.ClickCommand(commandName);
// TODO: Replace with commented out code when new EasyRepro version available.
// XrmApp.CommandBar.ClickCommand(commandName);
Client.ClickCommandV2(commandName);
}

/// <summary>
Expand All @@ -27,7 +30,9 @@ public static void WhenISelectTheCommand(string commandName)
[When("I select the '([^']+)' command under the '([^']+)' flyout")]
public static void WhenISelectTheCommandUnderTheFlyout(string commandName, string flyoutName)
{
XrmApp.CommandBar.ClickCommand(flyoutName, commandName);
// TODO: Replace with commented out code when new EasyRepro version available.
// XrmApp.CommandBar.ClickCommand(flyoutName, commandName);
Client.ClickCommandV2(flyoutName, commandName);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ public static void WhenISelectTheCommandOnTheSubgrid(string commandName, string
{
Driver.WaitUntilVisible(
By.CssSelector($"div#dataSetRoot_{subGridName} button[aria-label=\"{commandName}\"]"));
XrmApp.Entity.SubGrid.ClickCommand(subGridName, commandName);

// TODO: Replace with commented out code when new EasyRepro version available.
// XrmApp.Entity.SubGrid.ClickCommand(subGridName, commandName);
Client.ClickSubGridCommandV2(subGridName, commandName);
}

/// <summary>
Expand Down Expand Up @@ -235,7 +238,9 @@ public static void WhenIClickTheFlyoutOnTheSubgrid(string flyoutName, string sub
{
Driver.WaitUntilVisible(By.CssSelector($"div#dataSetRoot_{subGridName} li[aria-label=\"{flyoutName}\"]"));

XrmApp.Entity.SubGrid.ClickCommand(subGridName, flyoutName);
// TODO: Replace with commented out code when new EasyRepro version available.
// XrmApp.Entity.SubGrid.ClickCommand(subGridName, flyoutName);
Client.ClickSubGridCommandV2(subGridName, flyoutName);
}

/// <summary>
Expand Down Expand Up @@ -290,7 +295,9 @@ public static void ThenICanNotSeeTheCommandOnTheFlyoutOfTheSubgrid(string comman
[When(@"I click the '([^']+)' command under the '([^']+)' flyout on the '([^']+)' subgrid")]
public static void WhenIClickTheCommandUnderTheFlyoutOnTheSubgrid(string commandName, string flyoutName, string subGridName)
{
XrmApp.Entity.SubGrid.ClickCommand(subGridName, flyoutName, commandName);
// TODO: Replace with commented out code when new EasyRepro version available.
// XrmApp.Entity.SubGrid.ClickCommand(subGridName, flyoutName, commandName);
Client.ClickSubGridCommandV2(subGridName, flyoutName, commandName);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.Dynamics365.UIAutomation.Api.UCI;
using Microsoft.Xrm.Tooling.Connector;
using System.IO;
using System.Reflection;
using TechTalk.SpecFlow;

namespace Capgemini.PowerApps.SpecFlowBindings.UiTests.Hooks
{
/// <summary>
/// Temporary hooks related to fixing broken EasyRepro selectors.
/// </summary>
[Binding]
public class EasyReproSelectorFixHooks : PowerAppsStepDefiner
{
[BeforeTestRun]
public static void FixQuickCreateMenuItemSelector()
{
AppElements.Xpath[AppReference.Navigation.QuickCreateMenuItems] = "//button[@role='menuitem']";
}

[BeforeTestRun]
public static void FixEntitySubGridOverflowButtonSelector()
{
AppElements.Xpath[AppReference.Entity.SubGridOverflowButton] = ".//button[contains(@aria-label, '[NAME]')]";
}

[BeforeTestRun]
public static void FixRelatedCommandBarButtonSelector()
{
AppElements.Xpath[AppReference.Related.CommandBarButton] = ".//button[contains(@aria-label, '[NAME]') and contains(@id,'SubGrid')]";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ Scenario: Click command in a related grid
When I open the related 'Activities' tab
And I click the 'Add Existing Activity' button on the related grid

Scenario: Assert a button is visible on a flyout in a related grid
Scenario: Assert a button is not visible on a flyout in a related grid
When I open the related 'Activities' tab
And I click the 'New Activity' button on the related grid
Then I should not see a 'Missing' button in the flyout on the related grid

Scenario: Assert a button is not visible on a flyout in a related grid
Scenario: Assert a button is visible on a flyout in a related grid
When I open the related 'Activities' tab
And I click the 'New Activity' button on the related grid
Then I should see a 'Task' button in the flyout on the related grid
23 changes: 18 additions & 5 deletions driver/src/data/deepInsertService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export default class DeepInsertService {
const singleNavProps = Object.keys(lookupRecordsByNavProp);
await Promise.all(singleNavProps.map(async (singleNavProp) => {
const res = await this.createLookupRecord(
logicalName, recordToCreate, lookupRecordsByNavProp, singleNavProp, dataByAlias,
logicalName, recordToCreate, lookupRecordsByNavProp, singleNavProp, dataByAlias, repo,
);
associatedRecords.push(res.record, ...res.associatedRecords);
}));
Expand Down Expand Up @@ -130,6 +130,7 @@ export default class DeepInsertService {
navigationPropertyMap: { [navigationProperty: string]: Record },
singleNavProp: string,
createdRecordsByAlias: { [alias: string]: Xrm.LookupValue },
repository: RecordRepository,
): Promise<DeepInsertResponse> {
const record = entity;
delete record[singleNavProp];
Expand All @@ -138,7 +139,7 @@ export default class DeepInsertService {
logicalName, singleNavProp,
);
const deepInsertResponse = await this.deepInsert(
entityName, navigationPropertyMap[singleNavProp], createdRecordsByAlias,
entityName, navigationPropertyMap[singleNavProp], createdRecordsByAlias, repository,
);
const entitySet = await this.metadataRepository.getEntitySetForEntity(entityName);
record[`${singleNavProp}@odata.bind`] = `/${entitySet}(${deepInsertResponse.record.reference.id})`;
Expand All @@ -159,7 +160,13 @@ export default class DeepInsertService {

if (DeepInsertService.isOneToManyMetadata(relMetadata)) {
return this.createOneToManyRecords(
relMetadata.ReferencingEntity, set, collNavProp, navPropMap, parent, refsByAlias,
relMetadata.ReferencingEntity,
set,
collNavProp,
navPropMap,
parent,
refsByAlias,
repository,
);
}

Expand All @@ -183,14 +190,15 @@ export default class DeepInsertService {
navPropMap: { [navProp: string]: Record[] },
parent: Xrm.LookupValue,
refsByAlias: { [alias: string]: Xrm.LookupValue },
repository: RecordRepository,
): Promise<{ alias?: string, reference: Xrm.LookupValue }[]> {
const oppNavProp = await this.metadataRepository
.getLookupPropertyForCollectionProperty(navProp);

const res = await Promise.all(navPropMap[navProp].map((oneToManyRecord) => {
// eslint-disable-next-line no-param-reassign
oneToManyRecord[`${oppNavProp}@odata.bind`] = `/${entitySet}(${parent.id})`;
return this.deepInsert(entity, oneToManyRecord, refsByAlias);
return this.deepInsert(entity, oneToManyRecord, refsByAlias, repository);
}));

return res.reduce<{ reference: Xrm.LookupValue; alias?: string; }[]>(
Expand All @@ -207,7 +215,12 @@ export default class DeepInsertService {
repository: RecordRepository,
): Promise<{ alias?: string, reference: Xrm.LookupValue }[]> {
const result = await Promise.all(navPropMap[navProp].map(async (manyToManyRecord) => {
const response = await this.deepInsert(entity, manyToManyRecord, createdRecordsByAlias);
const response = await this.deepInsert(
entity,
manyToManyRecord,
createdRecordsByAlias,
repository,
);

await repository.associateManyToManyRecords(
parent,
Expand Down
Loading

0 comments on commit 9e4df85

Please sign in to comment.