Files
UnrealEngine/Engine/Source/Developer/AutomationWindow/Private/SAutomationWindow.cpp
2025-05-18 13:04:45 +08:00

2644 lines
81 KiB
C++

// 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<FAutomationWindowCommands>
{
public:
FAutomationWindowCommands()
: TCommands<FAutomationWindowCommands>(
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<FUICommandInfo> RefreshTests;
TSharedPtr<FUICommandInfo> FindWorkers;
TSharedPtr<FUICommandInfo> ErrorFilter;
TSharedPtr<FUICommandInfo> WarningFilter;
TSharedPtr<FUICommandInfo> DeveloperDirectoryContent;
TSharedPtr<FUICommandInfo> ExcludedTestsFilter;
#if WITH_EDITOR
TSharedPtr<FUICommandInfo> RunLevelTest;
#endif
TSharedPtr<FUICommandInfo> CreateNewPreset;
TSharedPtr<FUICommandInfo> RemoveCurrentPreset;
TSharedPtr<FUICommandInfo> RenameCurrentPreset;
};
//////////////////////////////////////////////////////////////////////////
// SAutomationWindow
SAutomationWindow::SAutomationWindow()
: ColumnWidth(90.0f)
, bIsLabelVisibilityEnabled(false)
, bAutoExpandSingleItemSubgroups(true)
{
UAutomationControllerSettings* Settings = UAutomationControllerSettings::StaticClass()->GetDefaultObject<UAutomationControllerSettings>();
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<FAssetRegistryModule>(TEXT("AssetRegistry")).TryGet();
if (AssetRegistry)
{
AssetRegistry->OnFileLoadProgressUpdated().RemoveAll(this);
}
}
#endif
TSharedPtr<IAutomationReport> 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<ISessionManager>& InSessionManager )
{
FAutomationWindowCommands::Register();
CreateCommands();
#if WITH_EDITOR
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(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 <TSharedPtr <IAutomationReport> >& 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<UAutomationControllerSettings>();
TArray<FAutomatedTestGroup> TestGroups = Settings->Groups;
GroupComboList.Empty();
GroupFiltersMap.Empty();
const FString AllGroups = TEXT("All Groups");
GroupComboList.Add(MakeShareable(new FString(AllGroups)));
GroupFiltersMap.Add(AllGroups, TArray<FAutomatedTestFilter>());
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<SNotificationList> 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<FString> >)
.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<FString> >)
.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<TSharedPtr<FAutomationOutputMessage> >)
.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<FUICommandList>& InCommandList, TSharedPtr<class SAutomationWindow> InAutomationWindow )
{
return InAutomationWindow->MakeAutomationWindowToolBar(InCommandList);
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
TSharedRef< SWidget > SAutomationWindow::MakeAutomationWindowToolBar( const TSharedRef<FUICommandList>& InCommandList )
{
struct Local
{
static void FillToolbar(FToolBarBuilder& ToolbarBuilder, TSharedRef<SWidget> RunTests, TSharedRef<SWidget> DeviceGroups, TSharedRef<SWidget> PresetBox, TWeakPtr<class SAutomationWindow> 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<FText>(),
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<SWidget> 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<SWidget> 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<SWidget> 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<SAutomationWindow> 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<FUICommandList>& InCommandList, TSharedPtr<class SAutomationWindow> InAutomationWindow)
{
return InAutomationWindow->MakeAutomationFilterToolBar(InCommandList);
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
TSharedRef< SWidget > SAutomationWindow::MakeAutomationFilterToolBar(const TSharedRef<FUICommandList>& 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<SAutomationWindow> 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<FString> 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<FString> 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<FString> 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<TSharedPtr< IAutomationReport > > 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<FString> 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<SNotificationItem> 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<SWidget> SAutomationWindow::GenerateRequestedFilterComboItem(TSharedPtr<FString> InItem)
{
return SNew(STextBlock)
.Text(FText::FromString(*InItem));
}
TSharedRef<SWidget> SAutomationWindow::GenerateGroupComboItem(TSharedPtr<FString> InItem)
{
return SNew(STextBlock)
.Text(FText::FromString(*InItem));
}
TSharedRef< SWidget > SAutomationWindow::GenerateGroupOptionsMenuContent( TWeakPtr<class SAutomationWindow> InAutomationWindow )
{
TSharedPtr<SAutomationWindow> 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<SWidget> FlagWidgets[NumFlags];
for( int32 i=0; i<NumFlags; i++ )
{
FlagWidgets[i] =
SNew(SCheckBox)
.IsChecked(this, &SAutomationWindow::IsDeviceGroupCheckBoxIsChecked, i)
.OnCheckStateChanged(this, &SAutomationWindow::HandleDeviceGroupCheckStateChanged, i)
.Padding(FMargin(4.0f, 0.0f))
.ToolTipText(EAutomationDeviceGroupTypes::ToDescription((EAutomationDeviceGroupTypes::Type)i))
.IsEnabled( this, &SAutomationWindow::IsAutomationControllerIdle )
.Content()
[
SNew(STextBlock)
.Text(EAutomationDeviceGroupTypes::ToName((EAutomationDeviceGroupTypes::Type)i))
];
}
MenuBuilder.BeginSection("AutomationWindowGroupDevices", LOCTEXT("GroupTypeOptions", "Group Types"));
{
for( int32 i=0; i<NumFlags;i++ )
{
MenuBuilder.AddWidget(FlagWidgets[i].ToSharedRef(),FText::GetEmpty());
}
}
return MenuBuilder.MakeWidget();
}
TSharedRef< SWidget > SAutomationWindow::GeneratePresetsMenuContent( TWeakPtr<class SAutomationWindow> InAutomationWindow )
{
TSharedPtr<SAutomationWindow> 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<AutomationPresetPtr>& Presets = TestPresetManager->GetAllPresets();
TSharedPtr<TArray<TSharedPtr<SCheckBox>>> CheckBoxesSP = MakeShared<TArray<TSharedPtr<SCheckBox>>>();
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<SCheckBox> 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<TArray<TSharedPtr<SCheckBox>>> 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<class SAutomationWindow> InAutomationWindow )
{
TSharedPtr<SAutomationWindow> 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<SWidget> 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<int32>)
.MinValue(1)
.MaxValue(1000)
.MinSliderValue(1)
.MaxSliderValue(1000)
.Value(this,&SAutomationWindow::GetRepeatCount)
.OnValueChanged(this,&SAutomationWindow::OnChangeRepeatCount)
.IsEnabled( this, &SAutomationWindow::IsAutomationControllerIdle )
]
];
TSharedRef<SWidget> 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<SWidget> 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<SWidget> 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<FString> SAutomationWindow::SaveExpandedTestNames(TSet<TSharedPtr<IAutomationReport>> ExpandedItems)
{
TArray<FString> ExpandedItemsNames;
for ( TSharedPtr<IAutomationReport> 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<STreeView<TSharedPtr<IAutomationReport>>> InTestTable, TSharedPtr<IAutomationReport> InReport, TArray<FString> ItemsToExpand)
{
InTestTable->SetItemExpansion(InReport, ItemsToExpand.Contains(InReport->GetDisplayNameWithDecoration()));
TArray<TSharedPtr<IAutomationReport>> 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<SWidget> SAutomationWindow::HandleAutomationListContextMenuOpening()
{
TArray< TSharedPtr<IAutomationReport> >SelectedReport = TestTable->GetSelectedItems();
TArray<FString> TestNames;
TArray<FString> AssetNames;
for (TSharedPtr<IAutomationReport> 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<IAutomationReport> 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<IAutomationReport>();
}
void FindReportByGameRelativeAssetPath(const TSharedPtr<IAutomationReport>& RootReport, const FString& AssetRelativePath, TArray<TSharedPtr<IAutomationReport>>& 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<TSharedPtr<IAutomationReport>>& 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<TSharedPtr<IAutomationReport>> 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<IAutomationReport> 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<IAutomationReport> InRoot, TSharedPtr<IAutomationReport> 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<IAutomationReport> InItem, bool bExpanded)
{
ExpandSingleItemSubgroups(InItem, bExpanded);
}
void SAutomationWindow::ExpandSingleItemSubgroups(TSharedPtr<IAutomationReport> InItem, bool bExpanded)
{
check(InItem.IsValid());
if (bAutoExpandSingleItemSubgroups && bExpanded)
{
TArray<TSharedPtr<IAutomationReport>>& FilteredChildren = InItem->GetFilteredChildren();
if (FilteredChildren.Num() == 1)
{
TSharedPtr<IAutomationReport> SingleChild = FilteredChildren.Top();
check(SingleChild.IsValid());
if (!SingleChild->IsParent())
{
return;
}
TestTable->SetItemExpansion(InItem, bExpanded);
TArray<TSharedPtr<IAutomationReport>>& SingleChildFilteredChildren = SingleChild->GetFilteredChildren();
static auto IsChildPredicate = [](const TSharedPtr<IAutomationReport>& 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<IAutomationReport> InItem, TArray<TSharedPtr<IAutomationReport> >& OutItems)
{
OutItems = InItem->GetFilteredChildren();
}
void SAutomationWindow::OnTestExpansionRecursive(TSharedPtr<IAutomationReport> InAutomationReport, bool bInIsItemExpanded)
{
if ( InAutomationReport.IsValid() )
{
TArray<TSharedPtr<IAutomationReport> >& FilteredChildren = InAutomationReport->GetFilteredChildren();
TestTable->SetItemExpansion(InAutomationReport, bInIsItemExpanded);
for ( TSharedPtr<IAutomationReport>& Child : FilteredChildren )
{
OnTestExpansionRecursive(Child, bInIsItemExpanded);
}
}
}
void SAutomationWindow::OnTestSelectionChanged(TSharedPtr<IAutomationReport> Selection, ESelectInfo::Type /*SelectInfo*/)
{
TSharedPtr<IAutomationReport> 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<IAutomationReport> 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<ITableRow> SAutomationWindow::OnGenerateWidgetForTest( TSharedPtr<IAutomationReport> InItem, const TSharedRef<STableViewBase>& OwnerTable )
{
bIsRequestingTests = false;
return SNew(SAutomationTestItem, OwnerTable)
.TestStatus(InItem)
.ColumnWidth(ColumnWidth)
.IsLocalSession(ActiveSession->IsStandalone())
.HighlightText(this, &SAutomationWindow::HandleAutomationHighlightText)
.OnCheckedStateChanged(this, &SAutomationWindow::HandleItemCheckBoxCheckedStateChanged);
}
TSharedRef<ITableRow> SAutomationWindow::OnGenerateWidgetForLog(TSharedPtr<FAutomationOutputMessage> Message, const TSharedRef<STableViewBase>& 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<SWidget> 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<TSharedPtr<FAutomationOutputMessage> >, 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<FAssetRegistryModule>(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<TSharedPtr< IAutomationReport > > 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<TSharedPtr<IAutomationReport>> 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<TSharedPtr<ISessionInstanceInfo>> 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<ISessionInfo>& 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<ISessionInfo>& SelectedSession )
{
if (!GIsAutomationTesting)
{
FindWorkers();
}
}
bool SAutomationWindow::IsAutomationControllerIdle() const
{
return AutomationControllerState != EAutomationControllerModuleState::Running;
}
bool SAutomationWindow::IsAutomationRunButtonEnabled() const
{
return AutomationControllerState != EAutomationControllerModuleState::Disabled;
}
void SAutomationWindow::CopyLog( )
{
TArray<TSharedPtr<FAutomationOutputMessage> > 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<FAutomationOutputMessage> 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<IAutomationReport> >SelectedReport = TestTable->GetSelectedItems();
bool ThisRowIsInTheSelectedSet = false;
for (int i = 0; i<SelectedReport.Num();++i)
{
if (SelectedReport[i] == ThisRow)
{
ThisRowIsInTheSelectedSet = true;
}
}
return ThisRowIsInTheSelectedSet;
}
void SAutomationWindow::SetAllSelectedTestsChecked( bool InChecked )
{
TArray< TSharedPtr<IAutomationReport> >SelectedReport = TestTable->GetSelectedItems();
for (int i = 0; i<SelectedReport.Num();++i)
{
if (SelectedReport[i].IsValid())
{
SelectedReport[i]->SetEnabled(InChecked);
}
}
}
bool SAutomationWindow::IsAnySelectedRowEnabled()
{
TArray< TSharedPtr<IAutomationReport> >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; i<SelectedReport.Num();++i)
{
if (SelectedReport[i].IsValid())
{
if (SelectedReport[i]->IsEnabled())
{
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