Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow bypassing/streamlining of login using browser profiles #87

Merged
merged 41 commits into from
Jun 24, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
fecaac5
Inital logic commit to enable use of Chrome Profiles
Jun 15, 2021
578f047
Refactored work to apply to both Firefox & Chrome. Added Workaround f…
Jun 15, 2021
5851084
Tidied up and added switch to disable/enable the use of profiles
Jun 15, 2021
19f79c6
Reverted project version used in testing
Jun 15, 2021
e135286
Updated method name to not specifically refer to chrome
Jun 15, 2021
83d052c
Fixed a few naming &formatting issues
Jun 15, 2021
7aa4ce6
Updated inline with review comments
Jun 16, 2021
2772a08
Refactored custom login logic
Jun 16, 2021
836e0f1
Tweaked connection string
Jun 16, 2021
c28fa91
Removed debugging steps
Jun 16, 2021
28a52a9
Reworked the setting of userProfileDirectories
Jun 16, 2021
f116ad5
Removed logging
Jun 16, 2021
5b61846
Removed WaitForTransaction calls
Jun 16, 2021
26669ed
Updated readme
Jun 16, 2021
d94c733
Added logic to create a copy of the chrome profile for each test - WIP
Jun 18, 2021
84acbb1
Updated to reduce the length of the profile file paths
Jun 18, 2021
36a450c
Added hook to cleanup used profiles
Jun 18, 2021
7c252ba
Switched to use the default dir for profiles when building in ADO
Jun 18, 2021
d87365b
Fixed issue where variable wasn't set
Jun 18, 2021
4029d54
Added polly to retry the folder delete - as no way to tell when the d…
Jun 18, 2021
9c604fe
Updated after scenario hooks to handle errors
Jun 21, 2021
9da076c
Updated to use events rather than run order
Jun 21, 2021
4f03282
Updated to give up deleting profile folder after 4 attempts
Jun 21, 2021
cb6739a
Disabled the use of profiles - testing ADO pipeline
Jun 21, 2021
be67e0f
fix: multi-threading issues
ewingjm Jun 22, 2021
deb1939
Fixed errors when running tests locally
Jun 22, 2021
ab44036
Fixed issue with config file
Jun 22, 2021
6870368
fix: issue with locking and getting environment variables
Jun 22, 2021
b641940
fix: issues deleting profile directories
ewingjm Jun 22, 2021
27ef9ee
fix: condition for deleting profile directory
ewingjm Jun 22, 2021
eb8bacb
fix: errors copying and deleting profile directories
ewingjm Jun 22, 2021
b6c7296
ci: fix issues copying to new profile folder
ewingjm Jun 23, 2021
4786d4b
fix: force headless during profile setup
Jun 23, 2021
de7cf9b
fix: remove unnecessary lock
ewingjm Jun 23, 2021
229cbb9
Merge branch 'AB/bypass-login' of https://github.com/bancey/powerapps…
ewingjm Jun 23, 2021
c73ede7
fix: reported code smell
Jun 23, 2021
64b3e62
Resolved build issue
Jun 23, 2021
d789047
fix: issues disposing driver
ewingjm Jun 23, 2021
8ee2af8
ci: ignore test failing due to EasyRepro issue
ewingjm Jun 23, 2021
7571a41
docs: fix Markdown linting issues in README
ewingjm Jun 24, 2021
7ac87f6
fix: creates user profile for users without passwords
ewingjm Jun 24, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace Capgemini.PowerApps.SpecFlowBindings.Configuration
{
using Microsoft.Dynamics365.UIAutomation.Browser;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Firefox;

/// <summary>
/// Extends the EasyRepro <see cref="BrowserOptions"/> class with additonal support for chrome profiles.
/// </summary>
public class BrowserOptionsWithProfileSupport : BrowserOptions
{
/// <summary>
/// Gets or sets the directory to use as the user profile.
/// </summary>
public string ProfileDirectory { get; set; }

/// <inheritdoc/>
public override ChromeOptions ToChrome()
{
var options = base.ToChrome();
if (!string.IsNullOrEmpty(this.ProfileDirectory))
{
options.AddArgument($"--user-data-dir={this.ProfileDirectory}");
bancey marked this conversation as resolved.
Show resolved Hide resolved
}

return options;
}

/// <inheritdoc/>
public override FirefoxOptions ToFireFox()
{
var options = base.ToFireFox();
if (!string.IsNullOrEmpty(this.ProfileDirectory))
{
this.ProfileDirectory = $"{this.ProfileDirectory}\\firefox";
bancey marked this conversation as resolved.
Show resolved Hide resolved
options.AddArgument($"-profile \"{this.ProfileDirectory}\"");
}

return options;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using Microsoft.Dynamics365.UIAutomation.Browser;
using YamlDotNet.Serialization;

/// <summary>
Expand All @@ -24,7 +23,7 @@ public class TestConfiguration
/// </summary>
public TestConfiguration()
{
this.BrowserOptions = new BrowserOptions();
this.BrowserOptions = new BrowserOptionsWithProfileSupport();
}

/// <summary>
Expand All @@ -33,11 +32,17 @@ public TestConfiguration()
[YamlMember(Alias = "url")]
public string Url { private get; set; }

/// <summary>
/// Gets or sets a value indicating whether to use profiles.
/// </summary>
[YamlMember(Alias = "useProfiles")]
public bool UseProfiles { get; set; } = false;

/// <summary>
/// Gets or sets the browser options to use for running tests.
/// </summary>
[YamlMember(Alias = "browserOptions")]
public BrowserOptions BrowserOptions { get; set; }
public BrowserOptionsWithProfileSupport BrowserOptions { get; set; }

/// <summary>
/// Gets or sets users that tests can be run as.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace Capgemini.PowerApps.SpecFlowBindings.Extensions
{
using Microsoft.Dynamics365.UIAutomation.Browser;

/// <summary>
/// Provides extension methods on <see cref="BrowserType"/>.
/// </summary>
public static class BrowserTypeExtensions
{
/// <summary>
/// Determines if the given browser type supports profiles.
/// </summary>
/// <param name="type">The <see cref="BrowserType"/> to check.</param>
/// <returns>true if the browser supports profiles otherwise false.</returns>
public static bool SupportsProfiles(this BrowserType type)
{
switch (type)
{
case BrowserType.IE:
return false;
case BrowserType.Chrome:
return true;
case BrowserType.Firefox:
return true;
case BrowserType.Edge:
return false;
case BrowserType.Remote:
return false;
default:
return false;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
namespace Capgemini.PowerApps.SpecFlowBindings
{
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Reflection;
using Capgemini.PowerApps.SpecFlowBindings.Configuration;
using Capgemini.PowerApps.SpecFlowBindings.Extensions;
using Microsoft.Dynamics365.UIAutomation.Api.UCI;
using Microsoft.Identity.Client;
using OpenQA.Selenium;
Expand Down Expand Up @@ -32,6 +34,8 @@ public abstract class PowerAppsStepDefiner
[ThreadStatic]
private static XrmApp xrmApp;

private static Dictionary<string, string> userProfileDirectories;

/// <summary>
/// Gets access token used to authenticate as the application user configured for testing.
/// </summary>
Expand Down Expand Up @@ -73,6 +77,11 @@ protected static TestConfiguration TestConfig
testConfig.BrowserOptions.DriversPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
}
}

if (testConfig.UseProfiles && testConfig.BrowserOptions.BrowserType.SupportsProfiles())
bancey marked this conversation as resolved.
Show resolved Hide resolved
{
GenerateUserProfiles(testConfig.Users);
}
}

return testConfig;
Expand Down Expand Up @@ -116,6 +125,22 @@ protected static ITestDriver TestDriver
}
}

/// <summary>
/// Gets the directories for the chrome profiles if the brower type is chrome.
/// </summary>
protected static Dictionary<string, string> UserProfileDirectories
{
get
{
if (testConfig.BrowserOptions.BrowserType.SupportsProfiles())
{
return userProfileDirectories;
}

throw new ArgumentException($"The {testConfig.BrowserOptions.BrowserType} does not support profiles.");
bancey marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// <summary>
/// Performs any cleanup necessary when quitting the WebBrowser.
/// </summary>
Expand Down Expand Up @@ -152,5 +177,37 @@ private static IConfidentialClientApplication GetApp()

return app;
}

/// <summary>
/// Creates a directory for each user to store information specific to a chrome profile.
/// </summary>
/// <param name="users">List of user to create profile directories for.</param>
private static void GenerateUserProfiles(List<UserConfiguration> users)
bancey marked this conversation as resolved.
Show resolved Hide resolved
{
userProfileDirectories = new Dictionary<string, string>();
string profilesDir = $"{Directory.GetCurrentDirectory()}\\profiles";
bancey marked this conversation as resolved.
Show resolved Hide resolved
bancey marked this conversation as resolved.
Show resolved Hide resolved
CreateDirectoryIfNotExists(profilesDir);

foreach (var user in users)
{
if (userProfileDirectories.ContainsKey(user.Username))
{
continue;
}

var userProfileDir = $"{profilesDir}\\{user.Username}";
CreateDirectoryIfNotExists(userProfileDir);

userProfileDirectories.Add(user.Username, userProfileDir);
}
}

private static void CreateDirectoryIfNotExists(string directoryPath)
bancey marked this conversation as resolved.
Show resolved Hide resolved
{
if (!Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
namespace Capgemini.PowerApps.SpecFlowBindings.Steps
{
using System;
using Capgemini.PowerApps.SpecFlowBindings.Extensions;
using Microsoft.Dynamics365.UIAutomation.Api.UCI;
using Microsoft.Dynamics365.UIAutomation.Browser;
using OpenQA.Selenium;
using TechTalk.SpecFlow;
Expand All @@ -20,6 +23,12 @@ public static void GivenIAmLoggedInToTheAppAs(string appName, string userAlias)
{
var user = TestConfig.GetUser(userAlias);

if (TestConfig.UseProfiles && TestConfig.BrowserOptions.BrowserType.SupportsProfiles())
{
TestConfig.BrowserOptions.ProfileDirectory = UserProfileDirectories[user.Username];
ForgetExistingAccounts(TestConfig.GetTestUrl());
}

XrmApp.OnlineLogin.Login(
TestConfig.GetTestUrl(),
user.Username.ToSecureString(),
Expand All @@ -38,5 +47,18 @@ private static void CloseTeachingBubbles()
closeButton.Click();
}
}

// This logic is only required as there is currently a defect in Easy Repro which causes it to not handle the "Pick and Account" dialog
// This can be removed when the PR into EasyRepro is merged. https://github.com/microsoft/EasyRepro/pull/1143
private static void ForgetExistingAccounts(Uri orgUrl)
{
Client.Browser.Driver.Navigate().GoToUrl(orgUrl);

foreach (var existingAccountMenuButton in Driver.FindElements(By.ClassName("tile-menu")))
{
existingAccountMenuButton.Click();
Driver.FindElement(By.Id("forgetLink")).Click();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
url:
useProfiles: false
browserOptions:
browserType:
users:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
url: POWERAPPS_SPECFLOW_BINDINGS_TEST_URL
useProfiles: true
browserOptions:
browserType: Chrome
headless: true
Expand Down