// Copyright Epic Games, Inc. All Rights Reserved. #include "SAutomationWindow.h" #include "HAL/PlatformProcess.h" #include "HAL/PlatformApplicationMisc.h" #include "PlatformInfo.h" #include "Misc/MessageDialog.h" #include "Misc/TextFilter.h" #include "Misc/FilterCollection.h" #include "Widgets/Layout/SSplitter.h" #include "SlateOptMacros.h" #include "Textures/SlateIcon.h" #include "Framework/Commands/InputChord.h" #include "Framework/Commands/UIAction.h" #include "Framework/Commands/Commands.h" #include "Framework/Commands/UICommandList.h" #include "Widgets/Images/SImage.h" #include "Framework/MultiBox/MultiBoxDefs.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SButton.h" #include "Widgets/Layout/SSpacer.h" #include "Widgets/Layout/SScrollBox.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SSpinBox.h" #include "SAutomationWindowCommandBar.h" #include "AutomationFilter.h" #include "AutomationGroupFilter.h" #include "AutomationPresetManager.h" #include "SAutomationTestItemContextMenu.h" #include "SAutomationTestItem.h" #include "SSimpleComboButton.h" #if WITH_EDITOR #include "Engine/Level.h" #include "Engine/World.h" #include "FileHelpers.h" #include "AssetRegistry/AssetRegistryModule.h" #endif #include "Widgets/Input/SSearchBox.h" #include "Widgets/Notifications/SNotificationList.h" #include "Widgets/Images/SThrobber.h" #include "Widgets/Input/SHyperlink.h" #include "Internationalization/Regex.h" #include "AutomationWindowStyle.h" #include "AutomationControllerSettings.h" #include "Framework/Notifications/NotificationManager.h" #define LOCTEXT_NAMESPACE "AutomationTest" ////////////////////////////////////////////////////////////////////////// // FAutomationWindowCommands class FAutomationWindowCommands : public TCommands { public: FAutomationWindowCommands() : TCommands( TEXT("AutomationWindow"), NSLOCTEXT("Contexts", "AutomationWindow", "Automation Window"), NAME_None, FAutomationWindowStyle::Get().GetStyleSetName() ) { } virtual void RegisterCommands() override { UI_COMMAND( RefreshTests, "Refresh Tests", "Refresh Tests", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( FindWorkers, "Find Workers", "Find Workers", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( ErrorFilter, "Errors", "Toggle Error Filter", EUserInterfaceActionType::ToggleButton, FInputChord() ); UI_COMMAND( WarningFilter, "Warnings", "Toggle Warning Filter", EUserInterfaceActionType::ToggleButton, FInputChord() ); UI_COMMAND( DeveloperDirectoryContent, "Dev Content", "Developer Directory Content Filter (when enabled, developer directories are also included)", EUserInterfaceActionType::ToggleButton, FInputChord() ); UI_COMMAND( ExcludedTestsFilter, "Excluded Tests", "Toggle Excluded Tests only", EUserInterfaceActionType::ToggleButton, FInputChord()); #if WITH_EDITOR // Added button for running the currently open level test. UI_COMMAND(RunLevelTest, "Run Level Test", "Run Level Test", EUserInterfaceActionType::Button, FInputChord()); #endif UI_COMMAND(CreateNewPreset, "Create Automation Preset", "Create New Preset", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(RemoveCurrentPreset, "Delete", "Remove Current Preset", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(RenameCurrentPreset, "Rename", "Rename Current Preset", EUserInterfaceActionType::Button, FInputChord()); } public: TSharedPtr RefreshTests; TSharedPtr FindWorkers; TSharedPtr ErrorFilter; TSharedPtr WarningFilter; TSharedPtr DeveloperDirectoryContent; TSharedPtr ExcludedTestsFilter; #if WITH_EDITOR TSharedPtr RunLevelTest; #endif TSharedPtr CreateNewPreset; TSharedPtr RemoveCurrentPreset; TSharedPtr RenameCurrentPreset; }; ////////////////////////////////////////////////////////////////////////// // SAutomationWindow SAutomationWindow::SAutomationWindow() : ColumnWidth(90.0f) , bIsLabelVisibilityEnabled(false) , bAutoExpandSingleItemSubgroups(true) { UAutomationControllerSettings* Settings = UAutomationControllerSettings::StaticClass()->GetDefaultObject(); if (nullptr == Settings) { return; } bAutoExpandSingleItemSubgroups = Settings->bAutoExpandSingleItemSubgroups; } SAutomationWindow::~SAutomationWindow() { // @todo PeterMcW: is there an actual delegate missing here? //give the controller a way to indicate it requires a UI update //AutomationController->SetRefreshTestCallback(FOnAutomationControllerTestsRefreshed()); // Remove ourselves from the session manager if( SessionManager.IsValid( ) ) { SessionManager->OnCanSelectSession().RemoveAll(this); SessionManager->OnSelectedSessionChanged().RemoveAll(this); SessionManager->OnSessionInstanceUpdated().RemoveAll(this); } if (AutomationController.IsValid()) { AutomationController->RemoveCallbacks(); AutomationController->OnControllerReset().RemoveAll(this); AutomationController->OnTestsRefreshed().RemoveAll(this); AutomationController->OnTestsAvailable().RemoveAll(this); AutomationController->OnTestsComplete().RemoveAll(this); } #if WITH_EDITOR if ( FModuleManager::Get().IsModuleLoaded(TEXT("AssetRegistry")) ) { IAssetRegistry* AssetRegistry = FModuleManager::GetModuleChecked(TEXT("AssetRegistry")).TryGet(); if (AssetRegistry) { AssetRegistry->OnFileLoadProgressUpdated().RemoveAll(this); } } #endif TSharedPtr PreviousSelectionLock = PreviousSelection.Pin(); if ( PreviousSelectionLock.IsValid() ) { PreviousSelectionLock->OnSetResults.Unbind(); } } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SAutomationWindow::Construct( const FArguments& InArgs, const IAutomationControllerManagerRef& InAutomationController, const TSharedRef& InSessionManager ) { FAutomationWindowCommands::Register(); CreateCommands(); #if WITH_EDITOR FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().OnFilesLoaded().AddSP(this, &SAutomationWindow::OnAssetRegistryFilesLoaded); #endif TestPresetManager = MakeShareable(new FAutomationTestPresetManager()); TestPresetManager->LoadPresets(); bAddingTestPreset = false; bRenamingTestPreset = false; bHasChildTestSelected = false; SessionManager = InSessionManager; AutomationController = InAutomationController; AutomationController->OnControllerReset().AddSP(this, &SAutomationWindow::OnRefreshTestCallback); AutomationController->OnTestsRefreshed().AddSP(this, &SAutomationWindow::OnRefreshTestCallback); AutomationController->OnTestsAvailable().AddSP(this, &SAutomationWindow::OnTestAvailableCallback); AutomationController->OnTestsComplete().AddSP(this, &SAutomationWindow::OnTestsCompleteCallback); AutomationControllerState = AutomationController->GetTestState(); //cache off reference to filtered reports TArray >& TestReports = AutomationController->GetFilteredReports(); // Create the search filter and set criteria AutomationTextFilter = MakeShareable( new AutomationReportTextFilter( AutomationReportTextFilter::FItemToStringArray::CreateSP( this, &SAutomationWindow::PopulateReportSearchStrings ) ) ); AutomationGeneralFilter = MakeShareable( new FAutomationFilter() ); AutomationGroupFilter = MakeShareable( new FAutomationGroupFilter() ); AutomationFilters = MakeShareable( new AutomationFilterCollection() ); AutomationFilters->Add( AutomationTextFilter ); AutomationFilters->Add( AutomationGeneralFilter ); AutomationFilters->Add( AutomationGroupFilter ); bIsRequestingTests = false; //make the widget for platforms PlatformsHBox = SNew(SHorizontalBox); TestTable = SNew(STreeView< TSharedPtr< IAutomationReport > >) .SelectionMode(ESelectionMode::Multi) .TreeItemsSource( &TestReports ) // Generates the actual widget for a tree item .OnGenerateRow( this, &SAutomationWindow::OnGenerateWidgetForTest ) // Gets children .OnGetChildren(this, &SAutomationWindow::OnGetChildren) // on recursive expansion (shift + click) .OnSetExpansionRecursive(this, &SAutomationWindow::OnTestExpansionRecursive) //on selection .OnSelectionChanged(this, &SAutomationWindow::OnTestSelectionChanged) .OnExpansionChanged( this, &SAutomationWindow::OnExpansionChanged) #if WITH_EDITOR // If in editor - add a context menu for opening assets when in editor .OnContextMenuOpening(this, &SAutomationWindow::HandleAutomationListContextMenuOpening) #endif .HeaderRow ( SNew(SHeaderRow) + SHeaderRow::Column( AutomationTestWindowConstants::Checked ) .FixedWidth(30.0f) .HAlignHeader(HAlign_Center) .VAlignHeader(VAlign_Center) .HAlignCell(HAlign_Center) .VAlignCell(VAlign_Center) [ //global enable/disable check box SNew(SCheckBox) .OnCheckStateChanged(this, &SAutomationWindow::HeaderCheckboxStateChange) .ToolTipText(LOCTEXT("Enable Disable Test", "Enable / Disable Test")) ] + SHeaderRow::Column( AutomationTestWindowConstants::Skipped ) .FixedWidth(30.0f) .HAlignHeader(HAlign_Center) .VAlignHeader(VAlign_Center) .HAlignCell(HAlign_Center) .VAlignCell(VAlign_Center) [ SNew(SImage) .ColorAndOpacity(FLinearColor(1.0f, 1.0f, 1.0f, 0.4f)) .ToolTipText(LOCTEXT("Excluded", "Excluded")) .Image(FAutomationWindowStyle::Get().GetBrush("AutomationWindow.ExcludedTestsFilter")) ] + SHeaderRow::Column( AutomationTestWindowConstants::SkippedOptions ) .FixedWidth(30.0f) .HAlignHeader(HAlign_Center) .VAlignHeader(VAlign_Center) .HAlignCell(HAlign_Center) .VAlignCell(VAlign_Center) [ SNew(SImage) .ColorAndOpacity(FLinearColor(1.0f, 1.0f, 1.0f, 0.4f)) .Image(FAutomationWindowStyle::Get().GetBrush("Icons.Edit")) ] + SHeaderRow::Column( AutomationTestWindowConstants::Title ) .FillWidth(0.80f) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew( STextBlock ) .Text( LOCTEXT("TestName_Header", "Test") ) ] ] + SHeaderRow::Column( AutomationTestWindowConstants::SmokeTest ) .FixedWidth( 30.0f ) .HAlignHeader(HAlign_Center) .VAlignHeader(VAlign_Center) .HAlignCell(HAlign_Center) .VAlignCell(VAlign_Center) [ //icon for the smoke test column SNew(SImage) .ColorAndOpacity(FLinearColor(1.0f, 1.0f, 1.0f, 0.4f)) .ToolTipText( LOCTEXT( "Smoke Test", "Smoke Test" ) ) .Image(FAutomationWindowStyle::Get().GetBrush("Automation.SmokeTest")) ] + SHeaderRow::Column( AutomationTestWindowConstants::RequiredDeviceCount ) .FixedWidth(30.0f) .HAlignHeader(HAlign_Center) .VAlignHeader(VAlign_Center) .HAlignCell(HAlign_Center) .VAlignCell(VAlign_Center) [ SNew( SImage ) .Image(FAutomationWindowStyle::Get().GetBrush("Automation.ParticipantsWarning") ) .ToolTipText( LOCTEXT( "RequiredDeviceCountWarningToolTip", "Number of devices required." ) ) ] + SHeaderRow::Column(AutomationTestWindowConstants::Timing) .FixedWidth( 80.0f ) .DefaultLabel(LOCTEXT("TestDurationRange", "Duration")) + SHeaderRow::Column( AutomationTestWindowConstants::Status ) .FixedWidth(ColumnWidth) .HAlignHeader(HAlign_Left) .VAlignHeader(VAlign_Center) [ //platform header placeholder PlatformsHBox.ToSharedRef() ] ); RequestedFilterComboList.Empty(); RequestedFilterComboList.Add(MakeShareable(new FString(TEXT("All Tests")))); RequestedFilterComboList.Add(MakeShareable(new FString(TEXT("Smoke Tests")))); RequestedFilterComboList.Add(MakeShareable(new FString(TEXT("Engine Tests")))); RequestedFilterComboList.Add(MakeShareable(new FString(TEXT("Product Tests")))); RequestedFilterComboList.Add(MakeShareable(new FString(TEXT("Performance Tests")))); RequestedFilterComboList.Add(MakeShareable(new FString(TEXT("Stress Tests")))); RequestedFilterComboList.Add(MakeShareable(new FString(TEXT("Standard Tests")))); RequestedFilterComboList.Add(MakeShareable(new FString(TEXT("Negative Tests")))); UAutomationControllerSettings* Settings = UAutomationControllerSettings::StaticClass()->GetDefaultObject(); TArray TestGroups = Settings->Groups; GroupComboList.Empty(); GroupFiltersMap.Empty(); const FString AllGroups = TEXT("All Groups"); GroupComboList.Add(MakeShareable(new FString(AllGroups))); GroupFiltersMap.Add(AllGroups, TArray()); for (int TestGroupIdx = 0; TestGroupIdx < TestGroups.Num(); TestGroupIdx++) { GroupComboList.Add(MakeShareable(new FString(TestGroups[TestGroupIdx].Name))); GroupFiltersMap.Add(TestGroups[TestGroupIdx].Name, TestGroups[TestGroupIdx].Filters); } TSharedRef NotificationList = SNew(SNotificationList) .Visibility( EVisibility::HitTestInvisible ); //build the actual guts of the window this->ChildSlot [ SNew(SOverlay) + SOverlay::Slot() [ SNew( SSplitter ) .IsEnabled(this, &SAutomationWindow::HandleMainContentIsEnabled) .Orientation(Orient_Vertical) + SSplitter::Slot() .Value(0.66f) [ //automation test panel SAssignNew( MenuBar, SVerticalBox ) //ACTIONS + SVerticalBox::Slot() .AutoHeight() [ SNew( SHorizontalBox ) + SHorizontalBox::Slot() .HAlign(HAlign_Left) .VAlign(VAlign_Center) .FillWidth(1) [ SAutomationWindow::MakeAutomationWindowToolBar( AutomationWindowActions.ToSharedRef(), SharedThis(this) ) ] ] + SVerticalBox::Slot() .FillHeight(1.0f) .Padding(0.0f) [ SNew(SOverlay) + SOverlay::Slot() [ SNew(SBorder) .BorderImage(FAutomationWindowStyle::Get().GetBrush("NoBorder")) .Padding(3) [ SNew(SBox) .Padding(4) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 0.0f, 0.0f, 4.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SBox) .MinDesiredWidth(130.0f) [ SAssignNew(RequestedFilterComboBox, SComboBox< TSharedPtr >) .OptionsSource(&RequestedFilterComboList) .InitiallySelectedItem(RequestedFilterComboList[6]) .OnGenerateWidget(this, &SAutomationWindow::GenerateRequestedFilterComboItem) .OnSelectionChanged(this, &SAutomationWindow::HandleRequesteFilterChanged) .ContentPadding(FMargin(4.0, 1.0f)) [ SNew(STextBlock) .Text(this, &SAutomationWindow::GetRequestedFilterComboText) ] ] ] + SHorizontalBox::Slot() .FillWidth(0.02f) [ SNew(SSpacer) .Visibility(this, &SAutomationWindow::HandleGroupsVisibility) ] + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SBox) .MinDesiredWidth(130.0f) [ SAssignNew(GroupComboBox, SComboBox< TSharedPtr >) .Visibility(this, &SAutomationWindow::HandleGroupsVisibility) .OptionsSource(&GroupComboList) .InitiallySelectedItem(GroupComboList[0]) .OnGenerateWidget(this, &SAutomationWindow::GenerateGroupComboItem) .OnSelectionChanged(this, &SAutomationWindow::HandleGroupChanged) .ContentPadding(FMargin(4.0, 1.0f)) [ SNew(STextBlock) .Text(this, &SAutomationWindow::GetGroupComboText) ] ] ] + SHorizontalBox::Slot() .FillWidth(0.02f) [ SNew(SSpacer) ] + SHorizontalBox::Slot() .FillWidth(0.8f) .VAlign(VAlign_Center) .Padding(2.0f, 0, 0, 0) [ SAssignNew(AutomationSearchBox, SSearchBox) .ToolTipText(LOCTEXT("Search Tests", "Search Tests")) .OnTextChanged(this, &SAutomationWindow::OnFilterTextChanged) .IsEnabled(this, &SAutomationWindow::IsAutomationControllerIdle) ] + SHorizontalBox::Slot() .FillWidth(0.02f) [ SNew(SSpacer) ] +SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .Text( LOCTEXT("AutomationFilterLabel", "Filter:") ) ] +SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SAutomationWindow::MakeAutomationFilterToolBar(AutomationWindowActions.ToSharedRef(), SharedThis(this)) ] ] + SVerticalBox::Slot() .FillHeight(1.0f) [ //the actual table full of tests TestTable.ToSharedRef() ] ] ] ] + SOverlay::Slot() .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew(SThrobber) .Visibility(this, &SAutomationWindow::GetTestsUpdatingThrobberVisibility) ] ] ] + SSplitter::Slot() .Value(0.33f) [ SNew(SVerticalBox) + SVerticalBox::Slot() [ SNew(SOverlay) + SOverlay::Slot() [ SNew(SBox) .Visibility(this, &SAutomationWindow::GetTestGraphVisibility) [ //Graphical Results Panel SNew( SVerticalBox ) + SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .HAlign(HAlign_Left) [ SNew(STextBlock) .Text( LOCTEXT("AutomationTest_GraphicalResults", "Automation Test Graphical Results:")) ] + SHorizontalBox::Slot() .HAlign(HAlign_Right) .AutoWidth() [ SNew(STextBlock) .Text( LOCTEXT("AutomationTest_Display", "Display:")) ] + SHorizontalBox::Slot() .HAlign(HAlign_Right) .AutoWidth() [ SNew(SCheckBox) .Style(FCoreStyle::Get(), "RadioButton") .IsChecked(this, &SAutomationWindow::HandleResultDisplayTypeIsChecked, EAutomationGrapicalDisplayType::DisplayName) .OnCheckStateChanged(this, &SAutomationWindow::HandleResultDisplayTypeStateChanged, EAutomationGrapicalDisplayType::DisplayName) [ SNew(STextBlock) .Text( LOCTEXT("AutomationTest_GraphicalResultsDisplayName", "Name")) ] ] + SHorizontalBox::Slot() .HAlign(HAlign_Right) .AutoWidth() [ SNew(SCheckBox) .Style(FCoreStyle::Get(), "RadioButton") .IsChecked(this, &SAutomationWindow::HandleResultDisplayTypeIsChecked, EAutomationGrapicalDisplayType::DisplayTime) .OnCheckStateChanged(this, &SAutomationWindow::HandleResultDisplayTypeStateChanged, EAutomationGrapicalDisplayType::DisplayTime) [ SNew(STextBlock) .Text( LOCTEXT("AutomationTest_GraphicalResultsDisplayTime", "Time")) ] ] ] + SVerticalBox::Slot() .FillHeight(1.0f) [ SNew(SBorder) [ SNew(SScrollBox) + SScrollBox::Slot() [ SAssignNew(GraphicalResultBox, SAutomationGraphicalResultBox, InAutomationController) ] ] ] ] ] + SOverlay::Slot() [ SNew(SBox) .Visibility(this, &SAutomationWindow::GetTestLogVisibility) [ //results panel SNew( SVerticalBox ) + SVerticalBox::Slot() .AutoHeight() [ SNew( STextBlock ) .Text( LOCTEXT("AutomationTest_Results", "Automation Test Results:") ) ] + SVerticalBox::Slot() .FillHeight(1.0f) .Padding(0.0f, 4.0f, 0.0f, 0.0f) [ //list of results for the selected test SNew(SBorder) .BorderImage(FAutomationWindowStyle::Get().GetBrush("Brushes.Panel")) [ SNew(SScrollBox) .Orientation(EOrientation::Orient_Horizontal) +SScrollBox::Slot() .FillSize(1.0f) [ SAssignNew(LogListView, SListView >) .ListItemsSource(&LogMessages) .SelectionMode(ESelectionMode::Multi) .OnGenerateRow(this, &SAutomationWindow::OnGenerateWidgetForLog) .OnSelectionChanged(this, &SAutomationWindow::HandleLogListSelectionChanged) ] ] ] ] ] ] +SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 4.0f, 0.0f, 0.0f) [ SNew(SBorder) .BorderImage(FAutomationWindowStyle::Get().GetBrush("ToolPanel.GroupBorder")) .Padding(FMargin(8.0f, 6.0f)) [ // Add the command bar SAssignNew(CommandBar, SAutomationWindowCommandBar, NotificationList) .OnCopyLogClicked(this, &SAutomationWindow::HandleCommandBarCopyLogClicked) ] ] ] ] + SOverlay::Slot() .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .Padding( 15.0f ) [ NotificationList ] + SOverlay::Slot() .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .Padding( 15.0f ) [ SNew(SBorder) .BorderImage(FAutomationWindowStyle::Get().GetBrush("NotificationList.ItemBackground")) .Padding(8.0f) .Visibility(this, &SAutomationWindow::HandleSelectSessionOverlayVisibility) [ SNew(STextBlock) .Text(LOCTEXT("SelectSessionOverlayText", "Please select at least one instance from the Session Browser")) ] ] ]; SessionManager->OnCanSelectSession().AddSP( this, &SAutomationWindow::HandleSessionManagerCanSelectSession ); SessionManager->OnSelectedSessionChanged().AddSP( this, &SAutomationWindow::HandleSessionManagerSelectionChanged ); SessionManager->OnSessionInstanceUpdated().AddSP( this, &SAutomationWindow::HandleSessionManagerInstanceChanged ); FindWorkers(); } END_SLATE_FUNCTION_BUILD_OPTIMIZATION void SAutomationWindow::HandleResultDisplayTypeStateChanged( ECheckBoxState NewRadioState, EAutomationGrapicalDisplayType::Type NewDisplayType) { if (NewRadioState == ECheckBoxState::Checked) { GraphicalResultBox->SetDisplayType(NewDisplayType); } } ECheckBoxState SAutomationWindow::HandleResultDisplayTypeIsChecked( EAutomationGrapicalDisplayType::Type InDisplayType ) const { return (GraphicalResultBox->GetDisplayType() == InDisplayType) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } const FSlateBrush* SAutomationWindow::GetTestBackgroundBorderImage() const { switch(TestBackgroundType) { case EAutomationTestBackgroundStyle::Game: return FAutomationWindowStyle::Get().GetBrush("AutomationWindow.GameGroupBorder"); case EAutomationTestBackgroundStyle::Editor: return FAutomationWindowStyle::Get().GetBrush("AutomationWindow.EditorGroupBorder"); case EAutomationTestBackgroundStyle::Unknown: default: return FAutomationWindowStyle::Get().GetBrush("ToolPanel.GroupBorder"); } } void SAutomationWindow::CreateCommands() { check(!AutomationWindowActions.IsValid()); AutomationWindowActions = MakeShareable(new FUICommandList); const FAutomationWindowCommands& Commands = FAutomationWindowCommands::Get(); FUICommandList& ActionList = *AutomationWindowActions; ActionList.MapAction( Commands.RefreshTests, FExecuteAction::CreateRaw( this, &SAutomationWindow::ListTests ), FCanExecuteAction::CreateRaw( this, &SAutomationWindow::IsAutomationControllerIdle ) ); ActionList.MapAction( Commands.FindWorkers, FExecuteAction::CreateRaw( this, &SAutomationWindow::FindWorkers ), FCanExecuteAction::CreateRaw( this, &SAutomationWindow::IsAutomationControllerIdle ) ); ActionList.MapAction( Commands.ErrorFilter, FExecuteAction::CreateRaw( this, &SAutomationWindow::OnToggleErrorFilter ), FCanExecuteAction::CreateRaw( this, &SAutomationWindow::IsAutomationControllerIdle ), FIsActionChecked::CreateRaw( this, &SAutomationWindow::IsErrorFilterOn ) ); ActionList.MapAction( Commands.WarningFilter, FExecuteAction::CreateRaw( this, &SAutomationWindow::OnToggleWarningFilter ), FCanExecuteAction::CreateRaw( this, &SAutomationWindow::IsAutomationControllerIdle ), FIsActionChecked::CreateRaw( this, &SAutomationWindow::IsWarningFilterOn ) ); ActionList.MapAction( Commands.DeveloperDirectoryContent, FExecuteAction::CreateRaw( this, &SAutomationWindow::OnToggleDeveloperDirectoryIncluded ), FCanExecuteAction::CreateRaw( this, &SAutomationWindow::IsAutomationControllerIdle ), FIsActionChecked::CreateRaw( this, &SAutomationWindow::IsDeveloperDirectoryIncluded ) ); ActionList.MapAction( Commands.ExcludedTestsFilter, FExecuteAction::CreateRaw( this, &SAutomationWindow::OnToggleExcludedTestsFilter ), FCanExecuteAction::CreateRaw( this, &SAutomationWindow::IsAutomationControllerIdle ), FIsActionChecked::CreateRaw( this, &SAutomationWindow::IsExcludedTestsFilterOn ) ); // Added button for running the currently open level test. #if WITH_EDITOR ActionList.MapAction(Commands.RunLevelTest, FExecuteAction::CreateRaw(this, &SAutomationWindow::OnRunLevelTest), FCanExecuteAction::CreateRaw(this, &SAutomationWindow::CanExecuteRunLevelTest) ); #endif // WITH_EDITOR ActionList.MapAction(Commands.CreateNewPreset, FExecuteAction::CreateRaw(this, &SAutomationWindow::OnNewPresetClicked), FCanExecuteAction::CreateRaw(this, &SAutomationWindow::IsAutomationControllerIdle) ); ActionList.MapAction(Commands.RemoveCurrentPreset, FExecuteAction::CreateRaw(this, &SAutomationWindow::OnRemovePresetClicked), FCanExecuteAction::CreateRaw(this, &SAutomationWindow::IsAutomationControllerIdle) ); ActionList.MapAction(Commands.RenameCurrentPreset, FExecuteAction::CreateRaw(this, &SAutomationWindow::OnRenamePresetClicked), FCanExecuteAction::CreateRaw(this, &SAutomationWindow::IsAutomationControllerIdle) ); } TSharedRef< SWidget > SAutomationWindow::MakeAutomationWindowToolBar( const TSharedRef& InCommandList, TSharedPtr InAutomationWindow ) { return InAutomationWindow->MakeAutomationWindowToolBar(InCommandList); } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION TSharedRef< SWidget > SAutomationWindow::MakeAutomationWindowToolBar( const TSharedRef& InCommandList ) { struct Local { static void FillToolbar(FToolBarBuilder& ToolbarBuilder, TSharedRef RunTests, TSharedRef DeviceGroups, TSharedRef PresetBox, TWeakPtr InAutomationWindow) { ToolbarBuilder.BeginSection("Automation"); { ToolbarBuilder.AddWidget(RunTests); // Added button for running the currently open level test. #if WITH_EDITOR ToolbarBuilder.AddToolBarButton( FAutomationWindowCommands::Get().RunLevelTest, NAME_None, TAttribute(), LOCTEXT("RunLevelTest_ToolTip", "If the currently loaded editor level is a test map, click this to select the test and run it immediately."), FSlateIcon(FAutomationWindowStyle::Get().GetStyleSetName(), "AutomationWindow.RunLevelTest")); #endif ToolbarBuilder.AddWidget(SNew(SSpacer)); ToolbarBuilder.AddToolBarButton( FAutomationWindowCommands::Get().RefreshTests ); ToolbarBuilder.AddWidget(SNew(SSpacer)); ToolbarBuilder.AddToolBarButton( FAutomationWindowCommands::Get().FindWorkers ); ToolbarBuilder.AddWidget(SNew(SSpacer)); } ToolbarBuilder.EndSection(); ToolbarBuilder.BeginSection("GroupFlags"); { ToolbarBuilder.AddWidget(SNew(SSpacer)); ToolbarBuilder.AddWidget(DeviceGroups); ToolbarBuilder.AddWidget(SNew(SSpacer)); } ToolbarBuilder.EndSection(); ToolbarBuilder.BeginSection("Presets"); { ToolbarBuilder.AddWidget(SNew(SSpacer)); ToolbarBuilder.AddWidget( PresetBox ); ToolbarBuilder.AddWidget(SNew(SSpacer)); } ToolbarBuilder.EndSection(); } }; TSharedRef RunTests = SNew(SHorizontalBox) + SHorizontalBox::Slot() .VAlign( VAlign_Center ) .AutoWidth() .Padding(0) [ SNew( SComboButton ) .ComboButtonStyle( FAppStyle::Get(), "SimpleComboButton" ) .OnGetMenuContent( this, &SAutomationWindow::GenerateTestsOptionsMenuContent ) .ToolTipText( LOCTEXT("TestOptionsToolTip", "Test Options") ) .HasDownArrow(true) .ContentPadding(0) .ButtonContent() [ SNew( SButton ) .ButtonStyle(FAppStyle::Get(), "NoBorder") .ToolTipText( LOCTEXT( "StartStop Tests", "Start / Stop tests" ) ) .OnClicked( this, &SAutomationWindow::RunTests ) .IsEnabled( this, &SAutomationWindow::IsAutomationRunButtonEnabled ) .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .ContentPadding(0) [ SNew( SHorizontalBox ) + SHorizontalBox::Slot() .VAlign( VAlign_Center ) [ SNew( SVerticalBox ) + SVerticalBox::Slot() .AutoHeight() [ SNew( SOverlay ) +SOverlay::Slot() .Padding(0) .VAlign( VAlign_Center ) .HAlign( HAlign_Center ) [ SNew( SImage ) .Image( this, &SAutomationWindow::GetRunAutomationIcon ) ] +SOverlay::Slot() .Padding(0) .VAlign( VAlign_Bottom ) .HAlign( HAlign_Right ) [ SNew( STextBlock ) .Margin( FMargin(22.0f, 25.0f, 0.0, 0.0f) ) .Text( this, &SAutomationWindow::OnGetNumEnabledTestsString ) .ColorAndOpacity(FSlateColor::UseForeground()) .MinDesiredWidth(55.0f) .Justification(ETextJustify::Right) ] ] + SVerticalBox::Slot() .AutoHeight() .HAlign( HAlign_Center ) [ SNew( STextBlock ) .Visibility( bIsLabelVisibilityEnabled ? EVisibility::Visible : EVisibility::Collapsed ) .Text( this, &SAutomationWindow::GetRunAutomationLabel ) .ColorAndOpacity(FSlateColor::UseForeground()) ] ] ] ] ]; TSharedRef DeviceGroups = SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .VAlign(VAlign_Center) .FillHeight(1) [ SNew(SSimpleComboButton) .OnGetMenuContent(this, &SAutomationWindow::GenerateGroupOptionsMenuContent) .Icon(FAutomationWindowStyle::Get().GetBrush(FName("AutomationWindow.GroupSettings" + GetSmallIconExtension()))) .Text(LOCTEXT("GroupOptions_Label", "Device Groups")) .ToolTipText(LOCTEXT("GroupOptionsToolTip", "Device Group Options")) .HasDownArrow(true) ]; TSharedRef TestPresets = SNew( SVerticalBox ) +SVerticalBox::Slot() .FillHeight(1) .VAlign( VAlign_Center ) .Padding( 5.0f ) [ SNew( SHorizontalBox ) +SHorizontalBox::Slot() .AutoWidth() [ SNew( SOverlay ) //Preset Combo / Text +SOverlay::Slot() [ SNew( SHorizontalBox ) + SHorizontalBox::Slot() .VAlign( VAlign_Center ) .FillWidth( 65.0f ) [ SNew( STextBlock ) .Text( LOCTEXT("AutomationPresetLabel", "Preset:") ) ] +SHorizontalBox::Slot() .AutoWidth() [ SNew( SComboButton ) .OnGetMenuContent( this, &SAutomationWindow::GeneratePresetsMenuContent ) .ButtonContent() [ SNew( SHorizontalBox ) + SHorizontalBox::Slot() .FillWidth(1) .VAlign( VAlign_Center ) [ SAssignNew( PresetComboButtonText, STextBlock ) .MinDesiredWidth( 60.0f ) .Text( LOCTEXT("AutomationPreset_None", "None") ) ] ] ] ] +SOverlay::Slot() [ SNew( SHorizontalBox ) .Visibility( this, &SAutomationWindow::HandlePresetTextVisibility ) +SHorizontalBox::Slot() .FillWidth( 100.0f ) [ SAssignNew( PresetTextBox, SEditableTextBox ) .OnTextCommitted( this, &SAutomationWindow::HandlePresetTextCommited ) .IsEnabled( this, &SAutomationWindow::IsAutomationControllerIdle ) ] ] ] //Save button +SHorizontalBox::Slot() .AutoWidth() [ SNew( SButton ) .ButtonStyle(FAutomationWindowStyle::Get(), "NoBorder" ) .OnClicked( this, &SAutomationWindow::HandleSavePresetClicked ) .ToolTipText( LOCTEXT("AutomationPresetSaveButtonTooltip", "Save the current test list") ) .IsEnabled(this, &SAutomationWindow::IsSaveButtonEnabled) .Content() [ SNew( SImage ) .Image(FAutomationWindowStyle::Get().GetBrush("Icons.Save")) ] ] ]; FToolBarBuilder ToolbarBuilder( InCommandList, FMultiBoxCustomization::None ); ToolbarBuilder.SetLabelVisibility(bIsLabelVisibilityEnabled ? EVisibility::Visible : EVisibility::Collapsed); TWeakPtr AutomationWindow = SharedThis(this); Local::FillToolbar(ToolbarBuilder, RunTests, DeviceGroups, TestPresets, AutomationWindow); // Create the tool bar! return SNew( SHorizontalBox ) +SHorizontalBox::Slot() .AutoWidth() [ SNew( SBorder ) .Padding(0) .BorderImage(FAutomationWindowStyle::Get().GetBrush("NoBorder") ) .IsEnabled( FSlateApplication::Get().GetNormalExecutionAttribute() ) [ ToolbarBuilder.MakeWidget() ] ]; } END_SLATE_FUNCTION_BUILD_OPTIMIZATION TSharedRef< SWidget > SAutomationWindow::MakeAutomationFilterToolBar(const TSharedRef& InCommandList, TSharedPtr InAutomationWindow) { return InAutomationWindow->MakeAutomationFilterToolBar(InCommandList); } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION TSharedRef< SWidget > SAutomationWindow::MakeAutomationFilterToolBar(const TSharedRef& InCommandList) { struct Local { static void FillToolbar(FToolBarBuilder& ToolbarBuilder) { ToolbarBuilder.BeginSection("Filters"); { ToolbarBuilder.AddWidget(SNew(SSpacer)); ToolbarBuilder.AddToolBarButton(FAutomationWindowCommands::Get().ErrorFilter); ToolbarBuilder.AddToolBarButton(FAutomationWindowCommands::Get().WarningFilter); ToolbarBuilder.AddToolBarButton(FAutomationWindowCommands::Get().ExcludedTestsFilter); ToolbarBuilder.AddToolBarButton(FAutomationWindowCommands::Get().DeveloperDirectoryContent); ToolbarBuilder.AddWidget(SNew(SSpacer)); } ToolbarBuilder.EndSection(); } }; FToolBarBuilder ToolbarBuilder(InCommandList, FMultiBoxCustomization::None); ToolbarBuilder.SetLabelVisibility(EVisibility::Collapsed); TWeakPtr AutomationWindow = SharedThis(this); Local::FillToolbar(ToolbarBuilder); // Create the tool bar! return SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(SBorder) .Padding(0) .BorderImage(FAutomationWindowStyle::Get().GetBrush("NoBorder")) .IsEnabled(FSlateApplication::Get().GetNormalExecutionAttribute()) [ ToolbarBuilder.MakeWidget() ] ]; } END_SLATE_FUNCTION_BUILD_OPTIMIZATION EVisibility SAutomationWindow::HandlePresetComboVisibility( ) const { return bAddingTestPreset ? EVisibility::Hidden : EVisibility::Visible; } EVisibility SAutomationWindow::HandlePresetTextVisibility( ) const { return bAddingTestPreset || bRenamingTestPreset ? EVisibility::Visible : EVisibility::Hidden; } EVisibility SAutomationWindow::HandleGroupsVisibility() const { return GroupComboList.Num() == 1 ? EVisibility::Collapsed : EVisibility::Visible; } bool SAutomationWindow::IsSaveButtonEnabled() const { return (!bAddingTestPreset && !bRenamingTestPreset && SelectedPreset.IsValid() && IsAutomationControllerIdle()); } void SAutomationWindow::HandlePresetTextCommited( const FText& CommittedText, ETextCommit::Type CommitType ) { if( CommitType == ETextCommit::OnEnter ) { if ( bAddingTestPreset ) { bAddingTestPreset = false; if (CommittedText.IsEmpty()) { return; } TArray EnabledTests; AutomationController->GetEnabledTestNames(EnabledTests); AutomationPresetPtr NewPreset = TestPresetManager->AddNewPreset(CommittedText, EnabledTests); if (NewPreset.IsValid()) { SelectedPreset = NewPreset; PresetTextBox->SetText(FText()); } } else if (bRenamingTestPreset) { bRenamingTestPreset = false; if (CommittedText.IsEmpty()) { return; } SelectedPreset->SetName(CommittedText); PresetTextBox->SetText(FText()); } PresetComboButtonText->SetText(CommittedText); } else if( CommitType == ETextCommit::OnCleared || CommitType == ETextCommit::OnUserMovedFocus ) { if( bAddingTestPreset || bRenamingTestPreset ) { bAddingTestPreset = false; bRenamingTestPreset = false; SelectedPreset = nullptr; PresetTextBox->SetText(FText()); } } } void SAutomationWindow::HandleRequesteFilterChanged(TSharedPtr Item, ESelectInfo::Type SelectInfo) { const int32 EntryIndex = RequestedFilterComboList.Find(Item); EAutomationTestFlags NewRequestedFlags = EAutomationTestFlags::SmokeFilter; switch (EntryIndex) { case 0: // "All Tests" NewRequestedFlags = EAutomationTestFlags_FilterMask; break; case 1: // "Smoke Tests" NewRequestedFlags = EAutomationTestFlags::SmokeFilter; break; case 2: // "Engine Tests" NewRequestedFlags = EAutomationTestFlags::EngineFilter; break; case 3: // "Product Tests" NewRequestedFlags = EAutomationTestFlags::ProductFilter; break; case 4: // "Performance Tests" NewRequestedFlags = EAutomationTestFlags::PerfFilter; break; case 5: // "Stress Tests" NewRequestedFlags = EAutomationTestFlags::StressFilter; break; case 6: // "Standard Tests" NewRequestedFlags = EAutomationTestFlags::SmokeFilter | EAutomationTestFlags::EngineFilter | EAutomationTestFlags::ProductFilter | EAutomationTestFlags::PerfFilter; break; case 7: // "Negative Tests" NewRequestedFlags = EAutomationTestFlags::NegativeFilter; break; } AutomationController->SetRequestedTestFlags(NewRequestedFlags); } void SAutomationWindow::HandleGroupChanged(TSharedPtr Item, ESelectInfo::Type SelectInfo) { AutomationGroupFilter->SetFilters(GroupFiltersMap[*Item.Get()]); OnRefreshTestCallback(); } void SAutomationWindow::ExpandEnabledTests( TSharedPtr< IAutomationReport > InReport ) { // Expand node if the report is enabled or contains an enabled test TestTable->SetItemExpansion( InReport, InReport->IsEnabled() || InReport->GetEnabledTestsNum() > 0 ); // Iterate through the child nodes to see if they should be expanded TArray > Reports = InReport->GetFilteredChildren(); for ( int32 ChildItem = 0; ChildItem < Reports.Num(); ChildItem++ ) { ExpandEnabledTests( Reports[ ChildItem ] ); } } FReply SAutomationWindow::HandleSavePresetClicked() { FText PresetSaveStatusText; bool bSavedSuccessfully = false; if (SelectedPreset.IsValid()) { TArray EnabledTests; AutomationController->GetEnabledTestNames(EnabledTests); SelectedPreset->SetEnabledTests(EnabledTests); bSavedSuccessfully = TestPresetManager->SavePreset(SelectedPreset.ToSharedRef()); if (bSavedSuccessfully) { PresetSaveStatusText = FText::Format(LOCTEXT("AutomationPresetSaveSucceeded", "Preset '{0}' saved successfully."), SelectedPreset->GetName()); } else { PresetSaveStatusText = FText::Format(LOCTEXT("AutomationPresetSaveFailed", "Failed to save preset '{0}'."), SelectedPreset->GetName()); } } const FNotificationInfo SaveNotificationInfo = FNotificationInfo(PresetSaveStatusText); TSharedPtr NotificationPtr = FSlateNotificationManager::Get().AddNotification(SaveNotificationInfo); if (NotificationPtr) { NotificationPtr->SetCompletionState(bSavedSuccessfully ? SNotificationItem::CS_Success : SNotificationItem::CS_Fail); } return FReply::Handled(); } void SAutomationWindow::OnNewPresetClicked() { bAddingTestPreset = true; FSlateApplication::Get().SetUserFocus(0, PresetTextBox.ToSharedRef(), EFocusCause::SetDirectly); } void SAutomationWindow::OnRemovePresetClicked() { if ( SelectedPreset.IsValid() ) { TestPresetManager->RemovePreset(SelectedPreset.ToSharedRef()); SelectedPreset = nullptr; PresetComboButtonText->SetText(LOCTEXT("AutomationPresetComboLabel", "None")); } } void SAutomationWindow::OnRenamePresetClicked() { if ( SelectedPreset.IsValid() ) { bRenamingTestPreset = true; PresetTextBox->SetText(SelectedPreset->GetName()); FSlateApplication::Get().SetUserFocus(0, PresetTextBox.ToSharedRef(), EFocusCause::SetDirectly); } } FText SAutomationWindow::GetPresetComboText() const { if ( SelectedPreset.IsValid() ) { return SelectedPreset->GetName(); } else { return LOCTEXT("AutomationPresetComboLabel", "None"); } } FText SAutomationWindow::GetRequestedFilterComboText() const { if (RequestedFilterComboBox->GetSelectedItem().IsValid()) { return FText::FromString(*RequestedFilterComboBox->GetSelectedItem()); } else { return LOCTEXT("AutomationRequestedFilterComboLabel", "All Tests"); } } FText SAutomationWindow::GetGroupComboText() const { if (GroupComboBox->GetSelectedItem().IsValid()) { return FText::FromString(*GroupComboBox->GetSelectedItem()); } else { return LOCTEXT("AutomationGroupComboLabel", "All Groups"); } } TSharedRef SAutomationWindow::GenerateRequestedFilterComboItem(TSharedPtr InItem) { return SNew(STextBlock) .Text(FText::FromString(*InItem)); } TSharedRef SAutomationWindow::GenerateGroupComboItem(TSharedPtr InItem) { return SNew(STextBlock) .Text(FText::FromString(*InItem)); } TSharedRef< SWidget > SAutomationWindow::GenerateGroupOptionsMenuContent( TWeakPtr InAutomationWindow ) { TSharedPtr AutomationWindow(InAutomationWindow.Pin()); if( AutomationWindow.IsValid() ) { return AutomationWindow->GenerateGroupOptionsMenuContent(); } //Return empty menu FMenuBuilder MenuBuilder( true, nullptr ); MenuBuilder.BeginSection("AutomationWindowGroupOptions", LOCTEXT("DeviceGroupOptions", "Device Group Options")); MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } TSharedRef< SWidget > SAutomationWindow::GenerateGroupOptionsMenuContent( ) { const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, AutomationWindowActions ); const uint32 NumFlags = EAutomationDeviceGroupTypes::Max; TSharedPtr FlagWidgets[NumFlags]; for( int32 i=0; i SAutomationWindow::GeneratePresetsMenuContent( TWeakPtr InAutomationWindow ) { TSharedPtr AutomationWindow(InAutomationWindow.Pin()); if (AutomationWindow.IsValid()) { return AutomationWindow->GenerateGroupOptionsMenuContent(); } //Return empty menu FMenuBuilder MenuBuilder(true, nullptr); return MenuBuilder.MakeWidget(); } TSharedRef< SWidget > SAutomationWindow::GeneratePresetsMenuContent() { const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, AutomationWindowActions); MenuBuilder.BeginSection("AutomationWindowNewPresetGroup", LOCTEXT("GroupNewPreset", "New")); MenuBuilder.AddMenuEntry(FAutomationWindowCommands::Get().CreateNewPreset); MenuBuilder.EndSection(); const TArray& Presets = TestPresetManager->GetAllPresets(); TSharedPtr>> CheckBoxesSP = MakeShared>>(); MenuBuilder.BeginSection("AutomationWindowCurrentPresetGroup", LOCTEXT("GroupCurrentPreset", "Current Preset")); { MenuBuilder.AddMenuEntry(FAutomationWindowCommands::Get().RenameCurrentPreset); MenuBuilder.AddMenuEntry(FAutomationWindowCommands::Get().RemoveCurrentPreset); for (int32 i = 0; i < Presets.Num(); i++) { const auto& preset = Presets[i]; TSharedPtr CheckBox = SNew(SCheckBox) .Style(FAutomationWindowStyle::Get(), "AutomationWindow.ToggleButton") .IsChecked(preset.IsValid() && SelectedPreset.IsValid() && preset->GetID() == SelectedPreset->GetID()) .OnCheckStateChanged(this, &SAutomationWindow::HandlePresetCheckStateChanged, i, CheckBoxesSP) .Padding(FMargin(4.0f, 0.0f)) .IsEnabled(this, &SAutomationWindow::IsAutomationControllerIdle) .Content() [ SNew(STextBlock) .Text(preset.IsValid() ? preset.ToSharedRef()->GetName() : LOCTEXT("AutomationPreset_None", "None")) ]; MenuBuilder.AddWidget(CheckBox.ToSharedRef(), FText::GetEmpty()); CheckBoxesSP->Add(CheckBox); } } return MenuBuilder.MakeWidget(); } /** Returns if full size screen shots are enabled */ ECheckBoxState SAutomationWindow::IsDeviceGroupCheckBoxIsChecked(const int32 DeviceGroupFlag) const { return AutomationController->IsDeviceGroupFlagSet((EAutomationDeviceGroupTypes::Type)DeviceGroupFlag) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } /** Toggles if we are collecting full size screenshots */ void SAutomationWindow::HandleDeviceGroupCheckStateChanged(ECheckBoxState CheckBoxState, const int32 DeviceGroupFlag) { //Update the device groups AutomationController->ToggleDeviceGroupFlag((EAutomationDeviceGroupTypes::Type)DeviceGroupFlag); AutomationController->UpdateDeviceGroups(); //Update header RebuildPlatformIcons(); //Need to force the tree to do a full refresh here because the reports have changed but the tree will keep using cached data. TestTable->RebuildList(); } void SAutomationWindow::HandlePresetCheckStateChanged(ECheckBoxState CheckBoxState, const int32 EntryIndex, TSharedPtr>> CheckBoxes) { if (CheckBoxState != ECheckBoxState::Checked) { SelectedPreset = nullptr; PresetComboButtonText->SetText(LOCTEXT("AutomationPreset_None", "None")); return; } for (int32 i = 0; i < CheckBoxes->Num(); i++) { if (EntryIndex != i) { (*CheckBoxes)[i].Get()->SetIsChecked(false); } } SelectedPreset = *(&TestPresetManager->GetAllPresets()[EntryIndex]); PresetComboButtonText->SetText(SelectedPreset.IsValid() ? SelectedPreset.Get()->GetName() : LOCTEXT("AutomationPreset_None", "None")); if (SelectedPreset.IsValid()) { AutomationController->SetEnabledTests(SelectedPreset->GetEnabledTests()); TestTable->RequestTreeRefresh(); //Expand selected items TestTable->ClearExpandedItems(); TArray< TSharedPtr< IAutomationReport > >& TestReports = AutomationController->GetFilteredReports(); for (int32 Index = 0; Index < TestReports.Num(); Index++) { ExpandEnabledTests(TestReports[Index]); } } } TSharedRef< SWidget > SAutomationWindow::GenerateTestsOptionsMenuContent( TWeakPtr InAutomationWindow ) { TSharedPtr AutomationWindow(InAutomationWindow.Pin()); if( AutomationWindow.IsValid() ) { return AutomationWindow->GenerateTestsOptionsMenuContent(); } //Return empty menu FMenuBuilder MenuBuilder( true, nullptr ); MenuBuilder.BeginSection("AutomationWindowRunTest", LOCTEXT("RunTestOptions", "Advanced Settings")); MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } TSharedRef< SWidget > SAutomationWindow::GenerateTestsOptionsMenuContent( ) { const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, AutomationWindowActions ); TSharedRef NumTests = SNew(SBox) .WidthOverride( 200.0f ) [ SNew( SHorizontalBox ) +SHorizontalBox::Slot() .Padding(0.0f,0.0f,4.0f, 0.0f) .AutoWidth() [ SNew( STextBlock ) .Text( LOCTEXT("NumTestsToolTip", "Number of runs:") ) ] +SHorizontalBox::Slot() .FillWidth(1.f) [ SNew(SSpinBox) .MinValue(1) .MaxValue(1000) .MinSliderValue(1) .MaxSliderValue(1000) .Value(this,&SAutomationWindow::GetRepeatCount) .OnValueChanged(this,&SAutomationWindow::OnChangeRepeatCount) .IsEnabled( this, &SAutomationWindow::IsAutomationControllerIdle ) ] ]; TSharedRef SendAnalyticsWidget = SNew(SCheckBox) .IsChecked(this, &SAutomationWindow::IsSendAnalyticsCheckBoxChecked) .OnCheckStateChanged(this, &SAutomationWindow::HandleSendAnalyticsBoxCheckStateChanged) .Padding(FMargin(4.0f, 0.0f)) .ToolTipText(LOCTEXT("AutomationSendAnalyticsTip", "If checked, tests send analytics results to the backend")) .IsEnabled(this, &SAutomationWindow::IsAutomationControllerIdle) .Content() [ SNew(STextBlock) .Text(LOCTEXT("AutomationSendAnalyticsText", "Enable analytics")) ]; TSharedRef KeepPIEOpenWidget = SNew(SCheckBox) .IsChecked(this, &SAutomationWindow::KeepPIEOpenCheckBoxChecked) .OnCheckStateChanged(this, &SAutomationWindow::HandleKeepPIEOpenBoxCheckStateChanged) .Padding(FMargin(4.0f, 0.0f)) .ToolTipText(LOCTEXT("AutomationKeepPIEOpenTip", "If checked, the PIE will be kept open at the end of the test pass")) .IsEnabled(this, &SAutomationWindow::IsAutomationControllerIdle) .Content() [ SNew(STextBlock) .Text(LOCTEXT("AutomationKeepPIEOpenText", "Keep PIE open at the end")) ]; TSharedRef AutoExpandSingleItemSubgroupsWidget = SNew(SCheckBox) .IsChecked(this, &SAutomationWindow::AutoExpandSingleItemSubgroupsCheckBoxChecked) .OnCheckStateChanged(this, &SAutomationWindow::HandleAutoExpandSingleItemSubgroupsCheckStateChanged) .Padding(FMargin(4.0f, 0.0f)) .ToolTipText(LOCTEXT("AutomationAutoExpandSingleItemSubgroupsTip", "If checked, automatic expansion of single-item test subgroups will be enabled")) .IsEnabled(this, &SAutomationWindow::IsAutomationControllerIdle) .Content() [ SNew(STextBlock) .Text(LOCTEXT("AutomationAutoExpandSingleItemSubgroupsText", "Auto expand single-item subgroups")) ]; MenuBuilder.BeginSection("AutomationWindowRunTest", LOCTEXT("RunTestOptions", "Advanced Settings")); { MenuBuilder.AddWidget(NumTests, FText::GetEmpty()); MenuBuilder.AddWidget(SendAnalyticsWidget, FText::GetEmpty()); #if WITH_EDITOR MenuBuilder.AddWidget(KeepPIEOpenWidget, FText::GetEmpty()); #endif //WITH_EDITOR MenuBuilder.AddWidget(AutoExpandSingleItemSubgroupsWidget, FText::GetEmpty()); } MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } ECheckBoxState SAutomationWindow::IsSendAnalyticsCheckBoxChecked() const { return AutomationController->IsSendAnalytics() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void SAutomationWindow::HandleSendAnalyticsBoxCheckStateChanged(ECheckBoxState CheckBoxState) { AutomationController->SetSendAnalytics(CheckBoxState == ECheckBoxState::Checked); } ECheckBoxState SAutomationWindow::KeepPIEOpenCheckBoxChecked() const { return AutomationController->KeepPIEOpen() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void SAutomationWindow::HandleKeepPIEOpenBoxCheckStateChanged(ECheckBoxState CheckBoxState) { AutomationController->SetKeepPIEOpen(CheckBoxState == ECheckBoxState::Checked); } /** Returns if we should automatically expand single-item test subgroups */ ECheckBoxState SAutomationWindow::AutoExpandSingleItemSubgroupsCheckBoxChecked() const { return bAutoExpandSingleItemSubgroups ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } /** Toggles if automatic expansion of single-item subgroups is enabled */ void SAutomationWindow::HandleAutoExpandSingleItemSubgroupsCheckStateChanged(ECheckBoxState CheckBoxState) { bAutoExpandSingleItemSubgroups = (CheckBoxState == ECheckBoxState::Checked); } TArray SAutomationWindow::SaveExpandedTestNames(TSet> ExpandedItems) { TArray ExpandedItemsNames; for ( TSharedPtr ExpandedItem : ExpandedItems ) { ExpandedItemsNames.Add(ExpandedItem->GetDisplayNameWithDecoration()); } return ExpandedItemsNames; } // Expanded the given item if its name is in the array of strings given. void SAutomationWindow::ExpandItemsInList(TSharedPtr>> InTestTable, TSharedPtr InReport, TArray ItemsToExpand) { InTestTable->SetItemExpansion(InReport, ItemsToExpand.Contains(InReport->GetDisplayNameWithDecoration())); TArray> ChildReports = InReport->GetFilteredChildren(); for ( int32 Index = 0; Index < ChildReports.Num(); Index++ ) { ExpandItemsInList(InTestTable, ChildReports[Index], ItemsToExpand); } } // Only valid in the editor #if WITH_EDITOR TSharedPtr SAutomationWindow::HandleAutomationListContextMenuOpening() { TArray< TSharedPtr >SelectedReport = TestTable->GetSelectedItems(); TArray TestNames; TArray AssetNames; for (TSharedPtr Report : SelectedReport) { if (Report.IsValid()) { FString TestName = Report->GetFullTestPath(); if (!TestName.IsEmpty()) { TestNames.Add(MoveTemp(TestName)); } FString Param = Report->GetTestParameter(); if (Param.StartsWith(TEXT("/"))) { // Assume that if parameter start with a "/", it should be an asset AssetNames.Add(MoveTemp(Param)); } } } if (AssetNames.Num() || TestNames.Num()) { return SNew(SAutomationTestItemContextMenu, AssetNames, TestNames); } return nullptr; } void SAutomationWindow::RunSelectedTests() { AutomationController->SetVisibleTestsEnabled(false); SetAllSelectedTestsChecked(true); RunTests(); } namespace { /** * Kind of a hack - this requires that we know we group all the map tests coming from blueprints under "Functional Tests" */ TSharedPtr GetFunctionalTestsReport(const TArray< TSharedPtr< IAutomationReport > >& TestReports) { for ( auto& Report : TestReports ) { if ( Report->GetDisplayName() == TEXT("Functional Tests") ) { return Report; } auto FoundInChild = GetFunctionalTestsReport(Report->GetChildReports()); if ( FoundInChild.IsValid() ) { return FoundInChild; } } return TSharedPtr(); } void FindReportByGameRelativeAssetPath(const TSharedPtr& RootReport, const FString& AssetRelativePath, TArray>& OutLevelReports) { FString TestAssetRelativePath(RootReport->GetTestParameter()); if ( TestAssetRelativePath.StartsWith(AssetRelativePath) ) { OutLevelReports.Add(RootReport); } else { // Branch node for ( auto ChildReport : RootReport->GetChildReports() ) { FindReportByGameRelativeAssetPath(ChildReport, AssetRelativePath, OutLevelReports); } } } } // namespace void SAutomationWindow::FindTestReportsForCurrentEditorLevel(TArray>& OutLevelReports) { // Find the current map path if ( GWorld && GWorld->GetCurrentLevel() && GWorld->GetCurrentLevel()->GetPackage()) { const FString MapPath = GWorld->GetCurrentLevel()->GetPackage()->GetPathName(); if (!MapPath.IsEmpty()) { auto FunctionTestsReport = GetFunctionalTestsReport(AutomationController->GetFilteredReports()); if (FunctionTestsReport.IsValid()) { FindReportByGameRelativeAssetPath(FunctionTestsReport, MapPath, OutLevelReports); } } } } bool SAutomationWindow::CanExecuteRunLevelTest() { return IsAutomationControllerIdle(); } void SAutomationWindow::OnRunLevelTest() { TArray> LevelReports; FindTestReportsForCurrentEditorLevel(LevelReports); if ( LevelReports.Num() > 0 ) { TestTable->ClearSelection(); for ( auto& LevelReport : LevelReports ) { TestTable->SetItemSelection(LevelReport, true); } ScrollToTest(LevelReports[0]); RunSelectedTests(); } } void SAutomationWindow::ScrollToTest(TSharedPtr InReport) { auto& RootReports = AutomationController->GetFilteredReports(); for ( auto ChildReport : RootReports ) { auto ShouldExpand = ExpandToTest(ChildReport, InReport); TestTable->SetItemExpansion(ChildReport, ShouldExpand); } TestTable->RequestScrollIntoView(InReport); } bool SAutomationWindow::ExpandToTest(TSharedPtr InRoot, TSharedPtr InReport) { if ( InRoot == InReport ) return true; bool WasExpanded = false; for ( auto ChildReport : InRoot->GetChildReports() ) { auto ShouldExpand = ExpandToTest(ChildReport, InReport); TestTable->SetItemExpansion(ChildReport, ShouldExpand); if ( ShouldExpand ) { // Here we could just return true, but we want to collapse all the other reports // so we keep going and just remember that we found the test. WasExpanded = true; } } return WasExpanded; } #endif void SAutomationWindow::PopulateReportSearchStrings( const TSharedPtr< IAutomationReport >& Report, OUT TArray< FString >& OutSearchStrings ) const { if( !Report.IsValid() ) { return; } OutSearchStrings.Add( Report->GetDisplayName() ); OutSearchStrings.Add( Report->GetFullTestPath() ); } void SAutomationWindow::OnExpansionChanged(TSharedPtr InItem, bool bExpanded) { ExpandSingleItemSubgroups(InItem, bExpanded); } void SAutomationWindow::ExpandSingleItemSubgroups(TSharedPtr InItem, bool bExpanded) { check(InItem.IsValid()); if (bAutoExpandSingleItemSubgroups && bExpanded) { TArray>& FilteredChildren = InItem->GetFilteredChildren(); if (FilteredChildren.Num() == 1) { TSharedPtr SingleChild = FilteredChildren.Top(); check(SingleChild.IsValid()); if (!SingleChild->IsParent()) { return; } TestTable->SetItemExpansion(InItem, bExpanded); TArray>& SingleChildFilteredChildren = SingleChild->GetFilteredChildren(); static auto IsChildPredicate = [](const TSharedPtr& InItemToCheck) -> bool { check(InItemToCheck.IsValid()); return (!InItemToCheck->IsParent()); }; const bool bSingleChildHasAtLeastOneChildLeaf = (nullptr != SingleChildFilteredChildren.FindByPredicate(IsChildPredicate)); if (bSingleChildHasAtLeastOneChildLeaf) { return; } ExpandSingleItemSubgroups(SingleChild, bExpanded); } } } void SAutomationWindow::OnGetChildren(TSharedPtr InItem, TArray >& OutItems) { OutItems = InItem->GetFilteredChildren(); } void SAutomationWindow::OnTestExpansionRecursive(TSharedPtr InAutomationReport, bool bInIsItemExpanded) { if ( InAutomationReport.IsValid() ) { TArray >& FilteredChildren = InAutomationReport->GetFilteredChildren(); TestTable->SetItemExpansion(InAutomationReport, bInIsItemExpanded); for ( TSharedPtr& Child : FilteredChildren ) { OnTestExpansionRecursive(Child, bInIsItemExpanded); } } } void SAutomationWindow::OnTestSelectionChanged(TSharedPtr Selection, ESelectInfo::Type /*SelectInfo*/) { TSharedPtr PreviousSelectionLock = PreviousSelection.Pin(); if ( PreviousSelectionLock.IsValid() ) { PreviousSelectionLock->OnSetResults.Unbind(); } bHasChildTestSelected = false; UpdateTestLog(Selection); if ( Selection.IsValid() ) { Selection->OnSetResults.BindRaw(this, &SAutomationWindow::UpdateTestLog); PreviousSelection = Selection; if ( Selection->GetTotalNumChildren() == 0 ) { bHasChildTestSelected = true; } } CommandBar->SetCopyButtonVisibility(GetTestLogVisibility()); } void SAutomationWindow::UpdateTestLog(TSharedPtr Selection) { //empty the previous log LogMessages.Empty(); if (Selection.IsValid()) { //accumulate results for each device cluster that supports the test int32 NumClusters = AutomationController->GetNumDeviceClusters(); for (int32 ClusterIndex = 0; ClusterIndex < NumClusters; ++ClusterIndex) { //no sense displaying device name if only one is available if (NumClusters > 1) { FString DeviceTypeName = AutomationController->GetClusterGroupName(ClusterIndex) + TEXT(" - ") + Selection->GetGameInstanceName(ClusterIndex); LogMessages.Add(MakeShareable(new FAutomationOutputMessage(DeviceTypeName, TEXT("Automation.Header")))); } const int32 NumOfPasses = Selection->GetNumResults(ClusterIndex); for( int32 PassIndex = 0; PassIndex < NumOfPasses; ++PassIndex ) { //get strings out of the report and populate the Log Messages FAutomationTestResults TestResults = Selection->GetResults(ClusterIndex,PassIndex); //no sense displaying device name if only one is available if (NumOfPasses > 1) { FString PassHeader = LOCTEXT("TestPassHeader", "Pass:").ToString(); PassHeader += FString::Printf(TEXT("%i"),PassIndex+1); LogMessages.Add(MakeShareable(new FAutomationOutputMessage(PassHeader, TEXT("Automation.Header")))); } for (const FAutomationExecutionEntry& Entry : TestResults.GetEntries()) { switch (Entry.Event.Type) { case EAutomationEventType::Info: LogMessages.Add(MakeShareable(new FAutomationOutputMessage(Entry.ToString(), TEXT("Automation.Normal")))); break; case EAutomationEventType::Warning: LogMessages.Add(MakeShareable(new FAutomationOutputMessage(Entry.ToString(), TEXT("Automation.Warning")))); break; case EAutomationEventType::Error: LogMessages.Add(MakeShareable(new FAutomationOutputMessage(Entry.ToString(), TEXT("Automation.Error")))); break; } } if ( ( TestResults.GetWarningTotal() == 0 ) && ( TestResults.GetErrorTotal() == 0 ) && ( Selection->GetState(ClusterIndex, PassIndex) == EAutomationState::Success ) ) { LogMessages.Add(MakeShareable(new FAutomationOutputMessage(LOCTEXT("AutomationTest_SuccessMessage", "Success").ToString(), TEXT("Automation.Normal")))); } LogMessages.Add(MakeShareable(new FAutomationOutputMessage(TEXT(""), TEXT("Log.Normal")))); } } } //rebuild UI LogListView->RequestListRefresh(); } EVisibility SAutomationWindow::GetTestLogVisibility( ) const { return (GetTestGraphVisibility() == EVisibility::Visible) ? EVisibility::Hidden : EVisibility::Visible; } EVisibility SAutomationWindow::GetTestGraphVisibility( ) const { //Show the graphical window if we don't have a child test selected and we have results to view return (!bHasChildTestSelected && GraphicalResultBox->HasResults()) ? EVisibility::Visible : EVisibility::Hidden; } void SAutomationWindow::HeaderCheckboxStateChange(ECheckBoxState InCheckboxState) { const bool bState = (InCheckboxState == ECheckBoxState::Checked)? true : false; AutomationController->SetVisibleTestsEnabled(bState); } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SAutomationWindow::RebuildPlatformIcons() { //empty header UI PlatformsHBox->ClearChildren(); //for each device type int32 NumClusters = AutomationController->GetNumDeviceClusters(); for (int32 ClusterIndex = 0; ClusterIndex < NumClusters; ++ClusterIndex) { PlatformsHBox->AddSlot() .AutoWidth() .MaxWidth(ColumnWidth) [ SNew(STextBlock) .Text(FText::Format( LOCTEXT("GameInstances_Header", "Instances ({0})"), FText::AsNumber( AutomationController->GetNumDevicesInCluster(ClusterIndex) ) )) .ToolTipText(CreateDeviceTooltip(ClusterIndex)) ]; } } END_SLATE_FUNCTION_BUILD_OPTIMIZATION FText SAutomationWindow::CreateDeviceTooltip(int32 ClusterIndex) { FTextBuilder ReportBuilder; const int32 NumClusters = AutomationController->GetNumDeviceClusters(); if( NumClusters > 1 ) { ReportBuilder.AppendLine(LOCTEXT("ToolTipClusterName", "Cluster Name:")); ReportBuilder.AppendLine(AutomationController->GetClusterGroupName(ClusterIndex)); } ReportBuilder.AppendLine(LOCTEXT("ToolTipGameInstances", "Game Instances:")); int32 NumDevices = AutomationController->GetNumDevicesInCluster( ClusterIndex ); for ( int32 DeviceIndex = 0; DeviceIndex < NumDevices; ++DeviceIndex ) { ReportBuilder.AppendLine(AutomationController->GetGameInstanceName(ClusterIndex, DeviceIndex).LeftPad(2)); } return ReportBuilder.ToText(); } void SAutomationWindow::ClearAutomationUI () { // Clear results from the automation controller AutomationController->ClearAutomationReports(); TestTable->RequestTreeRefresh(); // Clear the platform icons if (PlatformsHBox.IsValid()) { PlatformsHBox->ClearChildren(); } // Clear the log LogMessages.Empty(); LogListView->RequestListRefresh(); } TSharedRef SAutomationWindow::OnGenerateWidgetForTest( TSharedPtr InItem, const TSharedRef& OwnerTable ) { bIsRequestingTests = false; return SNew(SAutomationTestItem, OwnerTable) .TestStatus(InItem) .ColumnWidth(ColumnWidth) .IsLocalSession(ActiveSession->IsStandalone()) .HighlightText(this, &SAutomationWindow::HandleAutomationHighlightText) .OnCheckedStateChanged(this, &SAutomationWindow::HandleItemCheckBoxCheckedStateChanged); } TSharedRef SAutomationWindow::OnGenerateWidgetForLog(TSharedPtr Message, const TSharedRef& OwnerTable) { check(Message.IsValid()); // \[((?:[\w]\:|\\)(?:(?:\\[A-Za-z_\-\s0-9\.]+)+)\.(?:cpp|h|ini))\((\d+)\)\]$ // https://regex101.com/r/vV4cV7/25 FRegexPattern FileAndLinePattern(TEXT("\\[((?:[\\w]\\:|\\\\)(?:(?:\\\\[A-Za-z_\\-\\s0-9\\.]+)+)\\.(?:cpp|h|ini))\\((\\d+)\\)\\]$")); FRegexMatcher FileAndLineRegexMatcher(FileAndLinePattern, Message->Text); TSharedRef SourceLink = SNullWidget::NullWidget; FString MessageString = Message->Text; if ( FileAndLineRegexMatcher.FindNext() ) { FString FileName = FileAndLineRegexMatcher.GetCaptureGroup(1); int32 LineNumber = FCString::Atoi(*FileAndLineRegexMatcher.GetCaptureGroup(2)); // Remove the hyperlink from the message, since we're splitting it into its own string. MessageString.LeftChopInline(FileAndLineRegexMatcher.GetCaptureGroup(0).Len(), EAllowShrinking::No); SourceLink = SNew(SHyperlink) .Style(FAutomationWindowStyle::Get(), "Common.GotoNativeCodeHyperlink") .TextStyle(FAutomationWindowStyle::Get(), Message->Style) .OnNavigate_Lambda([=] { FSlateApplication::Get().GotoLineInSource(FileName, LineNumber); }) .Text(FText::FromString(FileAndLineRegexMatcher.GetCaptureGroup(0))); } return SNew(STableRow >, OwnerTable) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(0) [ SNew(STextBlock) .TextStyle(FAutomationWindowStyle::Get(), Message->Style ) .Text(FText::FromString(MessageString)) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(0) [ SourceLink ] ]; } FText SAutomationWindow::OnGetNumEnabledTestsString() const { int32 NumPasses = AutomationController->GetNumPasses(); if( NumPasses > 1 ) { return FText::Format(LOCTEXT("NumEnabledTestsFmt", "{0} x{1}"), FText::AsNumber(AutomationController->GetEnabledTestsNum()), FText::AsNumber(NumPasses)); } else { return FText::AsNumber(AutomationController->GetEnabledTestsNum()); } } FText SAutomationWindow::OnGetNumDevicesInClusterString(const int32 ClusterIndex) const { return FText::AsNumber(AutomationController->GetNumDevicesInCluster(ClusterIndex)); } void SAutomationWindow::OnRefreshTestCallback() { //if the window hasn't been created yet if (!PlatformsHBox.IsValid()) { return; } //rebuild the platform header RebuildPlatformIcons(); //filter the tests that are shown AutomationController->SetFilter( AutomationFilters ); // Only expand the child nodes if we have a text filter bool ExpandChildren = !AutomationTextFilter->GetRawFilterText().IsEmpty(); TArray< TSharedPtr< IAutomationReport > >& TestReports = AutomationController->GetFilteredReports(); for( int32 Index = 0; Index < TestReports.Num(); Index++ ) { ExpandTreeView( TestReports[ Index ], ExpandChildren ); // Expand any items that where expanded before refresh tests was pressed. if( !ExpandChildren ) { ExpandItemsInList( TestTable, TestReports[Index], SavedExpandedItems ); } } SavedExpandedItems.Empty(); //rebuild the UI TestTable->RequestTreeRefresh(); //update the background style UpdateTestListBackgroundStyle(); } void SAutomationWindow::OnTestAvailableCallback( EAutomationControllerModuleState::Type InAutomationControllerState ) { AutomationControllerState = InAutomationControllerState; // Only list tests on opening the Window if the asset registry isn't in the middle of loading tests. if ( InAutomationControllerState == EAutomationControllerModuleState::Ready && AutomationController->GetFilteredReports().Num() == 0 && !bIsRequestingTests) { #if WITH_EDITOR FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); if ( !AssetRegistryModule.Get().IsLoadingAssets() ) { ListTests(); } #else ListTests(); #endif } } void SAutomationWindow::OnTestsCompleteCallback() { // Simulate selection again after testing finishes. if ( TestTable->GetNumItemsSelected() > 0 ) { OnTestSelectionChanged(TestTable->GetSelectedItems()[0], ESelectInfo::Direct); } } void SAutomationWindow::ExpandTreeView( TSharedPtr< IAutomationReport > InReport, const bool ShouldExpand ) { // Expand node if the report is flagged TestTable->SetItemExpansion( InReport, ShouldExpand && InReport->ExpandInUI() ); // Iterate through the child nodes to see if they should be expanded TArray > Reports = InReport->GetFilteredChildren(); for ( int32 ChildItem = 0; ChildItem < Reports.Num(); ChildItem++ ) { ExpandTreeView( Reports[ ChildItem ], ShouldExpand ); } } //TODO AUTOMATION - remove /** Updates list of all the tests */ void SAutomationWindow::ListTests( ) { // Save Expanded state prior to refresh TSet> ExpandedItems; TestTable->GetExpandedItems(ExpandedItems); SavedExpandedItems = SaveExpandedTestNames(ExpandedItems); AutomationController->RequestTests(); } //TODO AUTOMATION - remove /** Finds available workers */ void SAutomationWindow::FindWorkers() { ActiveSession = SessionManager->GetSelectedSession(); bool SessionIsValid = ActiveSession.IsValid() && (ActiveSession->GetSessionOwner() == FPlatformProcess::UserName(false)); if (SessionIsValid) { bIsRequestingTests = true; AutomationController->RequestAvailableWorkers(ActiveSession->GetSessionId()); RebuildPlatformIcons(); } else { bIsRequestingTests = false; // Clear UI if the session is invalid ClearAutomationUI(); } MenuBar->SetEnabled( SessionIsValid ); } void SAutomationWindow::HandleSessionManagerInstanceChanged() { UpdateTestListBackgroundStyle(); } void SAutomationWindow::UpdateTestListBackgroundStyle() { TArray> OutInstances; if( ActiveSession.IsValid() ) { ActiveSession->GetInstances(OutInstances); } TestBackgroundType = EAutomationTestBackgroundStyle::Unknown; if( OutInstances.Num() > 0 ) { FString FirstInstanceType = OutInstances[0]->GetInstanceType(); if( FirstInstanceType.Contains(TEXT("Editor")) ) { TestBackgroundType = EAutomationTestBackgroundStyle::Editor; } else if( FirstInstanceType.Contains(TEXT("Game")) ) { TestBackgroundType = EAutomationTestBackgroundStyle::Game; } } } FReply SAutomationWindow::RunTests() { if( AutomationControllerState == EAutomationControllerModuleState::Running ) { AutomationController->StopTests(); } else { // Prompt to save current map when running a test. #if WITH_EDITOR if ( !GIsDemoMode ) { // If there are any unsaved changes to the current level, see if the user wants to save those first. const bool bPromptUserToSave = true; const bool bSaveMapPackages = true; const bool bSaveContentPackages = true; if ( FEditorFileUtils::SaveDirtyPackages(bPromptUserToSave, bSaveMapPackages, bSaveContentPackages) == false ) { // something went wrong or the user pressed cancel. Return to the editor so the user doesn't lose their changes return FReply::Handled(); } } #endif AutomationController->RunTests( ActiveSession->IsStandalone() ); } LogMessages.Empty(); LogListView->RequestListRefresh(); //Clear old results GraphicalResultBox->ClearResults(); return FReply::Handled(); } /** Filtering */ void SAutomationWindow::OnFilterTextChanged( const FText& InFilterText ) { AutomationTextFilter->SetRawFilterText( InFilterText ); AutomationSearchBox->SetError( AutomationTextFilter->GetFilterErrorText() ); //update the widget OnRefreshTestCallback(); } bool SAutomationWindow::IsDeveloperDirectoryIncluded() const { return AutomationController->IsDeveloperDirectoryIncluded(); } void SAutomationWindow::OnToggleDeveloperDirectoryIncluded() { //Change controller filter AutomationController->SetDeveloperDirectoryIncluded(!IsDeveloperDirectoryIncluded()); // need to call this to request update ListTests(); } bool SAutomationWindow::IsExcludedTestsFilterOn() const { return AutomationGeneralFilter->ShouldShowOnlyExcludedTests(); } void SAutomationWindow::OnToggleExcludedTestsFilter() { AutomationGeneralFilter->SetShowOnlyExcludedTests(!IsExcludedTestsFilterOn()); OnRefreshTestCallback(); } bool SAutomationWindow::IsSmokeTestFilterOn() const { return AutomationGeneralFilter->OnlyShowSmokeTests(); } void SAutomationWindow::OnToggleSmokeTestFilter() { AutomationGeneralFilter->SetOnlyShowSmokeTests( !IsSmokeTestFilterOn() ); OnRefreshTestCallback(); } bool SAutomationWindow::IsWarningFilterOn() const { return AutomationGeneralFilter->ShouldShowWarnings(); } void SAutomationWindow::OnToggleWarningFilter() { AutomationGeneralFilter->SetShowWarnings( !IsWarningFilterOn() ); OnRefreshTestCallback(); } bool SAutomationWindow::IsErrorFilterOn() const { return AutomationGeneralFilter->ShouldShowErrors(); } void SAutomationWindow::OnToggleErrorFilter() { AutomationGeneralFilter->SetShowErrors( !IsErrorFilterOn() ); OnRefreshTestCallback(); } void SAutomationWindow::OnChangeRepeatCount(int32 InNewValue) { AutomationController->SetNumPasses(InNewValue); } int32 SAutomationWindow::GetRepeatCount() const { return AutomationController->GetNumPasses(); } FString SAutomationWindow::GetSmallIconExtension() const { FString Brush; if (FMultiBoxSettings::UseSmallToolBarIcons.Get()) { Brush += TEXT( ".Small" ); } return Brush; } EVisibility SAutomationWindow::GetLargeToolBarVisibility() const { return FMultiBoxSettings::UseSmallToolBarIcons.Get() ? EVisibility::Collapsed : EVisibility::Visible; } const FSlateBrush* SAutomationWindow::GetRunAutomationIcon() const { FString Brush = TEXT( "AutomationWindow" ); if( AutomationControllerState == EAutomationControllerModuleState::Running ) { Brush += TEXT( ".StopTests" ); // Temporary brush type for stop tests } else { Brush += TEXT( ".RunTests" ); } Brush += GetSmallIconExtension(); return FAutomationWindowStyle::Get().GetBrush( *Brush ); } FText SAutomationWindow::GetRunAutomationLabel() const { if( AutomationControllerState == EAutomationControllerModuleState::Running ) { return LOCTEXT( "RunStopTestsLabel", "Stop Tests" ); } else { return LOCTEXT( "RunStartTestsLabel", "Start Tests" ); } } FText SAutomationWindow::HandleAutomationHighlightText( ) const { if ( AutomationSearchBox.IsValid() ) { return AutomationSearchBox->GetText(); } return FText(); } EVisibility SAutomationWindow::HandleSelectSessionOverlayVisibility( ) const { if (SessionManager->GetSelectedInstances().Num() > 0) { return EVisibility::Hidden; } return EVisibility::Visible; } void SAutomationWindow::HandleSessionManagerCanSelectSession( const TSharedPtr& Session, bool& CanSelect ) { // We are using a compilation condition here as FMessageDialog::Open will show dialog only if GIsEditor && !IsRunningCommandlet() && FCoreDelegates::ModalErrorMessage.IsBound() // else it always gives us EAppReturnType::No answer in EAppMsgType::YesNo case. // The result is the sessions cannot be chosen in not editor case (ie: UnrealFrontEnd) #if WITH_EDITOR if (ActiveSession.IsValid() && AutomationController->CheckTestResultsAvailable()) { EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("ChangeSessionDialog", "Are you sure you want to change sessions?\nAll automation results data will be lost")); CanSelect = Result == EAppReturnType::Yes ? true : false; } #endif //WITH_EDITOR } void SAutomationWindow::HandleSessionManagerSelectionChanged( const TSharedPtr& SelectedSession ) { if (!GIsAutomationTesting) { FindWorkers(); } } bool SAutomationWindow::IsAutomationControllerIdle() const { return AutomationControllerState != EAutomationControllerModuleState::Running; } bool SAutomationWindow::IsAutomationRunButtonEnabled() const { return AutomationControllerState != EAutomationControllerModuleState::Disabled; } void SAutomationWindow::CopyLog( ) { TArray > SelectedItems = LogListView->GetSelectedItems(); if (SelectedItems.Num() > 0) { FString SelectedText; for( int32 Index = 0; Index < SelectedItems.Num(); ++Index ) { SelectedText += SelectedItems[Index]->Text; SelectedText += LINE_TERMINATOR; } FPlatformApplicationMisc::ClipboardCopy( *SelectedText ); } } FReply SAutomationWindow::HandleCommandBarCopyLogClicked( ) { CopyLog(); return FReply::Handled(); } void SAutomationWindow::HandleLogListSelectionChanged( TSharedPtr InItem, ESelectInfo::Type SelectInfo ) { CommandBar->SetNumLogMessages(LogListView->GetNumItemsSelected()); } void SAutomationWindow::ChangeTheSelectionToThisRow(TSharedPtr< IAutomationReport > ThisRow) { TestTable->SetSelection(ThisRow, ESelectInfo::Direct); } bool SAutomationWindow::IsRowSelected(TSharedPtr< IAutomationReport > ThisRow) { TArray< TSharedPtr >SelectedReport = TestTable->GetSelectedItems(); bool ThisRowIsInTheSelectedSet = false; for (int i = 0; i >SelectedReport = TestTable->GetSelectedItems(); for (int i = 0; iSetEnabled(InChecked); } } } bool SAutomationWindow::IsAnySelectedRowEnabled() { TArray< TSharedPtr >SelectedReport = TestTable->GetSelectedItems(); //Do check or uncheck selected rows based on current settings bool bFoundCheckedRow = false; bool bFoundNotCheckedRow = false; bool bRowCheckedValue = true; //Check all the rows if there is a mixture of checked and unchecked then we set all checked, otherwise set to opposite of current values for (int i = 0; iIsEnabled()) { bFoundCheckedRow = true; } else { bFoundNotCheckedRow = true; } } //break when all rows checked or different values found if (bFoundCheckedRow && bFoundNotCheckedRow) { break; } } //if rows were all checked set to unchecked otherwise we can set to checked if (bFoundCheckedRow && !bFoundNotCheckedRow) { bRowCheckedValue = false; } return bRowCheckedValue; } /* SWidget implementation *****************************************************************************/ FReply SAutomationWindow::OnKeyUp( const FGeometry& InGeometry, const FKeyEvent& InKeyEvent ) { if (InKeyEvent.GetKey() == EKeys::SpaceBar) { SetAllSelectedTestsChecked(IsAnySelectedRowEnabled()); return FReply::Handled(); } return FReply::Unhandled(); } FReply SAutomationWindow::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent ) { if (InKeyEvent.IsControlDown()) { if (InKeyEvent.GetKey() == EKeys::C) { CopyLog(); return FReply::Handled(); } } return FReply::Unhandled(); } /* SAutomationWindow callbacks *****************************************************************************/ void SAutomationWindow::HandleItemCheckBoxCheckedStateChanged( TSharedPtr< IAutomationReport > TestStatus ) { //If multiple rows selected then handle all the rows if (AreMultipleRowsSelected()) { //if current row is not in the selected list select that row if(IsRowSelected(TestStatus)) { //Just set them all to the opposite of the row just clicked. SetAllSelectedTestsChecked(!TestStatus->IsEnabled()); } else { //Change the selection to this row rather than keep other rows selected unrelated to the ticked/unticked item ChangeTheSelectionToThisRow(TestStatus); TestStatus->SetEnabled( !TestStatus->IsEnabled() ); } } else { TestStatus->SetEnabled( !TestStatus->IsEnabled() ); } } bool SAutomationWindow::HandleItemCheckBoxIsEnabled( ) const { return IsAutomationControllerIdle(); } bool SAutomationWindow::HandleMainContentIsEnabled() const { return (SessionManager->GetSelectedInstances().Num() > 0); } #if WITH_EDITOR // React to asset registry finishing updating. // We only want to do this if there are no tests already listed, otherwise this fires every time you save a map for example. void SAutomationWindow::OnAssetRegistryFilesLoaded() { ListTests(); } #endif #undef LOCTEXT_NAMESPACE