diff --git a/src/DocumentationBrowserViewExtension/DocumentationBrowserView.xaml.cs b/src/DocumentationBrowserViewExtension/DocumentationBrowserView.xaml.cs index 766261cab34..b3990d9d288 100644 --- a/src/DocumentationBrowserViewExtension/DocumentationBrowserView.xaml.cs +++ b/src/DocumentationBrowserViewExtension/DocumentationBrowserView.xaml.cs @@ -7,6 +7,7 @@ using System.Windows.Controls; using Dynamo.Logging; using Dynamo.Utilities; +using DynamoUtilities; using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.Wpf; @@ -21,7 +22,7 @@ public partial class DocumentationBrowserView : UserControl, IDisposable private readonly DocumentationBrowserViewModel viewModel; private const string VIRTUAL_FOLDER_MAPPING = "appassets"; static readonly string HTML_IMAGE_PATH_PREFIX = @"http://"; - private bool hasBeenInitialized; + internal AsyncMethodState initState = AsyncMethodState.NotStarted; private ScriptingObject comScriptingObject; private string fontStylePath = "Dynamo.Wpf.Views.GuidedTour.HtmlPages.Resources.ArtifaktElement-Regular.woff"; @@ -104,26 +105,22 @@ private void ShouldAllowNavigation(object sender, CoreWebView2NavigationStarting /// public void NavigateToPage(Uri link) { - InitializeAsync(); + Dispatcher.Invoke(InitializeAsync); } protected virtual void Dispose(bool disposing) { // Cleanup this.viewModel.LinkChanged -= NavigateToPage; - this.documentationBrowser.NavigationStarting -= ShouldAllowNavigation; - this.documentationBrowser.DpiChanged -= DocumentationBrowser_DpiChanged; - - if (this.documentationBrowser.CoreWebView2 != null) + if (this.documentationBrowser != null) { - this.documentationBrowser.CoreWebView2.WebMessageReceived -= CoreWebView2OnWebMessageReceived; - } + this.documentationBrowser.NavigationStarting -= ShouldAllowNavigation; + this.documentationBrowser.DpiChanged -= DocumentationBrowser_DpiChanged; + if (this.documentationBrowser.CoreWebView2 != null) + { + this.documentationBrowser.CoreWebView2.WebMessageReceived -= CoreWebView2OnWebMessageReceived; + } - // Note to test writers - // Disposing the document browser will cause future tests - // that uses the Browser component to crash - if (!Models.DynamoModel.IsTestMode) - { this.documentationBrowser.Dispose(); } } @@ -159,8 +156,9 @@ async void InitializeAsync() } // Only initialize once - if (!hasBeenInitialized) + if (initState == AsyncMethodState.NotStarted) { + initState = AsyncMethodState.Started; if (!string.IsNullOrEmpty(WebBrowserUserDataFolder)) { //This indicates in which location will be created the WebView2 cache folder @@ -169,9 +167,9 @@ async void InitializeAsync() UserDataFolder = WebBrowserUserDataFolder }; } + //Initialize the CoreWebView2 component otherwise we can't navigate to a web page await documentationBrowser.EnsureCoreWebView2Async(); - this.documentationBrowser.CoreWebView2.WebMessageReceived += CoreWebView2OnWebMessageReceived; comScriptingObject = new ScriptingObject(this.viewModel); @@ -181,7 +179,7 @@ async void InitializeAsync() this.documentationBrowser.CoreWebView2.Settings.IsZoomControlEnabled = true; this.documentationBrowser.CoreWebView2.Settings.AreDevToolsEnabled = true; - hasBeenInitialized = true; + initState = AsyncMethodState.Done; } if(Directory.Exists(VirtualFolderPath)) @@ -192,10 +190,7 @@ async void InitializeAsync() htmlContent = ResourceUtilities.LoadResourceAndReplaceByKey(htmlContent, "#fontStyle", fontStylePath); - Dispatcher.BeginInvoke(new Action(() => - { - this.documentationBrowser.NavigateToString(htmlContent); - })); + this.documentationBrowser.NavigateToString(htmlContent); } private void CoreWebView2OnWebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e) @@ -209,6 +204,11 @@ private void CoreWebView2OnWebMessageReceived(object sender, CoreWebView2WebMess /// public void Dispose() { + if (initState == AsyncMethodState.Started) + { + Log("DocumentationBrowserView is being disposed but async initialization is still not done"); + } + Dispose(true); GC.SuppressFinalize(this); } diff --git a/src/DocumentationBrowserViewExtension/DocumentationBrowserViewExtension.cs b/src/DocumentationBrowserViewExtension/DocumentationBrowserViewExtension.cs index b56498864e5..3934a1c6377 100644 --- a/src/DocumentationBrowserViewExtension/DocumentationBrowserViewExtension.cs +++ b/src/DocumentationBrowserViewExtension/DocumentationBrowserViewExtension.cs @@ -178,7 +178,7 @@ public override void Loaded(ViewLoadedParams viewLoadedParams) public override void Shutdown() { - Dispose(); + // Do nothing for now } private void OnInsertFile(object sender, InsertDocumentationLinkEventArgs e) diff --git a/src/DynamoCoreWpf/Views/GuidedTour/PopupWindow.xaml.cs b/src/DynamoCoreWpf/Views/GuidedTour/PopupWindow.xaml.cs index c007b7d3014..fe5f47fe0f3 100644 --- a/src/DynamoCoreWpf/Views/GuidedTour/PopupWindow.xaml.cs +++ b/src/DynamoCoreWpf/Views/GuidedTour/PopupWindow.xaml.cs @@ -105,7 +105,9 @@ private void PopupWindow_Opened(object sender, EventArgs e) { if (hostControlInfo.HtmlPage != null && !string.IsNullOrEmpty(hostControlInfo.HtmlPage.FileName)) { - ContentRichTextBox.Visibility = Visibility.Hidden; + ContentRichTextBox.Visibility = Visibility.Hidden; + + // Opened event ensures the webview2 will be visible when added to the popup layout tree. InitWebView2Component(); } } diff --git a/src/DynamoCoreWpf/Views/SplashScreen/SplashScreen.xaml.cs b/src/DynamoCoreWpf/Views/SplashScreen/SplashScreen.xaml.cs index 51b781759c7..44918de435b 100644 --- a/src/DynamoCoreWpf/Views/SplashScreen/SplashScreen.xaml.cs +++ b/src/DynamoCoreWpf/Views/SplashScreen/SplashScreen.xaml.cs @@ -304,6 +304,8 @@ protected override async void OnContentRendered(EventArgs e) { UserDataFolder = webBrowserUserDataFolder.FullName }; + + //ContentRendered ensures that the webview2 component is visible. await webView.EnsureCoreWebView2Async(); // Context menu disabled webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false; diff --git a/src/DynamoUtilities/AsyncMethodState.cs b/src/DynamoUtilities/AsyncMethodState.cs new file mode 100644 index 00000000000..9385dbe40bf --- /dev/null +++ b/src/DynamoUtilities/AsyncMethodState.cs @@ -0,0 +1,14 @@ + +namespace DynamoUtilities +{ + /// + /// Simple representation of the states of an async method + /// Used for identifying issues like Dispose called before async method is finished. + /// + internal enum AsyncMethodState + { + NotStarted = 0,// Async method not called yet + Started,// Async method called but not finished execution (usually set before any awaits) + Done// Async method has finished execution (all awaits have finished) + } +} diff --git a/src/DynamoUtilities/Properties/AssemblyInfo.cs b/src/DynamoUtilities/Properties/AssemblyInfo.cs index 67a7633b408..208a7fe482b 100644 --- a/src/DynamoUtilities/Properties/AssemblyInfo.cs +++ b/src/DynamoUtilities/Properties/AssemblyInfo.cs @@ -25,4 +25,4 @@ [assembly: InternalsVisibleTo("DynamoCLI")] [assembly: InternalsVisibleTo("NodeDocumentationMarkdownGenerator")] [assembly: InternalsVisibleTo("DynamoUtilitiesTests")] - +[assembly: InternalsVisibleTo("Notifications")] diff --git a/src/LibraryViewExtensionWebView2/LibraryViewController.cs b/src/LibraryViewExtensionWebView2/LibraryViewController.cs index 6c4499fd368..749598a66c8 100644 --- a/src/LibraryViewExtensionWebView2/LibraryViewController.cs +++ b/src/LibraryViewExtensionWebView2/LibraryViewController.cs @@ -120,6 +120,22 @@ internal LibraryViewController(Window dynamoView, ICommandExecutive commandExecu { WebBrowserUserDataFolder = webBrowserUserDataFolder.FullName; } + + /// Create and add the library view to the WPF visual tree. + /// Also load the library.html and js files. + LibraryViewModel model = new LibraryViewModel(); + LibraryView view = new LibraryView(model); + + //Adding the LibraryView to the sidebar ensures that the webview2 component is visible. + var sidebarGrid = dynamoWindow.FindName("sidebarGrid") as Grid; + sidebarGrid.Children.Add(view); + + browser = view.mainGrid.Children.OfType().FirstOrDefault(); + browser.Loaded += Browser_Loaded; + browser.SizeChanged += Browser_SizeChanged; + + LibraryViewController.SetupSearchModelEventsObserver(browser, dynamoViewModel.Model.SearchModel, + this, this.customization); } private void DynamoViewModel_PreferencesWindowChanged(object sender, EventArgs e) @@ -302,30 +318,6 @@ private string ReplaceUrlWithBase64Image(string html, string minifiedURL, bool m Tuple.Create("/resources/search-icon-clear.svg",true) }; - /// - /// Creates and adds the library view to the WPF visual tree. - /// Also loads the library.html and js files. - /// - /// LibraryView control - internal void AddLibraryView() - { - LibraryViewModel model = new LibraryViewModel(); - LibraryView view = new LibraryView(model); - - var sidebarGrid = dynamoWindow.FindName("sidebarGrid") as Grid; - sidebarGrid.Children.Add(view); - - browser = view.mainGrid.Children.OfType().FirstOrDefault(); - browser.Loaded += Browser_Loaded; - browser.SizeChanged += Browser_SizeChanged; - - this.browser = view.mainGrid.Children.OfType().FirstOrDefault(); - InitializeAsync(); - - LibraryViewController.SetupSearchModelEventsObserver(browser, dynamoViewModel.Model.SearchModel, - this, this.customization); - } - async void InitializeAsync() { try @@ -422,6 +414,8 @@ private void Browser_Loaded(object sender, RoutedEventArgs e) { string msg = "Browser Loaded"; LogToDynamoConsole(msg); + + InitializeAsync(); } // This enum is for matching the modifier keys between C# and javaScript diff --git a/src/LibraryViewExtensionWebView2/ViewExtension.cs b/src/LibraryViewExtensionWebView2/ViewExtension.cs index 999e02b596d..69d679dfd27 100644 --- a/src/LibraryViewExtensionWebView2/ViewExtension.cs +++ b/src/LibraryViewExtensionWebView2/ViewExtension.cs @@ -1,4 +1,4 @@ -using System.Windows; +using System.Windows; using Dynamo.Models; using Dynamo.ViewModels; using Dynamo.Wpf.Extensions; @@ -35,7 +35,6 @@ public void Loaded(ViewLoadedParams viewLoadedParams) { viewParams = viewLoadedParams; controller = new LibraryViewController(viewLoadedParams.DynamoWindow, viewLoadedParams.CommandExecutive, customization); - controller.AddLibraryView(); (viewLoadedParams.DynamoWindow.DataContext as DynamoViewModel).PropertyChanged += handleDynamoViewPropertyChanges; } diff --git a/src/Notifications/NotificationCenterController.cs b/src/Notifications/NotificationCenterController.cs index ec5d2740cd1..c66e7cd415e 100644 --- a/src/Notifications/NotificationCenterController.cs +++ b/src/Notifications/NotificationCenterController.cs @@ -12,6 +12,7 @@ using Dynamo.Controls; using Dynamo.Logging; using Dynamo.Notifications.View; +using DynamoUtilities; using Dynamo.ViewModels; using Dynamo.Wpf.ViewModels.Core; using Newtonsoft.Json; @@ -48,7 +49,7 @@ public void UpdateNotificationWindowSize(int height) } } - public class NotificationCenterController + public class NotificationCenterController : IDisposable { private readonly NotificationUI notificationUIPopup; private readonly DynamoView dynamoView; @@ -64,6 +65,8 @@ public class NotificationCenterController private static readonly string NotificationCenterButtonName = "notificationsButton"; internal DirectoryInfo webBrowserUserDataFolder; + internal AsyncMethodState initState = AsyncMethodState.NotStarted; + private readonly DynamoLogger logger; private string jsonStringFile; private NotificationsModel notificationsModel; @@ -82,7 +85,6 @@ internal NotificationCenterController(DynamoView view, DynamoLogger dynLogger) dynamoView.SizeChanged += DynamoView_SizeChanged; dynamoView.LocationChanged += DynamoView_LocationChanged; notificationsButton.Click += NotificationsButton_Click; - dynamoView.Closing += View_Closing; dynamoView.PreviewMouseDown += DynamoView_PreviewMouseDown; notificationUIPopup = new NotificationUI @@ -97,41 +99,14 @@ internal NotificationCenterController(DynamoView view, DynamoLogger dynLogger) // If user turns on the feature, they will need to restart Dynamo to see the count // This ensures no network traffic when Notification center feature is turned off - if (dynamoViewModel.PreferenceSettings.EnableNotificationCenter && !dynamoViewModel.Model.NoNetworkMode ) - { - InitializeBrowserAsync(); + if (dynamoViewModel.PreferenceSettings.EnableNotificationCenter && !dynamoViewModel.Model.NoNetworkMode ) + { + notificationUIPopup.webView.Loaded += InitializeBrowserAsync; RequestNotifications(); } } - private void View_Closing(object sender, System.ComponentModel.CancelEventArgs e) - { - dynamoView.Closing -= View_Closing; - dynamoView.PreviewMouseDown -= DynamoView_PreviewMouseDown; - SuspendCoreWebviewAsync(); - } - - async void SuspendCoreWebviewAsync() - { - if (notificationUIPopup == null) return; - - notificationUIPopup.IsOpen = false; - notificationUIPopup.webView.Visibility = Visibility.Hidden; - - if (notificationUIPopup.webView.CoreWebView2 != null) - { - notificationUIPopup.webView.CoreWebView2.Stop(); - notificationUIPopup.webView.CoreWebView2InitializationCompleted -= WebView_CoreWebView2InitializationCompleted; - notificationUIPopup.webView.CoreWebView2.NewWindowRequested -= WebView_NewWindowRequested; - - dynamoView.SizeChanged -= DynamoView_SizeChanged; - dynamoView.LocationChanged -= DynamoView_LocationChanged; - notificationsButton.Click -= NotificationsButton_Click; - notificationUIPopup.webView.NavigationCompleted -= WebView_NavigationCompleted; - } - } - - private void InitializeBrowserAsync() + private async void InitializeBrowserAsync(object sender, RoutedEventArgs e) { if (webBrowserUserDataFolder != null) { @@ -142,7 +117,10 @@ private void InitializeBrowserAsync() }; } notificationUIPopup.webView.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted; - notificationUIPopup.webView.EnsureCoreWebView2Async(); + + initState = AsyncMethodState.Started; + await notificationUIPopup.webView.EnsureCoreWebView2Async(); + initState = AsyncMethodState.Done; } private void WebView_NavigationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs e) @@ -328,5 +306,49 @@ public void RefreshNotifications(string url="") { InvokeJS(@"window.RequestNotifications('" + DynamoUtilities.PathHelper.GetServiceBackendAddress(this, "notificationAddress") + "');"); } } + + protected virtual void Dispose(bool disposing) + { + if (dynamoView != null) + { + dynamoView.SizeChanged -= DynamoView_SizeChanged; + dynamoView.LocationChanged -= DynamoView_LocationChanged; + dynamoView.PreviewMouseDown -= DynamoView_PreviewMouseDown; + } + + if (notificationUIPopup != null) + { + notificationUIPopup.IsOpen = false; + notificationsButton.Click -= NotificationsButton_Click; + + if (notificationUIPopup.webView != null) + { + notificationUIPopup.webView.Visibility = Visibility.Hidden; + notificationUIPopup.webView.Loaded -= InitializeBrowserAsync; + notificationUIPopup.webView.NavigationCompleted -= WebView_NavigationCompleted; + notificationUIPopup.webView.CoreWebView2InitializationCompleted -= WebView_CoreWebView2InitializationCompleted; + + if (notificationUIPopup.webView.CoreWebView2 != null) + { + notificationUIPopup.webView.CoreWebView2.Stop(); + notificationUIPopup.webView.CoreWebView2.NewWindowRequested -= WebView_NewWindowRequested; + } + notificationUIPopup.webView.Dispose(); + } + } + } + + /// + /// Dispose function for DocumentationBrowser + /// + public void Dispose() + { + if (initState == AsyncMethodState.Started) + { + logger?.Log("NotificationCenterController is being disposed but async initialization is still not done"); + } + Dispose(true); + GC.SuppressFinalize(this); + } } } diff --git a/src/Notifications/NotificationsViewExtension.cs b/src/Notifications/NotificationsViewExtension.cs index 2840e073f23..d3e19b5b0ce 100644 --- a/src/Notifications/NotificationsViewExtension.cs +++ b/src/Notifications/NotificationsViewExtension.cs @@ -18,7 +18,7 @@ public class NotificationsViewExtension : IViewExtension, INotifyPropertyChanged private Action notificationHandler; private ObservableCollection notifications; private bool disposed; - private NotificationCenterController notificationCenterController; + internal NotificationCenterController notificationCenterController; /// /// Notifications data collection. PropertyChanged event is raised to help dealing WPF bind dispose. /// @@ -67,6 +67,8 @@ public void Dispose() { BindingOperations.ClearAllBindings(notificationsMenuItem.CountLabel); } + notificationCenterController?.Dispose(); + notificationCenterController = null; notificationsMenuItem = null; disposed = true; } @@ -115,6 +117,12 @@ public void Loaded(ViewLoadedParams viewStartupParams) private void LoadNotificationCenter() { var dynamoView = viewLoadedParams.DynamoWindow as DynamoView; + + if (notificationCenterController != null) + { + notificationCenterController.Dispose(); + notificationCenterController = null; + } notificationCenterController = new NotificationCenterController(dynamoView, logger); } diff --git a/src/Notifications/Properties/AssemblyInfo.cs b/src/Notifications/Properties/AssemblyInfo.cs index f5edea27fe7..32c77894c8c 100644 --- a/src/Notifications/Properties/AssemblyInfo.cs +++ b/src/Notifications/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -9,3 +10,4 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("0acedbf9-81ab-45ee-b5e5-0d036bce0a31")] +[assembly:InternalsVisibleTo("DynamoCoreWpfTests")] diff --git a/test/DynamoCoreTests/UnitTestBase.cs b/test/DynamoCoreTests/UnitTestBase.cs index 425ddc00bfc..3b846660b00 100644 --- a/test/DynamoCoreTests/UnitTestBase.cs +++ b/test/DynamoCoreTests/UnitTestBase.cs @@ -64,6 +64,7 @@ public static string TestDirectory [SetUp] public virtual void Setup() { + System.Console.WriteLine("Start test: " + TestContext.CurrentContext.Test.Name); SetupDirectories(); if (assemblyHelper == null) @@ -97,6 +98,7 @@ public virtual void Cleanup() { AppDomain.CurrentDomain.AssemblyResolve -= assemblyHelper.ResolveAssembly; } + System.Console.WriteLine("Finished test: " + TestContext.CurrentContext.Test.Name); } public string GetNewFileNameOnTempPath(string fileExtension = "dyn") diff --git a/test/DynamoCoreWpfTests/DynamoTestUIBase.cs b/test/DynamoCoreWpfTests/DynamoTestUIBase.cs index 2f7cd01bfb6..dab89d012a1 100644 --- a/test/DynamoCoreWpfTests/DynamoTestUIBase.cs +++ b/test/DynamoCoreWpfTests/DynamoTestUIBase.cs @@ -40,6 +40,7 @@ protected string ExecutingDirectory [SetUp] public virtual void Start() { + System.Console.WriteLine("Start test: " + TestContext.CurrentContext.Test.Name); var assemblyPath = Assembly.GetExecutingAssembly().Location; preloader = new Preloader(Path.GetDirectoryName(assemblyPath)); preloader.Preload(); @@ -78,7 +79,7 @@ public virtual void Start() View = new DynamoView(ViewModel); View.Show(); - SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); + SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext()); } protected static void RaiseLoadedEvent(FrameworkElement element) @@ -138,6 +139,7 @@ public void Exit() { Console.WriteLine(ex.StackTrace); } + System.Console.WriteLine("Finished test: " + TestContext.CurrentContext.Test.Name); } protected virtual void GetLibrariesToPreload(List libraries) diff --git a/test/DynamoCoreWpfTests/DynamoViewModelUnitTest.cs b/test/DynamoCoreWpfTests/DynamoViewModelUnitTest.cs index fe782319b6a..8705685c2ef 100644 --- a/test/DynamoCoreWpfTests/DynamoViewModelUnitTest.cs +++ b/test/DynamoCoreWpfTests/DynamoViewModelUnitTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -185,4 +185,4 @@ protected void RunCurrentModel() // Run currently loaded model. Assert.DoesNotThrow(() => ViewModel.HomeSpace.Run()); } } -} \ No newline at end of file +} diff --git a/test/DynamoCoreWpfTests/PackageManager/PackageManagerUITests.cs b/test/DynamoCoreWpfTests/PackageManager/PackageManagerUITests.cs index b24af6612a0..563f0f50a65 100644 --- a/test/DynamoCoreWpfTests/PackageManager/PackageManagerUITests.cs +++ b/test/DynamoCoreWpfTests/PackageManager/PackageManagerUITests.cs @@ -1262,8 +1262,12 @@ public void PackageManagerDownloadsBeforeInstalling() .Returns(MessageBoxResult.OK); MessageBoxService.OverrideMessageBoxDuringTests(dlgMock.Object); - //actually perform the download & install operations - pmVmMock.Object.ExecutePackageDownload(id, pkgVer, ""); + Task.Run(() => { + // This test runs on a single thread (using DispatcherSyncronizationContext) + // We need to run the async ExecutePackageDownload function in a separate thread so that the Thread.Sleep does not mess up the other awaiting tasks. + //actually perform the download & install operations + pmVmMock.Object.ExecutePackageDownload(id, pkgVer, ""); + }).Wait(); //wait a bit. Thread.Sleep(500); diff --git a/test/DynamoCoreWpfTests/PythonNodeCustomizationTests.cs b/test/DynamoCoreWpfTests/PythonNodeCustomizationTests.cs index 6a739b33db4..cc825e1e8da 100644 --- a/test/DynamoCoreWpfTests/PythonNodeCustomizationTests.cs +++ b/test/DynamoCoreWpfTests/PythonNodeCustomizationTests.cs @@ -12,6 +12,7 @@ using Dynamo.PythonServices; using Dynamo.Tests; using Dynamo.Utilities; +using Dynamo.ViewModels; using DynamoCoreWpfTests.Utility; using ICSharpCode.AvalonEdit.Document; using NUnit.Framework; @@ -20,7 +21,7 @@ namespace DynamoCoreWpfTests { - [TestFixture, Category("Failure")] + [TestFixture] public class PythonNodeCustomizationTests : DynamoTestUIBase { bool bTextEnteringEventRaised = false; @@ -49,6 +50,19 @@ private static DependencyObject GetInfoBubble(DependencyObject parent) return null; } + private void WaitForDocumentationBrowserInitialization() + { + var docBrowserviewExtension = this.View.viewExtensionManager.ViewExtensions.OfType().FirstOrDefault(); + + // Wait for the DocumentationBrowserView webview2 control to finish initialization + DispatcherUtil.DoEventsLoop(() => + { + return docBrowserviewExtension.BrowserView.initState == DynamoUtilities.AsyncMethodState.Done; + }); + + Assert.IsTrue(docBrowserviewExtension.BrowserView.initState == DynamoUtilities.AsyncMethodState.Done); + } + public override void Open(string path) { base.Open(path); @@ -276,7 +290,7 @@ public void OnMoreInfoEventTest() //Pressing the MoreInfo button, here the OnMoreInfoEvent is raised moreInfoButton.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent)); - DispatcherUtil.DoEvents(); + WaitForDocumentationBrowserInitialization(); //Check that now we are showing a extension tab (DocumentationBrowser) Assert.That(this.ViewModel.SideBarTabItems.Count, Is.EqualTo(1)); @@ -315,8 +329,7 @@ public void OpenPythonLearningMaterialValidationTest() //Click the button and internally the OpenPythonLearningMaterial method is executed learnMoreMenuItem.RaiseEvent(new RoutedEventArgs(MenuItem.ClickEvent)); - - DispatcherUtil.DoEvents(); + WaitForDocumentationBrowserInitialization(); var learnMoreTab = this.ViewModel.SideBarTabItems .Where(x => x.Content.GetType().Equals(typeof(DocumentationBrowserView))) @@ -349,8 +362,7 @@ public void OpenPythonLearningMaterial_PythonNodeFromStringValidationTest() //Click the button and internally the OpenPythonLearningMaterial method is executed learnMoreMenuItem.RaiseEvent(new RoutedEventArgs(MenuItem.ClickEvent)); - - DispatcherUtil.DoEvents(); + WaitForDocumentationBrowserInitialization(); var learnMoreTab = this.ViewModel.SideBarTabItems .Where(x => x.Content.GetType().Equals(typeof(DocumentationBrowserView))) diff --git a/test/DynamoCoreWpfTests/Utility/DispatcherUtil.cs b/test/DynamoCoreWpfTests/Utility/DispatcherUtil.cs index 9f12de81776..b7e21cddb75 100644 --- a/test/DynamoCoreWpfTests/Utility/DispatcherUtil.cs +++ b/test/DynamoCoreWpfTests/Utility/DispatcherUtil.cs @@ -1,9 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Security.Permissions; using System.Windows.Threading; +using System.Threading; +using NUnit.Framework; namespace DynamoCoreWpfTests.Utility { @@ -22,11 +24,39 @@ public static void DoEvents() } /// - /// Helper method for DispatcherUtil + /// Force the Dispatcher to empty it's queue every 100 ms for a maximum 4 seconds or until + /// the check function returns true. /// - /// - /// - private static object ExitFrame(object frame) + /// When check returns true, the even loop is stopped. + [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] + public static void DoEventsLoop(Func check = null) + { + const int max_count = 40; + + int count = 0; + while (true) + { + if (check != null && check()) + { + return; + } + if (count >= max_count) + { + return; + } + + DispatcherUtil.DoEvents(); + Thread.Sleep(100); + count++; + } + } + + /// + /// Helper method for DispatcherUtil + /// + /// + /// + private static object ExitFrame(object frame) { ((DispatcherFrame)frame).Continue = false; return null; diff --git a/test/DynamoCoreWpfTests/ViewExtensions/DocumentationBrowserViewExtensionTests.cs b/test/DynamoCoreWpfTests/ViewExtensions/DocumentationBrowserViewExtensionTests.cs index 6d3e73e72d8..e3abc8f4ee7 100644 --- a/test/DynamoCoreWpfTests/ViewExtensions/DocumentationBrowserViewExtensionTests.cs +++ b/test/DynamoCoreWpfTests/ViewExtensions/DocumentationBrowserViewExtensionTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -7,7 +6,6 @@ using System.Reflection; using System.Text; using System.Threading; -using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using Dynamo; @@ -20,6 +18,7 @@ using Dynamo.ViewModels; using Dynamo.Wpf.Extensions; using DynamoCoreWpfTests.Utility; +using DynamoUtilities; using NUnit.Framework; namespace DynamoCoreWpfTests @@ -80,6 +79,8 @@ public void ClickingMenuItemLaunchesSidebarWithIndexContent() // simulate clicking the Show docs browser menu item ShowDocsBrowser(); + WaitForWebView2Initialization(); + // confirm the extension loads a view into the sidebar // and get the html content inside var docsTab = GetDocsTabItem(); @@ -96,6 +97,9 @@ public void ShowingStartPageHidesBrowser() { // Arrange ShowDocsBrowser(); + + WaitForWebView2Initialization(); + var docsView = GetDocsTabItem().Content as DocumentationBrowserView; this.ViewModel.NewHomeWorkspaceCommand.Execute(null); var visibilityBeforeShowStartPageEvent = docsView.documentationBrowser.Visibility; @@ -136,11 +140,10 @@ public void CanCreatePackageNodeDocumentationAndLoadImages() ); var node = this.ViewModel.Model.CurrentWorkspace.Nodes.FirstOrDefault(); - node.Name = nodeRename; // Forces original name header to appear - var nodeAnnotationEventArgs = new OpenNodeAnnotationEventArgs(node, this.ViewModel); + node.Name = nodeRename;// Forces original name header to appear + + var htmlContent = RequestNodeDocs(node); - docBrowserviewExtension.HandleRequestOpenDocumentationLink(nodeAnnotationEventArgs); - var htmlContent = GetSidebarDocsBrowserContents(); //There are times in which the URL contain characters like backslash (%5C) then they need to be replaced by the normal slash "/" htmlContent = htmlContent.Replace(@"%5C", "/"); @@ -162,6 +165,9 @@ public void ViewExtensionIgnoresExternalEvents() // Act var tabsBeforeExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; viewExtension.HandleRequestOpenDocumentationLink(externalEvent); + DispatcherUtil.DoEventsLoop(); + + Assert.AreEqual(AsyncMethodState.NotStarted, viewExtension.BrowserView.initState); var tabsAfterExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; // Assert @@ -180,9 +186,8 @@ public void CanHandleDocsEventWithValidLink() { // Act var tabsBeforeExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - viewExtension.HandleRequestOpenDocumentationLink(docsEvent); + var htmlContent = RequestDocLink(viewExtension, docsEvent); var tabsAfterExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - var htmlContent = GetSidebarDocsBrowserContents(); // Assert Assert.IsFalse(docsEvent.IsRemoteResource); @@ -220,9 +225,8 @@ public void Displays404PageOnMissingDocFile() { // Act var tabsBeforeExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - viewExtension.HandleRequestOpenDocumentationLink(docsEvent); + var htmlContent = RequestDocLink(viewExtension, docsEvent); var tabsAfterExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - var htmlContent = GetSidebarDocsBrowserContents(); // Assert Assert.IsFalse(docsEvent.IsRemoteResource); @@ -246,9 +250,9 @@ public void DisplaysHtmlEmbeddedInLoadedAssemblies() // Act var tabsBeforeExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - viewExtension.HandleRequestOpenDocumentationLink(docsEvent); + var htmlContent = RequestDocLink(viewExtension, docsEvent); var tabsAfterExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - var htmlContent = GetSidebarDocsBrowserContents(); + // Assert Assert.IsFalse(docsEvent.IsRemoteResource); @@ -273,9 +277,9 @@ public void Displays404PageWhenLinkPointsToAssemblyThatCannotBeFound() // Act var tabsBeforeExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - viewExtension.HandleRequestOpenDocumentationLink(docsEvent); + var htmlContent = RequestDocLink(viewExtension, docsEvent); var tabsAfterExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - var htmlContent = GetSidebarDocsBrowserContents(); + // Assert Assert.IsFalse(docsEvent.IsRemoteResource); @@ -306,9 +310,7 @@ public void DisplaysLocalizedContentWhenAvailable() var uri = $"{assemblyName};{fileName}"; var docsEvent = new OpenDocumentationLinkEventArgs(new Uri(uri, UriKind.Relative)); - // Act - viewExtension.HandleRequestOpenDocumentationLink(docsEvent); - var htmlContent = GetSidebarDocsBrowserContents(); + var htmlContent = RequestDocLink(viewExtension, docsEvent); // Assert StringAssert.Contains("

Division by zero - en-us

", htmlContent); @@ -340,9 +342,7 @@ public void DisplayNeutralCultureContentWhenSpecificCultureContentIsNotAvailable var uri = $"{assemblyName};{fileName}"; var docsEvent = new OpenDocumentationLinkEventArgs(new Uri(uri, UriKind.Relative)); - // Act - viewExtension.HandleRequestOpenDocumentationLink(docsEvent); - var htmlContent = GetSidebarDocsBrowserContents(); + var htmlContent = RequestDocLink(viewExtension, docsEvent); // Assert StringAssert.Contains("

Division entre cero - es

", htmlContent); @@ -374,9 +374,7 @@ public void DisplaySpecificCultureContentWhenNeutralCultureContentIsNotAvailable var uri = $"{assemblyName};{fileName}"; var docsEvent = new OpenDocumentationLinkEventArgs(new Uri(uri, UriKind.Relative)); - // Act - viewExtension.HandleRequestOpenDocumentationLink(docsEvent); - var htmlContent = GetSidebarDocsBrowserContents(); + var htmlContent = RequestDocLink(viewExtension, docsEvent); // Assert StringAssert.Contains("

Division by zero - en-us

", htmlContent); @@ -409,9 +407,7 @@ public void DisplaysInvariantContentWhenNoCompatibleLocalizedContentIsAvailable( var uri = $"{assemblyName};{fileName}"; var docsEvent = new OpenDocumentationLinkEventArgs(new Uri(uri, UriKind.Relative)); - // Act - viewExtension.HandleRequestOpenDocumentationLink(docsEvent); - var htmlContent = GetSidebarDocsBrowserContents(); + var htmlContent = RequestDocLink(viewExtension, docsEvent); // Assert StringAssert.Contains("

Division by zero - invariant

", htmlContent); @@ -437,9 +433,9 @@ public void RemovesScriptTagsFromLoadedHtml() // Act var tabsBeforeExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - viewExtension.HandleRequestOpenDocumentationLink(docsEvent); + var htmlContent = RequestDocLink(viewExtension, docsEvent); + var tabsAfterExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - var htmlContent = GetSidebarDocsBrowserContents(); // Assert Assert.IsFalse(docsEvent.IsRemoteResource); @@ -464,9 +460,8 @@ public void DPIScriptExists() // Act var tabsBeforeExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - viewExtension.HandleRequestOpenDocumentationLink(docsEvent); + var htmlContent = RequestDocLink(viewExtension, docsEvent); var tabsAfterExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - var htmlContent = GetSidebarDocsBrowserContents(); // Assert Assert.IsFalse(docsEvent.IsRemoteResource); @@ -511,21 +506,21 @@ public void CanCreateNodeDocumenationHtmlFromNodeAnnotationEventArgsWithOOTBNode var nodeRename = "New node name"; var expectedNodeDocumentationTitle = $"

{nodeRename}

"; var expectedNodeDocumentationNamespace = $"

{nodeName}

"; - + // Act this.ViewModel.ExecuteCommand( new DynamoModel.CreateNodeCommand( Guid.NewGuid().ToString(), nodeName, 0, 0, false, false) ); + var tabsBeforeExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; + var node = this.ViewModel.Model.CurrentWorkspace.Nodes.FirstOrDefault(); - node.Name = nodeRename; // Forces original name header to appear - var nodeAnnotationEventArgs = new OpenNodeAnnotationEventArgs(node, this.ViewModel); + node.Name = nodeRename; // Forces original name header to appear + + var htmlContent = RequestNodeDocs(node); - var tabsBeforeExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - docBrowserviewExtension.HandleRequestOpenDocumentationLink(nodeAnnotationEventArgs); var tabsAfterExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - var htmlContent = GetSidebarDocsBrowserContents(); // Assert Assert.AreEqual(0, tabsBeforeExternalEventTrigger); @@ -565,14 +560,16 @@ public void CanCreateNodeDocumenationHtmlFromNodeAnnotationEventArgsWithPackageN Guid.NewGuid().ToString(), nodeName, 0, 0, false, false) ); + var tabsBeforeExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; + var node = this.ViewModel.Model.CurrentWorkspace.Nodes.FirstOrDefault(); + node.Name = nodeRename; // Forces original name header to appear - var nodeAnnotationEventArgs = new OpenNodeAnnotationEventArgs(node, this.ViewModel); - var tabsBeforeExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - docBrowserviewExtension.HandleRequestOpenDocumentationLink(nodeAnnotationEventArgs); + var htmlContent = RequestNodeDocs(node); + var tabsAfterExternalEventTrigger = this.ViewModel.SideBarTabItems.Count; - var htmlContent = GetSidebarDocsBrowserContents(); + htmlContent = htmlContent.Replace(@"%5C", "/"); // Assert @@ -630,8 +627,8 @@ public void DocsCanBeLoadedForDSNonPackageNodesFrom_FallBackPath() new DynamoModel.CreateNodeCommand( Guid.NewGuid().ToString(), nodeName, 0, 0, false, false) ); - node = this.ViewModel.Model.CurrentWorkspace.Nodes.FirstOrDefault(); - htmlContent = RequestNodeDocs(node); + node = this.ViewModel.Model.CurrentWorkspace.Nodes.FirstOrDefault(); + htmlContent = RequestNodeDocs(node); Assert.IsTrue(htmlContent.Contains("loopwhile sample docs")); } @@ -657,7 +654,7 @@ public void DocsAreLoadedFromHostPathBeforeCorePath() var htmlContent = RequestNodeDocs(node); Assert.IsTrue(htmlContent.Contains("list.rank sample docs from host path")); - } + } [Test] public void DocsCanBeLoadedForCoreNodeModelNodesFrom_FallBackPath() @@ -711,10 +708,22 @@ private string RequestNodeDocs(Dynamo.Graph.Nodes.NodeModel node) var docBrowserviewExtension = this.View.viewExtensionManager.ViewExtensions.OfType().FirstOrDefault(); var nodeAnnotationEventArgs = new OpenNodeAnnotationEventArgs(node, this.ViewModel); docBrowserviewExtension.HandleRequestOpenDocumentationLink(nodeAnnotationEventArgs); + + WaitForWebView2Initialization(docBrowserviewExtension.BrowserView); + + return GetSidebarDocsBrowserContents(); + } + + private string RequestDocLink(DocumentationBrowserViewExtension viewExtension, OpenDocumentationLinkEventArgs docsEvent) + { + viewExtension.HandleRequestOpenDocumentationLink(docsEvent); + + WaitForWebView2Initialization(viewExtension.BrowserView); + return GetSidebarDocsBrowserContents(); } - [Test,Category("Failure")] + [Test] public void AddGraphInSpecificLocationToWorkspace() { //TODO see this issue: @@ -752,7 +761,7 @@ public void AddGraphInSpecificLocationToWorkspace() Assert.AreEqual(ViewModel.Model.CurrentWorkspace.Nodes.Count(), 1); var node = ViewModel.Model.CurrentWorkspace.Nodes.FirstOrDefault(); - + RequestNodeDocs(node); var docsView = GetDocsTabItem().Content as DocumentationBrowserView; var docsViewModel = docsView.DataContext as DocumentationBrowserViewModel; @@ -784,8 +793,6 @@ public void Validate_GetGraphLinkFromMDLocation() //In this call the GetGraphLinkFromMDLocation() method is executed internally RequestNodeDocs(node); - // Show the DocumentationBrowser so we can get the DocumentationBrowserViewModel - ShowDocsBrowser(); var docsView = GetDocsTabItem().Content as DocumentationBrowserView; var docsViewModel = docsView.DataContext as DocumentationBrowserViewModel; @@ -821,8 +828,6 @@ public void Validate_GetGraphLinkFromPackage() //In this call the GetGraphLinkFromMDLocation() method is executed internally RequestNodeDocs(node); - // Show the DocumentationBrowser so we can get the DocumentationBrowserViewModel - ShowDocsBrowser(); var docsView = GetDocsTabItem().Content as DocumentationBrowserView; var docsViewModel = docsView.DataContext as DocumentationBrowserViewModel; @@ -834,7 +839,7 @@ public void Validate_GetGraphLinkFromPackage() Assert.IsTrue(!string.IsNullOrEmpty(graphPathValue)); //check that the path contains "packageWithDocumentation" - Assert.That(graphPathValue.Contains(Path.Combine("PackageWithNodeDocumentation","doc",dynFileName))); + Assert.That(graphPathValue.Contains(Path.Combine("PackageWithNodeDocumentation", "doc", dynFileName))); } #region Helpers @@ -865,10 +870,28 @@ private List GetDocsMenuItems() .ToList(); } + private void WaitForWebView2Initialization(DocumentationBrowserView docView = null) + { + if (docView == null) + { + var docsTab = GetDocsTabItem(); + docView = docsTab.Content as DocumentationBrowserView; + } + + // Wait for the DocumentationBrowserView webview2 control to finish initialization + DispatcherUtil.DoEventsLoop(() => + { + return docView.initState == AsyncMethodState.Done; + }); + + Assert.AreEqual(AsyncMethodState.Done, docView.initState); + } + private string GetSidebarDocsBrowserContents() { var docsTab = GetDocsTabItem(); var docsView = docsTab.Content as DocumentationBrowserView; + var docsViewModel = docsView.DataContext as DocumentationBrowserViewModel; return docsViewModel.GetContent(); } diff --git a/test/DynamoCoreWpfTests/ViewExtensions/NotificationsExtensionTests.cs b/test/DynamoCoreWpfTests/ViewExtensions/NotificationsExtensionTests.cs index db893d69222..8b14fa9e6bd 100644 --- a/test/DynamoCoreWpfTests/ViewExtensions/NotificationsExtensionTests.cs +++ b/test/DynamoCoreWpfTests/ViewExtensions/NotificationsExtensionTests.cs @@ -8,6 +8,8 @@ using System.Windows.Controls; using System.Windows.Controls.Primitives; using Dynamo.Notifications; +using Dynamo.DocumentationBrowser; +using DynamoCoreWpfTests.Utility; namespace DynamoCoreWpfTests.ViewExtensions { @@ -20,6 +22,14 @@ public void PressNotificationButtonAndShowPopup() var notificationsButton = (Button)shortcutBar.FindName("notificationsButton"); notificationsButton.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent)); + var notificationExtension = this.View.viewExtensionManager.ViewExtensions.OfType().FirstOrDefault(); + // Wait for the NotificationCenterController webview2 control to finish initialization + DispatcherUtil.DoEventsLoop(() => + { + return notificationExtension.notificationCenterController.initState == DynamoUtilities.AsyncMethodState.Done; + }); + Assert.AreEqual(DynamoUtilities.AsyncMethodState.Done, notificationExtension.notificationCenterController.initState); + NotificationUI notificationUI = PresentationSource.CurrentSources.OfType() .Select(h => h.RootVisual) .OfType() diff --git a/test/Libraries/SystemTestServices/SystemTestBase.cs b/test/Libraries/SystemTestServices/SystemTestBase.cs index d7eada732b5..774a09abb68 100644 --- a/test/Libraries/SystemTestServices/SystemTestBase.cs +++ b/test/Libraries/SystemTestServices/SystemTestBase.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Threading; +using System.Windows.Threading; using Dynamo.Configuration; using Dynamo.Controls; using Dynamo.Core; @@ -60,6 +61,7 @@ protected string ExecutingDirectory [SetUp] public virtual void Setup() { + System.Console.WriteLine("Start test: " + TestContext.CurrentContext.Test.Name); var testConfig = GetTestSessionConfiguration(); if (assemblyResolver == null) @@ -137,6 +139,7 @@ public virtual void TearDown() { Console.WriteLine(ex.StackTrace); } + System.Console.WriteLine("Finished test: " + TestContext.CurrentContext.Test.Name); } [OneTimeTearDown] @@ -204,7 +207,7 @@ protected virtual void StartDynamo(TestSessionConfiguration testConfig) View = new DynamoView(ViewModel); View.Show(); - SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); + SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext()); } /// diff --git a/test/VisualizationTests/HelixWatch3DViewModelTests.cs b/test/VisualizationTests/HelixWatch3DViewModelTests.cs index 83dda4484af..832eb83113a 100644 --- a/test/VisualizationTests/HelixWatch3DViewModelTests.cs +++ b/test/VisualizationTests/HelixWatch3DViewModelTests.cs @@ -8,6 +8,7 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media.Media3D; +using System.Windows.Threading; using System.Xml; using CoreNodeModels; using CoreNodeModels.Input; @@ -118,7 +119,7 @@ protected override void StartDynamo(TestSessionConfiguration testConfig) View = new DynamoView(ViewModel); View.Show(); - SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); + SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext()); } ///