From 2f7a932b6eae7e1755f5308f19f83284827082e3 Mon Sep 17 00:00:00 2001 From: Alex Bance Date: Fri, 25 Jun 2021 13:53:33 +0100 Subject: [PATCH] fix: GetUser returning wrong users for alias (#89) * fix: GetUser(string alias) not returning new users The default behaviour wasn't returning a user for the given alias when `currentUser` was `true`. It would also cycle users when requesting the same alias in a single scenario with `currentUser` was set to `true` if the calls were separated by a call for another alias. * fix: exceptions creating base profiles Depending on the test runner, this code would fail. Synchronisation also needs to happen across processes in the case where build agents are running multiple test runs concurrently. * fix: CurrentUsers occasionally `null` Co-authored-by: Max Ewing --- .../Configuration/TestConfiguration.cs | 27 +++++++++--- .../Hooks/BeforeRunHooks.cs | 42 ++++++++++++++----- .../PowerAppsStepDefiner.cs | 1 + 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/bindings/src/Capgemini.PowerApps.SpecFlowBindings/Configuration/TestConfiguration.cs b/bindings/src/Capgemini.PowerApps.SpecFlowBindings/Configuration/TestConfiguration.cs index d6b28e9..c4121dd 100644 --- a/bindings/src/Capgemini.PowerApps.SpecFlowBindings/Configuration/TestConfiguration.cs +++ b/bindings/src/Capgemini.PowerApps.SpecFlowBindings/Configuration/TestConfiguration.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Configuration; using System.Linq; - using System.Threading; using YamlDotNet.Serialization; /// @@ -19,8 +18,9 @@ public class TestConfiguration private const string GetUserException = "Unable to retrieve user configuration. Please ensure a user with the given alias exists in the config."; + private static readonly Dictionary CurrentUsers = new Dictionary(); + private readonly object userEnumeratorsLock = new object(); - private readonly ThreadLocal currentUser = new ThreadLocal(); private Dictionary> userEnumerators; private string profilesBasePath; @@ -107,9 +107,9 @@ public Uri GetTestUrl() /// The user configuration. public UserConfiguration GetUser(string userAlias, bool useCurrentUser = true) { - if (useCurrentUser && this.currentUser.Value != null) + if (useCurrentUser && CurrentUsers.ContainsKey(userAlias)) { - return this.currentUser.Value; + return CurrentUsers[userAlias]; } try @@ -124,7 +124,14 @@ public UserConfiguration GetUser(string userAlias, bool useCurrentUser = true) aliasEnumerator.MoveNext(); } - this.currentUser.Value = aliasEnumerator.Current; + if (CurrentUsers.ContainsKey(userAlias)) + { + CurrentUsers[userAlias] = aliasEnumerator.Current; + } + else + { + CurrentUsers.Add(userAlias, aliasEnumerator.Current); + } } } catch (Exception ex) @@ -132,7 +139,15 @@ public UserConfiguration GetUser(string userAlias, bool useCurrentUser = true) throw new ConfigurationErrorsException($"{GetUserException} User: {userAlias}", ex); } - return this.currentUser.Value; + return CurrentUsers[userAlias]; + } + + /// + /// Called internally between scenarios to reset thread state. + /// + internal void Flush() + { + CurrentUsers.Clear(); } } } diff --git a/bindings/src/Capgemini.PowerApps.SpecFlowBindings/Hooks/BeforeRunHooks.cs b/bindings/src/Capgemini.PowerApps.SpecFlowBindings/Hooks/BeforeRunHooks.cs index a035b5b..339fc82 100644 --- a/bindings/src/Capgemini.PowerApps.SpecFlowBindings/Hooks/BeforeRunHooks.cs +++ b/bindings/src/Capgemini.PowerApps.SpecFlowBindings/Hooks/BeforeRunHooks.cs @@ -2,11 +2,11 @@ { using System.IO; using System.Linq; + using System.Threading; using System.Threading.Tasks; using Capgemini.PowerApps.SpecFlowBindings.Configuration; using Capgemini.PowerApps.SpecFlowBindings.Steps; using Microsoft.Dynamics365.UIAutomation.Api.UCI; - using Microsoft.Dynamics365.UIAutomation.Browser; using TechTalk.SpecFlow; /// @@ -31,17 +31,39 @@ public static void BaseProfileSetup() var profileDirectory = UserProfileDirectories[username]; var baseDirectory = Path.Combine(profileDirectory, "base"); - Directory.CreateDirectory(baseDirectory); + // SpecFlow isolation settings may run scenarios in different processes or AppDomains and [BeforeTestRun] runs per thread. Lock statement insufficient. + using (var mutex = new Mutex(true, $"{nameof(BaseProfileSetup)}-{username}", out var createdNew)) + { + if (!createdNew) + { + mutex.WaitOne(); + } - var userBrowserOptions = (BrowserOptionsWithProfileSupport)TestConfig.BrowserOptions.Clone(); - userBrowserOptions.ProfileDirectory = baseDirectory; - userBrowserOptions.Headless = true; + if (Directory.Exists(baseDirectory)) + { + mutex.ReleaseMutex(); + return; + } - var webClient = new WebClient(userBrowserOptions); - using (new XrmApp(webClient)) - { - var user = TestConfig.Users.First(u => u.Username == username); - LoginSteps.Login(webClient.Browser.Driver, TestConfig.GetTestUrl(), user.Username, user.Password); + try + { + Directory.CreateDirectory(baseDirectory); + + var userBrowserOptions = (BrowserOptionsWithProfileSupport)TestConfig.BrowserOptions.Clone(); + userBrowserOptions.ProfileDirectory = baseDirectory; + userBrowserOptions.Headless = true; + + var webClient = new WebClient(userBrowserOptions); + using (new XrmApp(webClient)) + { + var user = TestConfig.Users.First(u => u.Username == username); + LoginSteps.Login(webClient.Browser.Driver, TestConfig.GetTestUrl(), user.Username, user.Password); + } + } + finally + { + mutex.ReleaseMutex(); + } } }); } diff --git a/bindings/src/Capgemini.PowerApps.SpecFlowBindings/PowerAppsStepDefiner.cs b/bindings/src/Capgemini.PowerApps.SpecFlowBindings/PowerAppsStepDefiner.cs index cf7eeea..3ce5f67 100644 --- a/bindings/src/Capgemini.PowerApps.SpecFlowBindings/PowerAppsStepDefiner.cs +++ b/bindings/src/Capgemini.PowerApps.SpecFlowBindings/PowerAppsStepDefiner.cs @@ -222,6 +222,7 @@ protected static void Quit() xrmApp = null; client = null; testDriver = null; + testConfig?.Flush(); if (!string.IsNullOrEmpty(currentProfileDirectory) && Directory.Exists(currentProfileDirectory)) {