Skip to content

Commit

Permalink
feat: 2021 release wave 2 (#119)
Browse files Browse the repository at this point in the history
Co-authored-by: Mike Andrews <mja.hlv@gmail.com>
Co-authored-by: Osagie Okoedo <osagie.okoedo@capgemini.com>
Co-authored-by: Tom Ashworth <tom.ashworth@capgemini.com>
Co-authored-by: Douglas <leroy.douglas@capgemini.com>
  • Loading branch information
5 people authored Aug 4, 2022
1 parent 0dccf41 commit dcc353e
Show file tree
Hide file tree
Showing 29 changed files with 18,075 additions and 395 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Installing the NuGet package creates a _power-apps-bindings.yml_ file in your pr
```yaml
url: SPECFLOW_POWERAPPS_URL # mandatory
useProfiles: false # optional - defaults to false if not set
deleteTestData: true # optional - defaults to true if not set
browserOptions: # optional - will use default EasyRepro options if not set
browserType: Chrome
headless: true
Expand Down Expand Up @@ -138,6 +139,8 @@ These bindings look for a corresponding JSON file in a _data_ folder in the root
with a difference.json
```

The deleteTestData property in the power-apps-bindings.yml file can be set to specify whether you want records created via these bindings to be deleted after a scenario has ran. You may wish to override the default value and retain these e.g. to aid in diagnosing failures.

If you are using the binding which creates data as someone other than the current user, you will need the following configuration to be present:

- a user with a matching alias in the `users` array that has the `username` set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
</Target>

<ItemGroup>
<PackageReference Include="Dynamics365.UIAutomation.Api" Version="9.2.21014.138" />
<PackageReference Include="Dynamics365.UIAutomation.Api" Version="9.2.21101.119-RW2-Preview" />
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.Build.Tasks.Git" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ public TestConfiguration()
[YamlMember(Alias = "useProfiles")]
public bool UseProfiles { get; set; } = false;

/// <summary>
/// Gets or sets a value indicating whether to delete test data.
/// </summary>
[YamlMember(Alias = "deleteTestData")]
public bool DeleteTestData { get; set; } = true;

/// <summary>
/// Gets or sets the base path where the user profiles are stored.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static int GetRecordIndexById(this SubGrid subGrid, string subgridName, I
throw new ArgumentNullException(nameof(driver));
}

driver.WaitUntilAvailable(By.XPath(AppElements.Xpath[AppReference.Grid.Container]));
driver.WaitUntilAvailable(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridContents].Replace("[NAME]", subgridName)));

var index = (long)driver.ExecuteScript(
$"return Xrm.Page.getControl(\"{subgridName}\").getGrid().getRows().get().findIndex(row => row.getData().getEntity().getId() == \"{recordId.ToString("B").ToUpper(CultureInfo.CurrentCulture)}\")");
Expand All @@ -52,8 +52,7 @@ public static void HighlightRecord(this SubGrid subGrid, string subgridName, IWe

var subGridElement = driver.FindElement(
By.XPath(AppElements.Xpath[AppReference.Entity.SubGridContents].Replace("[NAME]", subgridName)));

var rows = subGridElement.FindElements(By.CssSelector("div.wj-row[role=row][data-lp-id]"));
var rows = subGridElement.FindElements(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridRows]));

if (rows.Count == 0)
{
Expand All @@ -65,7 +64,7 @@ public static void HighlightRecord(this SubGrid subGrid, string subgridName, IWe
throw new IndexOutOfRangeException($"Subgrid {subgridName} record count: {rows.Count}. Expected: {index + 1}");
}

rows[index].FindElement(By.TagName("div")).Click();
rows[index].FindElements(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridCells]))[0].Click();
driver.WaitForTransaction();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ public static void TestCleanup()
{
try
{
TestDriver.DeleteTestData();
if (TestConfig.DeleteTestData)
{
TestDriver.DeleteTestData();
}
}
catch (WebDriverException)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Capgemini.PowerApps.SpecFlowBindings.Configuration;
using Capgemini.PowerApps.SpecFlowBindings.Steps;
using Microsoft.Dynamics365.UIAutomation.Api.UCI;
using Microsoft.Dynamics365.UIAutomation.Browser;
using TechTalk.SpecFlow;

/// <summary>
Expand Down Expand Up @@ -54,10 +55,10 @@ public static void BaseProfileSetup()
userBrowserOptions.Headless = true;
var webClient = new WebClient(userBrowserOptions);
using (new XrmApp(webClient))
using (var app = new XrmApp(webClient))
{
var user = TestConfig.Users.First(u => u.Username == username);
LoginSteps.Login(webClient.Browser.Driver, TestConfig.GetTestUrl(), user.Username, user.Password);
app.OnlineLogin.Login(TestConfig.GetTestUrl(), user.Username.ToSecureString(), user.Password.ToSecureString());
}
}
finally
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ protected static string AccessToken
{
var hostSegments = TestConfig.GetTestUrl().Host.Split('.');

return GetApp().AcquireTokenForClient(new string[] { $"https://{hostSegments[0]}.api.{hostSegments[1]}.dynamics.com//.default" })
return GetApp()
.AcquireTokenForClient(new string[] { $"https://{hostSegments[0]}.api.{hostSegments[1]}.dynamics.com//.default" })
.ExecuteAsync()
.Result.AccessToken;
}
Expand Down Expand Up @@ -212,12 +213,21 @@ protected static IDictionary<string, string> UserProfileDirectories
/// </summary>
protected static void Quit()
{
var driver = client?.Browser?.Driver;

xrmApp?.Dispose();
// Try to dispose, and catch web driver errors that can occur on disposal. Retry the disposal if these occur. Trap the final exception and continue the disposal process.
var polly = Policy
.Handle<WebDriverException>()
.Retry(3, (ex, i) =>
{
Console.WriteLine(ex.Message);
})
.ExecuteAndCapture(() =>
{
xrmApp?.Dispose();
// Ensuring that the driver gets disposed. Previously we were left with orphan processes and were unable to clean up profile folders.
driver?.Dispose();
// Ensuring that the driver gets disposed. Previously we were left with orphan processes and were unable to clean up profile folders. We cannot rely on xrmApp.Dispose to properly dispose of the web driver.
var driver = client?.Browser?.Driver;
driver?.Dispose();
});

xrmApp = null;
client = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ public static void ThenICanNotSeeTheField(string fieldName)
[Then("the status of the record is (active|inactive)")]
public static void ThenTheStatusOfTheRecordIs(string status)
{
XrmApp.Entity.GetFooterStatusValue().Should().BeEquivalentTo(status);
XrmApp.Entity.GetFormState().Should().BeEquivalentTo(status);
}

private static void SetFieldValue(string fieldName, string fieldValue, string fieldType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public class EntitySubGridSteps : PowerAppsStepDefiner
[When(@"I click the '(.*)' command on the '(.*)' subgrid")]
public static void WhenISelectTheCommandOnTheSubgrid(string commandName, string subGridName)
{
Driver.WaitUntilVisible(
By.CssSelector($"div#dataSetRoot_{subGridName} button[aria-label=\"{commandName}\"]"));
Driver.WaitUntilAvailable(
By.XPath(AppElements.Xpath[AppReference.Entity.SubGridContents].Replace("[NAME]", subGridName)));

XrmApp.Entity.SubGrid.ClickCommand(subGridName, commandName);
}
Expand Down Expand Up @@ -220,10 +220,13 @@ public static void ThenTheSubgridContainsRecordsWithInTheField(string subGridNam
[Then(@"I can see the '(.*)' command on the '(.*)' subgrid")]
public static void ThenICanSeeTheCommandOnTheSubgrid(string commandName, string subGridName)
{
Driver.WaitUntilVisible(
By.CssSelector($"div#dataSetRoot_{subGridName} button[aria-label=\"{commandName}\"]"),
new TimeSpan(0, 0, 5),
$"Could not find the {commandName} command on the {subGridName} subgrid.");
Driver
.WaitUntilAvailable(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridContents].Replace("[NAME]", subGridName)))
.WaitUntilAvailable(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridCommandBar]))
.WaitUntilVisible(
By.XPath(AppElements.Xpath[AppReference.Entity.SubGridCommandLabel].Replace("[NAME]", commandName)),
new TimeSpan(0, 0, 5),
$"Could not find the {commandName} command on the {subGridName} subgrid.");
}

/// <summary>
Expand All @@ -234,8 +237,6 @@ public static void ThenICanSeeTheCommandOnTheSubgrid(string commandName, string
[When(@"I click the '([^']+)' flyout on the '([^']+)' subgrid")]
public static void WhenIClickTheFlyoutOnTheSubgrid(string flyoutName, string subGridName)
{
Driver.WaitUntilVisible(By.CssSelector($"div#dataSetRoot_{subGridName} li[aria-label=\"{flyoutName}\"]"));

XrmApp.Entity.SubGrid.ClickCommand(subGridName, flyoutName);
}

Expand All @@ -247,8 +248,11 @@ public static void WhenIClickTheFlyoutOnTheSubgrid(string flyoutName, string sub
[Then(@"I can not see the '(.*)' command on the '(.*)' subgrid")]
public static void ThenICanNotSeeTheCommandOnTheSubgrid(string commandName, string subGridName)
{
Driver.WaitUntilVisible(
By.CssSelector($"div#dataSetRoot_{subGridName} button[aria-label=\"{commandName}\"]"),
Driver
.WaitUntilAvailable(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridContents].Replace("[NAME]", subGridName)))
.WaitUntilAvailable(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridCommandBar]))
.WaitUntilVisible(
By.XPath(AppElements.Xpath[AppReference.Entity.SubGridCommandLabel].Replace("[NAME]", commandName)),
new TimeSpan(0, 0, 5))
.Should().BeNull();
}
Expand All @@ -260,10 +264,12 @@ public static void ThenICanNotSeeTheCommandOnTheSubgrid(string commandName, stri
[Then(@"I can see the '(.*)' command on the flyout of the subgrid")]
public static void ThenICanSeeTheCommandOnTheFlyoutOfTheSubgrid(string commandName)
{
Driver.WaitUntilVisible(
By.CssSelector($"#__flyoutRootNode button[aria-label$='{commandName}']"),
new TimeSpan(0, 0, 10),
$"Could not find the {commandName} command on the flyout of the subgrid.");
Driver
.WaitUntilAvailable(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridOverflowContainer]))
.WaitUntilVisible(
By.XPath(AppElements.Xpath[AppReference.Entity.SubGridOverflowButton].Replace("[NAME]", commandName)),
new TimeSpan(0, 0, 10),
$"Could not find the {commandName} command on the flyout of the subgrid.");
}

/// <summary>
Expand All @@ -274,10 +280,12 @@ public static void ThenICanSeeTheCommandOnTheFlyoutOfTheSubgrid(string commandNa
public static void ThenICanNotSeeTheCommandOnTheFlyoutOfTheSubgrid(string commandName)
{
Driver
.Invoking(d => d.WaitUntilVisible(
By.CssSelector($"#__flyoutRootNode button[aria-label$=\"{commandName}\"]"),
new TimeSpan(0, 0, 1),
$"Could not find the {commandName} command on the flyout of the subgrid."))
.Invoking(d => d
.WaitUntilAvailable(By.XPath(AppElements.Xpath[AppReference.Entity.SubGridOverflowContainer]))
.WaitUntilVisible(
By.XPath(AppElements.Xpath[AppReference.Entity.SubGridOverflowButton].Replace("[NAME]", commandName)),
new TimeSpan(0, 0, 1),
$"Could not find the {commandName} command on the flyout of the subgrid."))
.Should()
.Throw<Exception>();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,6 @@ public static void WhenIPerformASearch(string filterValue)
XrmApp.GlobalSearch.Search(filterValue);
}

/// <summary>
/// Performs an advanced search using the filter attribute and a filter value.
/// </summary>
/// <param name="filterValue">Attribute filter value.</param>
[When("I change the search type using the filter '(.*)'")]
public static void WhenIChangeTheSearchType(string filterValue)
{
XrmApp.GlobalSearch.ChangeSearchType(filterValue);
}

/// <summary>
/// Open a record from a global search at a certain row.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,6 @@
[Binding]
public class LoginSteps : PowerAppsStepDefiner
{
/// <summary>
/// Logs into the given instance with the given credentials.
/// </summary>
/// <param name="driver">WebDriver used to imitate user actions.</param>
/// <param name="orgUrl">The <see cref="Uri"/> of the instance.</param>
/// <param name="username">The username of the user.</param>
/// <param name="password">The password of the user.</param>
public static void Login(IWebDriver driver, Uri orgUrl, string username, string password)
{
driver.Navigate().GoToUrl(orgUrl);
driver.ClickIfVisible(By.Id("otherTile"));

bool waitForMainPage = WaitForMainPage(driver);

if (!waitForMainPage)
{
IWebElement usernameInput = driver.WaitUntilAvailable(By.XPath(Elements.Xpath[Reference.Login.UserId]), 30.Seconds());
usernameInput.SendKeys(username);
usernameInput.SendKeys(Keys.Enter);

IWebElement passwordInput = driver.WaitUntilClickable(By.XPath(Elements.Xpath[Reference.Login.LoginPassword]), 30.Seconds());
passwordInput.SendKeys(password);
passwordInput.Submit();

var staySignedIn = driver.WaitUntilClickable(By.XPath(Elements.Xpath[Reference.Login.StaySignedIn]), 10.Seconds());
if (staySignedIn != null)
{
staySignedIn.Click();
}

WaitForMainPage(driver, 30.Seconds());
}
}

/// <summary>
/// Logs in to a given app as a given user.
/// </summary>
Expand All @@ -64,13 +30,16 @@ public static void GivenIAmLoggedInToTheAppAs(string appName, string userAlias)
}

var url = TestConfig.GetTestUrl();
Login(Driver, url, user.Username, user.Password);

XrmApp.OnlineLogin.Login(url, user.Username.ToSecureString(), user.Password.ToSecureString());

if (!url.Query.Contains("appid"))
{
XrmApp.Navigation.OpenApp(appName);
}

Driver.WaitForTransaction();

CloseTeachingBubbles();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static void WhenISelectInTheLookupDialog(string searchTerm)
[When("I click Add in the lookup dialog")]
public static void WhenIClickAddInTheLookupDialog()
{
var container = Driver.WaitUntilAvailable(By.CssSelector("div[id=\"lookupDialogContainer\"]"));
var container = Driver.WaitUntilAvailable(By.CssSelector("div[id=\"lookupDialogFooterContainer\"]"));

container.FindElement(By.CssSelector("button[data-id*=\"lookupDialogSaveBtn\"]"))
.Click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public static void ThenICanSeeTheGroup(string groupName)
var groupNameWithoutWhitespace = groupName?.Replace(" ", string.Empty);

Driver
.WaitUntilAvailable(By.XPath($"//span[@data-id='sitemap-sitemapAreaGroup-{groupNameWithoutWhitespace}']"))
.WaitUntilAvailable(By.XPath($"//h3[@data-id='sitemap-sitemapAreaGroup-{groupNameWithoutWhitespace}']"))
.Text
.Should().Contain(groupName);
}
Expand Down
14 changes: 7 additions & 7 deletions bindings/src/Capgemini.PowerApps.SpecFlowBindings/TestDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,23 @@ public void InjectOnPage(string authToken)
{
var scriptBuilder = new StringBuilder();
scriptBuilder.AppendLine(File.ReadAllText(this.FilePath));
scriptBuilder.AppendLine($@"var recordRepository = new {LibraryNamespace}.CurrentUserRecordRepository(Xrm.WebApi.online);
var metadataRepository = new {LibraryNamespace}.MetadataRepository(Xrm.WebApi.online);
var deepInsertService = new {LibraryNamespace}.DeepInsertService(metadataRepository, recordRepository);");
scriptBuilder.AppendLine($@"top.recordRepository = new {LibraryNamespace}.CurrentUserRecordRepository(Xrm.WebApi.online);
top.metadataRepository = new {LibraryNamespace}.MetadataRepository(Xrm.WebApi.online);
top.deepInsertService = new {LibraryNamespace}.DeepInsertService(top.metadataRepository, top.recordRepository);");

if (!string.IsNullOrEmpty(authToken))
{
scriptBuilder.AppendLine(
$@"var appUserRecordRepository = new {LibraryNamespace}.AuthenticatedRecordRepository(metadataRepository, '{authToken}');
var dataManager = new {LibraryNamespace}.DataManager(recordRepository, deepInsertService, [new {LibraryNamespace}.FakerPreprocessor()], appUserRecordRepository);");
$@"top.appUserRecordRepository = new {LibraryNamespace}.AuthenticatedRecordRepository(top.metadataRepository, '{authToken}');
top.dataManager = new {LibraryNamespace}.DataManager(top.recordRepository, top.deepInsertService, [new {LibraryNamespace}.FakerPreprocessor()], top.appUserRecordRepository);");
}
else
{
scriptBuilder.AppendLine(
$"var dataManager = new {LibraryNamespace}.DataManager(recordRepository, deepInsertService, [new {LibraryNamespace}.FakerPreprocessor()]);");
$"top.dataManager = new {LibraryNamespace}.DataManager(top.recordRepository, top.deepInsertService, [new {LibraryNamespace}.FakerPreprocessor()]);");
}

scriptBuilder.AppendLine($"{TestDriverReference} = new {LibraryNamespace}.Driver(dataManager);");
scriptBuilder.AppendLine($"{TestDriverReference} = new {LibraryNamespace}.Driver(top.dataManager);");

this.javascriptExecutor.ExecuteScript(scriptBuilder.ToString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<PackageReference Include="Microsoft.CrmSdk.XrmTooling.CoreAssembly" Version="9.1.0.64" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="103.0.5060.5300" />
<PackageReference Include="SpecFlow" Version="3.5.14" />
<PackageReference Include="SpecFlow.MsTest" Version="3.5.14" />
<PackageReference Include="SpecFlow.Tools.MsBuild.Generation" Version="3.5.14" />
Expand Down
Loading

0 comments on commit dcc353e

Please sign in to comment.