// Copyright Epic Games, Inc. All Rights Reserved. #include "CoreMinimal.h" #include "HAL/FileManager.h" #include "Misc/Paths.h" #include "Misc/AutomationTest.h" #include "Modules/ModuleManager.h" #include "UObject/Class.h" #include "UObject/Package.h" #include "Misc/PackageName.h" #include "Framework/Commands/InputChord.h" #include "Templates/SubclassOf.h" #include "EdGraph/EdGraphPin.h" #include "Components/ActorComponent.h" #include "GameFramework/Actor.h" #include "Engine/Blueprint.h" #include "Components/StaticMeshComponent.h" #include "EdGraph/EdGraph.h" #include "Factories/BlueprintFactory.h" #include "Particles/ParticleSystem.h" #include "Engine/StaticMesh.h" #include "Kismet/KismetSystemLibrary.h" #include "Engine/BlueprintGeneratedClass.h" #include "AssetRegistry/AssetData.h" #include "Editor.h" #include "UObject/SavePackage.h" #include "Kismet2/KismetEditorUtilities.h" #include "EdGraphSchema_K2.h" #include "EdGraphSchema_K2_Actions.h" #include "K2Node_Event.h" #include "K2Node_CallFunction.h" #include "K2Node_AddComponent.h" #include "K2Node_CustomEvent.h" #include "K2Node_FunctionEntry.h" #include "K2Node_VariableGet.h" #include "K2Node_VariableSet.h" #include "Engine/SCS_Node.h" #include "BlueprintEditorModes.h" #include "Kismet2/BlueprintEditorUtils.h" #include "ComponentAssetBroker.h" #include "AssetRegistry/ARFilter.h" #include "ScopedTransaction.h" #include "ObjectTools.h" // Automation #include "AssetRegistry/AssetRegistryModule.h" #include "Tests/AutomationTestSettings.h" #include "Tests/AutomationEditorCommon.h" #include "Tests/AutomationEditorPromotionCommon.h" #include "Subsystems/AssetEditorSubsystem.h" #if WITH_DEV_AUTOMATION_TESTS #define LOCTEXT_NAMESPACE "BlueprintEditorPromotionTests" DEFINE_LOG_CATEGORY_STATIC(LogBlueprintEditorPromotionTests, Log, All); IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBlueprintEditorPromotionTest, "System.Promotion.Editor.Blueprint Editor", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter); /** * Helper functions used by the blueprint editor promotion automation test */ namespace BlueprintEditorPromotionUtils { /** * Constants */ static const FString BlueprintNameString = TEXT("BlueprintEditorPromotionBlueprint"); static const FName BlueprintStringVariableName(TEXT("MyStringVariable")); /** * Gets the full path to the folder on disk */ static FString GetFullPath() { return FPackageName::FilenameToLongPackageName(FPaths::ProjectContentDir() + TEXT("BlueprintEditorPromotionTest")); } /** * Helper class to track once a certain time has passed */ class FDelayHelper { public: /** * Constructor */ FDelayHelper() : bIsRunning(false), StartTime(0.f), Duration(0.f) { } /** * Returns if the delay is still running */ bool IsRunning() { return bIsRunning; } /** * Sets the helper state to not running */ void Reset() { bIsRunning = false; } /** * Starts the delay timer * * @param InDuration - How long to delay for in seconds */ void Start(double InDuration) { bIsRunning = true; StartTime = FPlatformTime::Seconds(); Duration = InDuration; } /** * Returns true if the desired amount of time has passed */ bool IsComplete() { if (IsRunning()) { const double CurrentTime = FPlatformTime::Seconds(); return CurrentTime - StartTime >= Duration; } else { return false; } } private: /** If true, this delay timer is active */ bool bIsRunning; /** The time the delay started */ double StartTime; /** How long the timer is for */ double Duration; }; /** * Sends the AssetEditor->SaveAsset UI command */ static void SendBlueprintResetViewCommand() { const FString Context = TEXT("BlueprintEditor"); const FString Command = TEXT("ResetCamera"); FInputChord CurrentSaveChord = FEditorPromotionTestUtilities::GetOrSetUICommand(Context, Command); const FName FocusWidgetType(TEXT("SSCSEditorViewport")); FEditorPromotionTestUtilities::SendCommandToCurrentEditor(CurrentSaveChord, FocusWidgetType); } /** * Compiles the blueprint * * @param InBlueprint - The blueprint to compile */ static void CompileBlueprint(UBlueprint* InBlueprint) { FBlueprintEditorUtils::RefreshAllNodes(InBlueprint); FKismetEditorUtilities::CompileBlueprint(InBlueprint, EBlueprintCompileOptions::SkipGarbageCollection); if (InBlueprint->Status == EBlueprintStatus::BS_UpToDate) { UE_LOG(LogBlueprintEditorPromotionTests, Display, TEXT("Blueprint compiled successfully (%s)"), *InBlueprint->GetName()); } else if (InBlueprint->Status == EBlueprintStatus::BS_UpToDateWithWarnings) { UE_LOG(LogBlueprintEditorPromotionTests, Display, TEXT("Blueprint compiled successfully with warnings(%s)"), *InBlueprint->GetName()); } else if (InBlueprint->Status == EBlueprintStatus::BS_Error) { UE_LOG(LogBlueprintEditorPromotionTests, Display, TEXT("Blueprint failed to compile (%s)"), *InBlueprint->GetName()); } else { UE_LOG(LogBlueprintEditorPromotionTests, Error, TEXT("Blueprint is in an unexpected state after compiling (%s)"), *InBlueprint->GetName()); } } /** * Creates a blueprint component based off the supplied asset * * @param InBlueprint - The blueprint to modify * @param InAsset - The asset to use for the component */ static USCS_Node* CreateBlueprintComponent(UBlueprint* InBlueprint, UObject* InAsset) { IAssetEditorInstance* OpenEditor = GEditor->GetEditorSubsystem()->FindEditorForAsset(InBlueprint, true); FBlueprintEditor* CurrentBlueprintEditor = (FBlueprintEditor*)OpenEditor; TSubclassOf ComponentClass = FComponentAssetBrokerage::GetPrimaryComponentForAsset(InAsset->GetClass()); USCS_Node* NewNode = InBlueprint->SimpleConstructionScript->CreateNode(ComponentClass); // Assign the asset to the template FComponentAssetBrokerage::AssignAssetToComponent(NewNode->ComponentTemplate, InAsset); // Add node to the SCS TArray AllNodes = InBlueprint->SimpleConstructionScript->GetAllNodes(); USCS_Node* RootNode = AllNodes.Num() > 0 ? AllNodes[0] : NULL; if (!RootNode || (RootNode == InBlueprint->SimpleConstructionScript->GetDefaultSceneRootNode())) { //New Root InBlueprint->SimpleConstructionScript->AddNode(NewNode); } else { //Add as a child RootNode->AddChildNode(NewNode); } // Recompile skeleton because of the new component we added FKismetEditorUtilities::GenerateBlueprintSkeleton(InBlueprint, true); CurrentBlueprintEditor->UpdateSubobjectPreview(true); return NewNode; } /** * Sets a new component as the root * * @param InBlueprint - The blueprint to modify * @param NewRoot - The new root */ static void SetComponentAsRoot(UBlueprint* InBlueprint, USCS_Node* NewRoot) { // @FIXME: Current usages doesn't guarantee NewRoot is valid!!! Check first! //Get all the construction script nodes TArray AllNodes = InBlueprint->SimpleConstructionScript->GetAllNodes(); USCS_Node* OldRootNode = AllNodes[0]; USCS_Node* OldParent = NULL; //Find old parent for (int32 NodeIndex = 0; NodeIndex < AllNodes.Num(); ++NodeIndex) { if (AllNodes[NodeIndex]->ChildNodes.Contains(NewRoot)) { OldParent = AllNodes[NodeIndex]; break; } } check(OldParent); //Remove the new root from its old parent and OldParent->RemoveChildNode(NewRoot); NewRoot->Modify(); NewRoot->AttachToName = NAME_None; //Remove the old root, add the new root, and attach the old root as a child InBlueprint->SimpleConstructionScript->RemoveNode(OldRootNode); InBlueprint->SimpleConstructionScript->AddNode(NewRoot); NewRoot->AddChildNode(OldRootNode); } /** * Removes a blueprint component from the simple construction script * * @param InBlueprint - The blueprint to modify * @param InNode - The node of the component to remove */ static void RemoveBlueprintComponent(UBlueprint* InBlueprint, USCS_Node* InNode) { if (InNode != NULL) { // Remove node from SCS tree InNode->GetSCS()->RemoveNodeAndPromoteChildren(InNode); // Clear the delegate InNode->SetOnNameChanged(FSCSNodeNameChanged()); } } /** * Creates a new graph node from a given template * * @param NodeTemplate - The template to use for the node * @param InGraph - The graph to create the new node in * @param GraphLocation - The location to place the node * @param ConnectPin - The pin to connect the node to */ static UEdGraphNode* CreateNewGraphNodeFromTemplate(UK2Node* NodeTemplate, UEdGraph* InGraph, const FVector2f& GraphLocation, UEdGraphPin* ConnectPin = NULL) { TSharedPtr Action = TSharedPtr(new FEdGraphSchemaAction_K2NewNode(FText::GetEmpty(), FText::GetEmpty(), FText::GetEmpty(), 0)); Action->NodeTemplate = NodeTemplate; return Action->PerformAction(InGraph, ConnectPin, GraphLocation, false); } /** * Creates an AddComponent action to the blueprint graph * * @param InBlueprint - The blueprint to modify * @param InGraph - The blueprint graph to use * @param InAsset - The asset to use */ static UEdGraphNode* CreateAddComponentActionNode(UBlueprint* InBlueprint, UEdGraph* InGraph, UObject* InAsset) { UEdGraph* TempOuter = NewObject((UObject*)InBlueprint); TempOuter->SetFlags(RF_Transient); const FScopedTransaction PropertyChanged(LOCTEXT("AddedGraphNode", "Added a graph node")); InGraph->Modify(); // Make an add component node UK2Node_CallFunction* CallFuncNode = NewObject(TempOuter); UFunction* AddComponentFn = FindFieldChecked(AActor::StaticClass(), UK2Node_AddComponent::GetAddComponentFunctionName()); CallFuncNode->FunctionReference.SetFromField(AddComponentFn, FBlueprintEditorUtils::IsActorBased(InBlueprint)); UEdGraphNode* NewNode = CreateNewGraphNodeFromTemplate(CallFuncNode, InGraph, FVector2f(200.0f, 0.0f)); TSubclassOf ComponentClass = InAsset ? FComponentAssetBrokerage::GetPrimaryComponentForAsset(InAsset->GetClass()) : NULL; if ((NewNode != NULL) && (InBlueprint != NULL)) { UK2Node_AddComponent* AddCompNode = CastChecked(NewNode); ensure(NULL != Cast(InBlueprint->GeneratedClass)); // Then create a new template object, and add to array in UActorComponent* NewTemplate = NewObject(InBlueprint->GeneratedClass, ComponentClass, NAME_None, RF_ArchetypeObject | RF_Public); InBlueprint->ComponentTemplates.Add(NewTemplate); // Set the name of the template as the default for the TemplateName param UEdGraphPin* TemplateNamePin = AddCompNode->GetTemplateNamePinChecked(); if (TemplateNamePin) { TemplateNamePin->DefaultValue = NewTemplate->GetName(); } // Set the return type to be the type of the template UEdGraphPin* ReturnPin = AddCompNode->GetReturnValuePin(); if (ReturnPin) { ReturnPin->PinType.PinSubCategoryObject = *ComponentClass; } // Set the asset if (InAsset != NULL) { FComponentAssetBrokerage::AssignAssetToComponent(NewTemplate, InAsset); } AddCompNode->ReconstructNode(); } FBlueprintEditorUtils::MarkBlueprintAsModified(InBlueprint); return NewNode; } /** * Creates a SetStaticMesh action in the blueprint graph * * @param InBlueprint - The blueprint to modify * @param InGraph - The blueprint graph to use */ static UEdGraphNode* AddSetStaticMeshNode(UBlueprint* InBlueprint, UEdGraph* InGraph) { UEdGraph* TempOuter = NewObject((UObject*)InBlueprint); TempOuter->SetFlags(RF_Transient); // Make a call function template UK2Node_CallFunction* CallFuncNode = NewObject(TempOuter); static FName PrintStringFunctionName(TEXT("SetStaticMesh")); UFunction* DelayFn = FindFieldChecked(UStaticMeshComponent::StaticClass(), PrintStringFunctionName); CallFuncNode->FunctionReference.SetFromField(DelayFn, false); return CreateNewGraphNodeFromTemplate(CallFuncNode, InGraph, FVector2f(850.0f, 0.0f)); } /** * Connects two nodes using the supplied pin names * * @param NodeA - The first node to connect * @param PinAName - The name of the pin on the first node * @param NodeB - The second node to connect * @param PinBName - The name of the pin on the second node */ static void ConnectGraphNodes(UEdGraphNode* NodeA, const FName& PinAName, UEdGraphNode* NodeB, const FName& PinBName) { const FScopedTransaction PropertyChanged(LOCTEXT("ConnectedNode", "Connected graph nodes")); NodeA->GetGraph()->Modify(); UEdGraphPin* PinA = NodeA->FindPin(PinAName); UEdGraphPin* PinB = NodeB->FindPin(PinBName); if (PinA && PinB) { PinA->MakeLinkTo(PinB); } else { UE_LOG(LogBlueprintEditorPromotionTests, Error, TEXT("Could not connect pins %s and %s "), *PinAName.ToString(), *PinBName.ToString()); } } /** * Promotes a pin to a variable * * @param InBlueprint - The blueprint to modify * @param Node - The node that owns the pin * @param PinName - The name of the pin to promote */ static void PromotePinToVariable(UBlueprint* InBlueprint, UEdGraphNode* Node, const FName& PinName) { IAssetEditorInstance* OpenEditor = GEditor->GetEditorSubsystem()->FindEditorForAsset(InBlueprint, true); FBlueprintEditor* CurrentBlueprintEditor = (FBlueprintEditor*)OpenEditor; UEdGraphPin* PinToPromote = Node->FindPin(PinName); CurrentBlueprintEditor->DoPromoteToVariable2f(InBlueprint, PinToPromote, true); } /** * Creates a ReceiveBeginPlay event node * * @param InBlueprint - The blueprint to modify * @param InGraph - The graph to use for the new node */ static UEdGraphNode* CreatePostBeginPlayEvent(UBlueprint* InBlueprint, UEdGraph* InGraph) { UEdGraph* TempOuter = NewObject((UObject*)InBlueprint); TempOuter->SetFlags(RF_Transient); // Make an add component node UK2Node_Event* NewEventNode = NewObject(TempOuter); NewEventNode->EventReference.SetExternalMember(FName(TEXT("ReceiveBeginPlay")), AActor::StaticClass()); NewEventNode->bOverrideFunction = true; //Check for existing events UK2Node_Event* ExistingEvent = FBlueprintEditorUtils::FindOverrideForFunction(InBlueprint, NewEventNode->EventReference.GetMemberParentClass(NewEventNode->GetBlueprintClassFromNode()), NewEventNode->EventReference.GetMemberName()); if (!ExistingEvent) { return CreateNewGraphNodeFromTemplate(NewEventNode, InGraph, FVector2f(200.0f, 0.0f)); } return ExistingEvent; } /** * Creates a custom event node * * @param InBlueprint - The blueprint to modify * @param InGraph - The graph to use for the new node * @param EventName - The name of the event */ static UEdGraphNode* CreateCustomEvent(UBlueprint* InBlueprint, UEdGraph* InGraph, const FString& EventName) { UEdGraph* TempOuter = NewObject((UObject*)InBlueprint); TempOuter->SetFlags(RF_Transient); // Make an add component node UK2Node_CustomEvent* NewEventNode = NewObject(TempOuter); NewEventNode->CustomFunctionName = "EventName"; return CreateNewGraphNodeFromTemplate(NewEventNode, InGraph, FVector2f(1200.0f, 0.0f)); } /** * Creates a node template for a UKismetSystemLibrary function * * @param NodeOuter - The outer to use for the template * @param InGraph - The function to use for the node */ static UK2Node* CreateKismetFunctionTemplate(UObject* NodeOuter, const FName& FunctionName) { // Make a call function template UK2Node_CallFunction* CallFuncNode = NewObject(NodeOuter); UFunction* Function = FindFieldChecked(UKismetSystemLibrary::StaticClass(), FunctionName); CallFuncNode->FunctionReference.SetFromField(Function, false); return CallFuncNode; } /** * Creates a delay node * * @param InBlueprint - The blueprint to modify * @param InGraph - The graph to use for the new node * @param ConnectPin - The pin to connect the new node to */ static UEdGraphNode* AddDelayNode(UBlueprint* InBlueprint, UEdGraph* InGraph, UEdGraphPin* ConnectPin = NULL) { UEdGraph* TempOuter = NewObject((UObject*)InBlueprint); TempOuter->SetFlags(RF_Transient); const FScopedTransaction PropertyChanged(LOCTEXT("AddedGraphNode", "Added a graph node")); InGraph->Modify(); // Make a call function template static FName DelayFunctionName(TEXT("Delay")); UK2Node* CallFuncNode = CreateKismetFunctionTemplate(TempOuter, DelayFunctionName); //Create the node return CreateNewGraphNodeFromTemplate(CallFuncNode, InGraph, FVector2f(400.0f, 0.0f), ConnectPin); } /** * Creates a PrintString node * * @param InBlueprint - The blueprint to modify * @param InGraph - The graph to use for the new node * @param ConnectPin - The pin to connect the new node to */ static UEdGraphNode* AddPrintStringNode(UBlueprint* InBlueprint, UEdGraph* InGraph, UEdGraphPin* ConnectPin = NULL) { UEdGraph* TempOuter = NewObject((UObject*)InBlueprint); TempOuter->SetFlags(RF_Transient); // Make a call function template static FName PrintStringFunctionName(TEXT("PrintString")); UK2Node* CallFuncNode = CreateKismetFunctionTemplate(TempOuter, PrintStringFunctionName); return CreateNewGraphNodeFromTemplate(CallFuncNode, InGraph, FVector2f(680.0f, 0.0f), ConnectPin); } /** * Creates a call function node * * @param InBlueprint - The blueprint to modify * @param InGraph - The graph to use for the new node * @param FunctionName - The name of the function to call * @param ConnectPin - The pin to connect the new node to */ static UEdGraphNode* AddCallFunctionGraphNode(UBlueprint* InBlueprint, UEdGraph* InGraph, const FName& FunctionName, UEdGraphPin* ConnectPin = NULL) { UEdGraph* TempOuter = NewObject((UObject*)InBlueprint); TempOuter->SetFlags(RF_Transient); // Make a call function template UK2Node_CallFunction* CallFuncNode = NewObject(TempOuter); CallFuncNode->FunctionReference.SetSelfMember(FunctionName); return CreateNewGraphNodeFromTemplate(CallFuncNode, InGraph, FVector2f(1200.0f, 0.0f), ConnectPin); } /** * Creates Get or Set node * * @param InBlueprint - The blueprint to modify * @param InGraph - The graph to use for the new node * @param VarName - The name of the variable to use * @param bGet - If true, create a Get node. If false, create a Set node. * @param XOffset - How far to offset the node in the graph */ static UEdGraphNode* AddGetSetNode(UBlueprint* InBlueprint, UEdGraph* InGraph, const FString& VarName, bool bGet, float XOffset = 0.f) { const FScopedTransaction PropertyChanged(LOCTEXT("AddedGraphNode", "Added a graph node")); InGraph->Modify(); FEdGraphSchemaAction_K2NewNode NodeInfo; // Create get or set node, depending on whether we clicked on an input or output pin UK2Node_Variable* TemplateNode = NULL; if (bGet) { TemplateNode = NewObject(); } else { TemplateNode = NewObject(); } TemplateNode->VariableReference.SetSelfMember(FName(*VarName)); NodeInfo.NodeTemplate = TemplateNode; return NodeInfo.PerformAction(InGraph, NULL, FVector2f(XOffset, 130.0f), true); } /** * Sets the default value for a pin * * @param Node - The node that owns the pin to set * @param PinName - The name of the pin * @param PinValue - The new default value */ static void SetPinDefaultValue(UEdGraphNode* Node, const FName PinName, const FString& PinValue) { UEdGraphPin* Pin = Node->FindPin(PinName); Pin->DefaultValue = PinValue; } /** * Sets the default object for a pin * * @param Node - The node that owns the pin to set * @param PinName - The name of the pin * @param PinObject - The new default object */ static void SetPinDefaultObject(UEdGraphNode* Node, const FName PinName, UObject* PinObject) { UEdGraphPin* Pin = Node->FindPin(PinName); Pin->DefaultObject = PinObject; } /** * Adds a string member variable to a blueprint * * @param InBlueprint - The blueprint to modify * @param VariableName - The name of the new string variable */ static void AddStringMemberValue(UBlueprint* InBlueprint, const FName& VariableName) { FEdGraphPinType StringPinType(UEdGraphSchema_K2::PC_String, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType()); FBlueprintEditorUtils::AddMemberVariable(InBlueprint, VariableName, StringPinType); } /** * Creates a new function graph * * @param InBlueprint - The blueprint to modify * @param FunctionName - The function name to use for the new graph */ static UEdGraph* CreateNewFunctionGraph(UBlueprint* InBlueprint, const FName& FunctionName) { UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(InBlueprint, FunctionName, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass()); FBlueprintEditorUtils::AddFunctionGraph(InBlueprint, NewGraph, /*bIsUserCreated=*/ true, NULL); return NewGraph; } } /** * Container for items related to the blueprint editor test */ namespace BlueprintEditorPromotionTestHelper { /** * The main build promotion test class */ struct FBlueprintEditorPromotionTestHelper { /** Pointer to running automation test instance */ FBlueprintEditorPromotionTest* Test; /** Function definition for the test stage functions */ typedef bool(BlueprintEditorPromotionTestHelper::FBlueprintEditorPromotionTestHelper::*TestStageFn)(); /** List of test stage functions */ TArray TestStages; TArray StageNames; /** The index of the test stage we are on */ int32 CurrentStage; /** Pointer to the active editor world */ UWorld* CurrentWorld; /** Particle System loaded from Automation Settings for Blueprint Pass */ UParticleSystem* LoadedParticleSystem; /** Meshes to use for the blueprint */ UStaticMesh* FirstBlueprintMesh; UStaticMesh* SecondBlueprintMesh; /** Objects created by the Blueprint stages */ UBlueprint* BlueprintObject; UPackage* BlueprintPackage; UEdGraph* CustomGraph; USCS_Node* MeshNode; USCS_Node* OtherMeshNode; USCS_Node* PSNode; UEdGraphNode* AddMeshNode; UEdGraphNode* PostBeginPlayEventNode; UEdGraphNode* DelayNode; UEdGraphNode* SetNode; UEdGraphNode* GetNode; UEdGraphNode* PrintNode; UEdGraphNode* SetStaticMeshNode; UEdGraphNode* CustomEventNode; UEdGraphNode* AddParticleSystemNode; UEdGraphNode* CallFunctionNode; /** Delay helper */ BlueprintEditorPromotionUtils::FDelayHelper DelayHelper; /** List of skipped tests */ TArray SkippedTests; /** summary logs to display at the end */ TArray SummaryLines; /** Keeps track of errors generated by building the map */ int32 BuildStartErrorCount; int32 SectionSuccessCount; int32 SectionTestCount; int32 SectionErrorCount; int32 SectionWarningCount; int32 SectionLogCount; #define ADD_TEST_STAGE(FuncName,StageName) \ TestStages.Add(&BlueprintEditorPromotionTestHelper::FBlueprintEditorPromotionTestHelper::FuncName); \ StageNames.Add(StageName); /** * Constructor */ FBlueprintEditorPromotionTestHelper() : CurrentStage(0) { FMemory::Memzero(this, sizeof(FBlueprintEditorPromotionTestHelper)); ADD_TEST_STAGE(Cleanup, TEXT("Pre-start cleanup")); ADD_TEST_STAGE(Setup, TEXT("Setup")); ADD_TEST_STAGE(Blueprint_CreateNewBlueprint_Part1, TEXT("Create a new Blueprint")); ADD_TEST_STAGE(Blueprint_CreateNewBlueprint_Part2, TEXT("Create a new Blueprint")); ADD_TEST_STAGE(Blueprint_DataOnlyBlueprint_Part1, TEXT("Data-only Blueprint")); ADD_TEST_STAGE(Blueprint_DataOnlyBlueprint_Part2, TEXT("Data-only Blueprint")); ADD_TEST_STAGE(Blueprint_DataOnlyBlueprint_Part3, TEXT("Data-only Blueprint")); ADD_TEST_STAGE(Blueprint_ComponentsMode_Part1, TEXT("Components Mode")); ADD_TEST_STAGE(Blueprint_ComponentsMode_Part2, TEXT("Components Mode")); ADD_TEST_STAGE(Blueprint_ConstructionScript, TEXT("Construction Script")); ADD_TEST_STAGE(Blueprint_PromoteVariable_Part1, TEXT("Variable from Component Mode 1")); ADD_TEST_STAGE(Blueprint_PromoteVariable_Part2, TEXT("Variable from Component Mode 2")); //ADD_TEST_STAGE(Blueprint_PromoteVariable_Part3, TEXT("Variable from Component Mode 3")); ADD_TEST_STAGE(Blueprint_EventGraph, TEXT("Event Graph")); ADD_TEST_STAGE(Blueprint_CustomVariable, TEXT("Custom Variables")); ADD_TEST_STAGE(Blueprint_UsingVariables, TEXT("Using Variables")); ADD_TEST_STAGE(Blueprint_RenameCustomEvent, TEXT("Renaming Custom Event")); ADD_TEST_STAGE(Blueprint_NewFunctions, TEXT("New Function")); ADD_TEST_STAGE(Blueprint_CompleteBlueprint, TEXT("Completing the Blueprint")); ADD_TEST_STAGE(Cleanup, TEXT("Teardown")); } /** * Runs the current test stage */ bool Update() { Test->PushContext(StageNames[CurrentStage]); bool bStageComplete = (this->*TestStages[CurrentStage])(); Test->PopContext(); if (bStageComplete) { CurrentStage++; } return CurrentStage >= TestStages.Num(); } private: bool Cleanup() { //Make sure no editors are open GEditor->GetEditorSubsystem()->CloseAllAssetEditors(); //remove all assets in the build package // Load the asset registry module IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")).Get(); // Form a filter from the paths FARFilter Filter; Filter.bRecursivePaths = true; new (Filter.PackagePaths) FName(*FEditorPromotionTestUtilities::GetGamePath()); // Query for a list of assets in the selected paths TArray AssetList; AssetRegistry.GetAssets(Filter, AssetList); // Clear and try to delete all assets for (int32 AssetIdx = 0; AssetIdx < AssetList.Num(); ++AssetIdx) { Test->AddInfo(*FString::Printf(TEXT("Removing asset: %s"), *AssetList[AssetIdx].AssetName.ToString())); if (AssetList[AssetIdx].IsAssetLoaded()) { UObject* LoadedAsset = AssetList[AssetIdx].GetAsset(); AssetRegistry.AssetDeleted(LoadedAsset); bool bSuccessful = ObjectTools::DeleteSingleObject(LoadedAsset, false); //If we failed to delete this object manually clear any references and try again if (!bSuccessful) { //Clear references to the object so we can delete it FAutomationEditorCommonUtils::NullReferencesToObject(LoadedAsset); bSuccessful = ObjectTools::DeleteSingleObject(LoadedAsset, false); } } } Test->AddInfo(*FString::Printf(TEXT("Clearing Path: %s"), *FEditorPromotionTestUtilities::GetGamePath())); AssetRegistry.RemovePath(FEditorPromotionTestUtilities::GetGamePath()); //Remove the directory bool bEnsureExists = false; bool bDeleteEntireTree = true; FString PackageDirectory = FPaths::ProjectContentDir() / TEXT("BuildPromotionTest"); IFileManager::Get().DeleteDirectory(*PackageDirectory, bEnsureExists, bDeleteEntireTree); Test->AddInfo(*FString::Printf(TEXT("Deleting Folder: %s"), *PackageDirectory)); return true; } bool Setup() { //Make sure we have the required assets UAutomationTestSettings const* AutomationTestSettings = GetDefault(); check(AutomationTestSettings); FAssetData AssetData; const FString FirstMeshPath = AutomationTestSettings->BlueprintEditorPromotionTest.FirstMeshPath.FilePath; if (FirstMeshPath.Len() > 0) { AssetData = FAutomationEditorCommonUtils::GetAssetDataFromPackagePath(FirstMeshPath); FirstBlueprintMesh = Cast(AssetData.GetAsset()); } const FString SecondMeshPath = AutomationTestSettings->BlueprintEditorPromotionTest.SecondMeshPath.FilePath; if (SecondMeshPath.Len() > 0) { AssetData = FAutomationEditorCommonUtils::GetAssetDataFromPackagePath(SecondMeshPath); SecondBlueprintMesh = Cast(AssetData.GetAsset()); } const FString ParticleSystemPath = AutomationTestSettings->BlueprintEditorPromotionTest.DefaultParticleAsset.FilePath; if (ParticleSystemPath.Len() > 0) { AssetData = FAutomationEditorCommonUtils::GetAssetDataFromPackagePath(ParticleSystemPath); LoadedParticleSystem = Cast(AssetData.GetAsset()); } if (!(FirstBlueprintMesh && SecondBlueprintMesh && LoadedParticleSystem)) { SkippedTests.Add(TEXT("All Blueprint tests. (Missing a required mesh or particle system)")); if (FirstMeshPath.IsEmpty() || SecondMeshPath.IsEmpty()) { Test->AddInfo(TEXT("SKIPPING BLUEPRINT TESTS. FirstMeshPath or SecondMeshPath not configured in AutomationTestSettings.")); } else { Test->AddWarning(TEXT("SKIPPING BLUEPRINT TESTS. Invalid FirstMeshPath or SecondMeshPath in AutomationTestSettings, or particle system was not created.")); } } return true; } /** * Blueprint Test Stage: Create a new Blueprint (Part 1) * Creates a new actor based blueprint and opens the editor */ bool Blueprint_CreateNewBlueprint_Part1() { // Exit early if any of the required assets are missing if (!(FirstBlueprintMesh && SecondBlueprintMesh && LoadedParticleSystem)) { return true; } UBlueprintFactory* Factory = NewObject(); Factory->ParentClass = AActor::StaticClass(); const FString PackageName = FEditorPromotionTestUtilities::GetGamePath() + TEXT("/") + BlueprintEditorPromotionUtils::BlueprintNameString; BlueprintPackage = CreatePackage( *PackageName); EObjectFlags Flags = RF_Public | RF_Standalone; UObject* ExistingBlueprint = FindObject(BlueprintPackage, *BlueprintEditorPromotionUtils::BlueprintNameString); Test->TestNull(TEXT("Blueprint asset does not already exist (delete blueprint and restart editor)"), ExistingBlueprint); // Exit early since test will not be valid with pre-existing assets if (ExistingBlueprint) { return true; } BlueprintObject = Cast(Factory->FactoryCreateNew(UBlueprint::StaticClass(), BlueprintPackage, FName(*BlueprintEditorPromotionUtils::BlueprintNameString), Flags, NULL, GWarn)); Test->TestNotNull(TEXT("Created new Actor-based blueprint"), BlueprintObject); if (BlueprintObject) { // Update asset registry and mark package dirty FAssetRegistryModule::AssetCreated(BlueprintObject); BlueprintPackage->MarkPackageDirty(); Test->AddInfo(TEXT("Opening the blueprint editor for the first time")); GEditor->GetEditorSubsystem()->OpenEditorForAsset(BlueprintObject); } return true; } /** * Blueprint Test Stage: Create a new Blueprint (Part 2) * Checks that the blueprint editor opened in the correct mode */ bool Blueprint_CreateNewBlueprint_Part2() { if (BlueprintObject) { IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem()->FindEditorForAsset(BlueprintObject, true); IBlueprintEditor* BlueprintEditor = (IBlueprintEditor*)AssetEditor; Test->TestTrue(TEXT("Opened correct initial editor"), BlueprintEditor->GetCurrentMode() != FBlueprintEditorApplicationModes::BlueprintDefaultsMode); } return true; } /** * Blueprint Test Stage: Data-only Blueprint (Part 1) * Closes the blueprint editor */ bool Blueprint_DataOnlyBlueprint_Part1() { if (BlueprintObject) { Test->AddInfo(TEXT("Closing the blueprint editor")); GEditor->GetEditorSubsystem()->CloseAllAssetEditors(); } return true; } /** * Blueprint Test Stage: Data-only Blueprint (Part 2) * Re opens the blueprint editor */ bool Blueprint_DataOnlyBlueprint_Part2() { if (BlueprintObject) { Test->AddInfo(TEXT("Opening the blueprint editor for the second time")); GEditor->GetEditorSubsystem()->OpenEditorForAsset(BlueprintObject); } return true; } /** * Blueprint Test Stage: Data-only Blueprint (Part 3) * Checks that the editor opened in the Defaults mode */ bool Blueprint_DataOnlyBlueprint_Part3() { if (BlueprintObject) { IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem()->FindEditorForAsset(BlueprintObject, true); IBlueprintEditor* BlueprintEditor = (IBlueprintEditor*)AssetEditor; bool bCorrectEditorOpened = BlueprintEditor->GetCurrentMode() == FBlueprintEditorApplicationModes::BlueprintDefaultsMode; Test->TestTrue(TEXT("Correct defaults editor opened"), bCorrectEditorOpened); if (bCorrectEditorOpened) { Test->AddInfo(TEXT("Switching to components mode")); BlueprintEditor->SetCurrentMode(FBlueprintEditorApplicationModes::BlueprintComponentsMode); } } return true; } /** * Blueprint Test Stage: Components Mode (Part 1) * Adds 3 new components to the blueprint, changes the root component, renames the components, and compiles the blueprint */ bool Blueprint_ComponentsMode_Part1() { if (BlueprintObject) { IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem()->FindEditorForAsset(BlueprintObject, true); IBlueprintEditor* BlueprintEditor = (IBlueprintEditor*)AssetEditor; MeshNode = BlueprintEditorPromotionUtils::CreateBlueprintComponent(BlueprintObject, FirstBlueprintMesh); Test->TestNotNull(TEXT("First mesh component added"), MeshNode); OtherMeshNode = BlueprintEditorPromotionUtils::CreateBlueprintComponent(BlueprintObject, SecondBlueprintMesh); Test->TestNotNull(TEXT("Second mesh component added"), OtherMeshNode); PSNode = BlueprintEditorPromotionUtils::CreateBlueprintComponent(BlueprintObject, LoadedParticleSystem); Test->TestNotNull(TEXT("Particle system component added"), PSNode); //Set the Particle System as the root BlueprintEditorPromotionUtils::SetComponentAsRoot(BlueprintObject, PSNode); Test->TestTrue(TEXT("Particle system set as root"), PSNode->IsRootNode()); //Set Names const FName MeshName(TEXT("FirstMesh")); FBlueprintEditorUtils::RenameComponentMemberVariable(BlueprintObject, MeshNode, MeshName); Test->AddInfo(TEXT("Renamed the first mesh component to FirstMesh")); const FName OtherMeshName(TEXT("SecondMesh")); FBlueprintEditorUtils::RenameComponentMemberVariable(BlueprintObject, OtherMeshNode, OtherMeshName); Test->AddInfo(TEXT("Renamed the second mesh component to SecondMesh")); const FName PSName(TEXT("ParticleSys")); FBlueprintEditorUtils::RenameComponentMemberVariable(BlueprintObject, PSNode, PSName); Test->AddInfo(TEXT("Renamed the particle system component to ParticleSys")); BlueprintEditorPromotionUtils::CompileBlueprint(BlueprintObject); Test->AddInfo(TEXT("Switched to graph editing mode")); BlueprintEditor->SetCurrentMode(FBlueprintEditorApplicationModes::StandardBlueprintEditorMode); } return true; } /** * Blueprint Test Stage: Components Mode (Part 2) * Removes the 3 components added before and compiles the blueprint */ bool Blueprint_ComponentsMode_Part2() { if (BlueprintObject) { IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem()->FindEditorForAsset(BlueprintObject, true); IBlueprintEditor* BlueprintEditor = (IBlueprintEditor*)AssetEditor; //FEditorPromotionTestUtilities::TakeScreenshot(TEXT("BlueprintComponentVariables"), true); Test->AddInfo(TEXT("Switched to components mode")); BlueprintEditor->SetCurrentMode(FBlueprintEditorApplicationModes::BlueprintComponentsMode); BlueprintEditorPromotionUtils::RemoveBlueprintComponent(BlueprintObject, MeshNode); BlueprintEditorPromotionUtils::RemoveBlueprintComponent(BlueprintObject, OtherMeshNode); BlueprintEditorPromotionUtils::RemoveBlueprintComponent(BlueprintObject, PSNode); //There should only be the scene component left Test->TestTrue(TEXT("Successfully removed blueprint components"), BlueprintObject->SimpleConstructionScript->GetAllNodes().Num() == 1); MeshNode = NULL; OtherMeshNode = NULL; PSNode = NULL; Test->AddInfo(TEXT("Switched to graph mode")); BlueprintEditor->SetCurrentMode(FBlueprintEditorApplicationModes::StandardBlueprintEditorMode); BlueprintEditorPromotionUtils::CompileBlueprint(BlueprintObject); } return true; } /** * Blueprint Test Stage: Construction Script * Adds an AddStaticMeshComponent to the construction graph and links it to the entry node */ bool Blueprint_ConstructionScript() { if (BlueprintObject) { IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem()->FindEditorForAsset(BlueprintObject, true); FBlueprintEditor* BlueprintEditor = (FBlueprintEditor*)AssetEditor; UEdGraph* ConstructionGraph = FBlueprintEditorUtils::FindUserConstructionScript(BlueprintObject); BlueprintEditor->OpenGraphAndBringToFront(ConstructionGraph); AddMeshNode = BlueprintEditorPromotionUtils::CreateAddComponentActionNode(BlueprintObject, ConstructionGraph, FirstBlueprintMesh); Test->TestNotNull(TEXT("Add Static Mesh Component node created"), AddMeshNode); GEditor->UndoTransaction(); Test->TestTrue(TEXT("Undo add component node completed"), ConstructionGraph->Nodes.Num() == 0 || ConstructionGraph->Nodes[ConstructionGraph->Nodes.Num() - 1] != AddMeshNode); GEditor->RedoTransaction(); Test->TestTrue(TEXT("Redo add component node completed"), ConstructionGraph->Nodes.Num() > 0 && ConstructionGraph->Nodes[ConstructionGraph->Nodes.Num() - 1] == AddMeshNode); TArray EntryNodes; ConstructionGraph->GetNodesOfClass(EntryNodes); UEdGraphNode* EntryNode = EntryNodes.Num() > 0 ? EntryNodes[0] : NULL; Test->TestNotNull(TEXT("Found entry node to connect Add Static Mesh to"), EntryNode); if (EntryNode) { BlueprintEditorPromotionUtils::ConnectGraphNodes(AddMeshNode, UEdGraphSchema_K2::PN_Execute, EntryNode, UEdGraphSchema_K2::PN_Then); UEdGraphPin* EntryOutPin = EntryNode->FindPin(UEdGraphSchema_K2::PN_Then); UEdGraphPin* AddStaticMeshInPin = AddMeshNode->FindPin(UEdGraphSchema_K2::PN_Execute); Test->TestTrue(TEXT("Connected entry node to Add Static Mesh node"), EntryOutPin->LinkedTo.Contains(AddStaticMeshInPin)); GEditor->UndoTransaction(); Test->TestTrue(TEXT("Undo connection to Add Static Mesh Node succeeded"), EntryOutPin->LinkedTo.Num() == 0); GEditor->RedoTransaction(); Test->TestTrue(TEXT("Redo connection to Add Static Mesh Node succeeded"), EntryOutPin->LinkedTo.Contains(AddStaticMeshInPin)); } BlueprintEditorPromotionUtils::CompileBlueprint(BlueprintObject); } return true; } /** * Saves the blueprint stored in BlueprintObject */ void SaveBlueprint() { if (BlueprintObject && BlueprintPackage) { BlueprintPackage->SetDirtyFlag(true); BlueprintPackage->FullyLoad(); const FString PackagePath = FEditorPromotionTestUtilities::GetGamePath() + TEXT("/") + BlueprintEditorPromotionUtils::BlueprintNameString; FSavePackageArgs SaveArgs; SaveArgs.TopLevelFlags = RF_Standalone; SaveArgs.Error = GLog; bool bBlueprintSaved = UPackage::SavePackage(BlueprintPackage, nullptr, *FPackageName::LongPackageNameToFilename(PackagePath, FPackageName::GetAssetPackageExtension()), SaveArgs); Test->TestTrue(*FString::Printf(TEXT("Blueprint saved successfully (%s)"), *BlueprintObject->GetName()), bBlueprintSaved); } } /** * Blueprint Test Stage: Variable from component mode (Part 1) * Promotes the return pin of the AddStaticMeshNode to a variable and then renames it */ bool Blueprint_PromoteVariable_Part1() { if (BlueprintObject) { IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem()->FindEditorForAsset(BlueprintObject, true); FBlueprintEditor* BlueprintEditor = (FBlueprintEditor*)AssetEditor; BlueprintEditorPromotionUtils::PromotePinToVariable(BlueprintObject, AddMeshNode, UEdGraphSchema_K2::PN_ReturnValue); Test->AddInfo(TEXT("Promoted the return pin on the add mesh node to a variable")); const FName OldVarName(TEXT("NewVar")); // Default variable name const FName NewVarName(TEXT("MyMesh")); FBlueprintEditorUtils::RenameMemberVariable(BlueprintObject, OldVarName, NewVarName); Test->TestNotEqual(TEXT("New variable was renamed"), FBlueprintEditorUtils::FindMemberVariableGuidByName(BlueprintObject, OldVarName), FBlueprintEditorUtils::FindMemberVariableGuidByName(BlueprintObject, NewVarName)); BlueprintEditorPromotionUtils::CompileBlueprint(BlueprintObject); Test->AddInfo(TEXT("Switched to graph mode")); BlueprintEditor->SetCurrentMode(FBlueprintEditorApplicationModes::StandardBlueprintEditorMode); } return true; } bool Blueprint_PromoteVariable_Part2() { if (BlueprintObject) { IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem()->FindEditorForAsset(BlueprintObject, true); FBlueprintEditor* BlueprintEditor = (FBlueprintEditor*)AssetEditor; //FEditorPromotionTestUtilities::TakeScreenshot(TEXT("BlueprintMeshVariable"), true); Test->AddInfo(TEXT("Switched to components mode")); BlueprintEditor->SetCurrentMode(FBlueprintEditorApplicationModes::BlueprintComponentsMode); BlueprintEditorPromotionUtils::SendBlueprintResetViewCommand(); } return true; } ///** //* Blueprint Test Stage: Variable from component mode (Part 3) //* Takes a screenshot of the mesh variable //*/ //bool Blueprint_PromoteVariable_Part3() //{ // if (BlueprintObject) // { // FEditorPromotionTestUtilities::TakeScreenshot(TEXT("BlueprintComponent"), true); // } // return true; //} /** * Blueprint Test Stage: Event Graph * Adds a ReceiveBeginPlay and Delay node to the event graph */ bool Blueprint_EventGraph() { if (BlueprintObject) { IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem()->FindEditorForAsset(BlueprintObject, true); FBlueprintEditor* BlueprintEditor = (FBlueprintEditor*)AssetEditor; UEdGraph* EventGraph = FBlueprintEditorUtils::FindEventGraph(BlueprintObject); BlueprintEditor->OpenGraphAndBringToFront(EventGraph); Test->AddInfo(TEXT("Opened the event graph")); PostBeginPlayEventNode = BlueprintEditorPromotionUtils::CreatePostBeginPlayEvent(BlueprintObject, EventGraph); Test->TestNotNull(TEXT("Created EventBeginPlay node"), PostBeginPlayEventNode); UEdGraphPin* PlayThenPin = PostBeginPlayEventNode->FindPin(UEdGraphSchema_K2::PN_Then); DelayNode = BlueprintEditorPromotionUtils::AddDelayNode(BlueprintObject, EventGraph, PlayThenPin); Test->TestNotNull(TEXT("Created Delay node"), DelayNode); GEditor->UndoTransaction(); Test->TestTrue(TEXT("Undo adding Delay node succeeded"), EventGraph->Nodes.Num() == 0 || EventGraph->Nodes[EventGraph->Nodes.Num() - 1] != DelayNode); GEditor->RedoTransaction(); Test->TestTrue(TEXT("Redo adding Delay node succeeded"), EventGraph->Nodes.Num() > 0 && EventGraph->Nodes[EventGraph->Nodes.Num() - 1] == DelayNode); // Update Delay node's Duration pin with new default value const FName DelayDurationPinName = TEXT("Duration"); const FString NewDurationDefaultValue = TEXT("2.0"); BlueprintEditorPromotionUtils::SetPinDefaultValue(DelayNode, DelayDurationPinName, NewDurationDefaultValue); Test->TestEqual(TEXT("Delay node default duration set to 2.0 seconds"), DelayNode->FindPin(DelayDurationPinName)->DefaultValue, NewDurationDefaultValue); BlueprintEditorPromotionUtils::CompileBlueprint(BlueprintObject); } return true; } /** * Blueprint Test Stage: Custom Variable * Creates a custom string variable and adds Get/Set nodes for it */ bool Blueprint_CustomVariable() { if (BlueprintObject) { UEdGraph* EventGraph = FBlueprintEditorUtils::FindEventGraph(BlueprintObject); Test->AddInfo(TEXT("Added a string member variable")); BlueprintEditorPromotionUtils::AddStringMemberValue(BlueprintObject, BlueprintEditorPromotionUtils::BlueprintStringVariableName); SetNode = BlueprintEditorPromotionUtils::AddGetSetNode(BlueprintObject, EventGraph, BlueprintEditorPromotionUtils::BlueprintStringVariableName.ToString(), false); Test->TestNotNull(TEXT("Added Set node for string variable"), SetNode); GEditor->UndoTransaction(); Test->TestTrue(TEXT("Undo adding Set node succeeded"), EventGraph->Nodes.Num() == 0 || EventGraph->Nodes[EventGraph->Nodes.Num() - 1] != SetNode); GEditor->RedoTransaction(); Test->TestTrue(TEXT("Redo adding Set node succeeded"), EventGraph->Nodes.Num() > 0 && EventGraph->Nodes[EventGraph->Nodes.Num() - 1] == SetNode); GetNode = BlueprintEditorPromotionUtils::AddGetSetNode(BlueprintObject, EventGraph, BlueprintEditorPromotionUtils::BlueprintStringVariableName.ToString(), true, 400); Test->TestNotNull(TEXT("Added Get node for string variable"), GetNode); GEditor->UndoTransaction(); Test->TestTrue(TEXT("Undo adding Get node succeeded"), EventGraph->Nodes.Num() == 0 || EventGraph->Nodes[EventGraph->Nodes.Num() - 1] != GetNode); GEditor->RedoTransaction(); Test->TestTrue(TEXT("Redo adding Get node succeeded"), EventGraph->Nodes.Num() > 0 && EventGraph->Nodes[EventGraph->Nodes.Num() - 1] == GetNode); EventGraph->RemoveNode(SetNode); Test->TestFalse(TEXT("Set node removed from EventGraph"), EventGraph->Nodes.Contains(SetNode)); SetNode = NULL; } return true; } /** * Blueprint Test Stage: Using Variables * Adds a PrintString and SetStaticMesh then connects all the existing nodes */ bool Blueprint_UsingVariables() { if (BlueprintObject) { const bool bVariableIsHidden = false; FBlueprintEditorUtils::SetBlueprintOnlyEditableFlag(BlueprintObject, BlueprintEditorPromotionUtils::BlueprintStringVariableName, bVariableIsHidden); Test->AddInfo(TEXT("Exposed the blueprint string variable")); UEdGraph* EventGraph = FBlueprintEditorUtils::FindEventGraph(BlueprintObject); PrintNode = BlueprintEditorPromotionUtils::AddPrintStringNode(BlueprintObject, EventGraph); Test->TestNotNull(TEXT("Added Print String node"), PrintNode); //Connect Get to printstring UEdGraphPin* GetVarPin = GetNode->FindPin(BlueprintEditorPromotionUtils::BlueprintStringVariableName); UEdGraphPin* InStringPin = PrintNode->FindPin(TEXT("InString")); GetVarPin->MakeLinkTo(InStringPin); Test->TestTrue(TEXT("Connected string variable Get node to the Print String node"), GetVarPin->LinkedTo.Contains(InStringPin)); //Connect Delay to PrintString UEdGraphPin* DelayExecPin = DelayNode->FindPin(UEdGraphSchema_K2::PN_Then); UEdGraphPin* PrintStringPin = PrintNode->FindPin(UEdGraphSchema_K2::PN_Execute); DelayExecPin->MakeLinkTo(PrintStringPin); Test->TestTrue(TEXT("Connected Delay nod to Print String node"), DelayExecPin->LinkedTo.Contains(PrintStringPin)); const FName MyMeshVarName(TEXT("MyMesh")); GetNode = BlueprintEditorPromotionUtils::AddGetSetNode(BlueprintObject, EventGraph, MyMeshVarName.ToString(), true, 680); Test->TestNotNull(TEXT("Added Get node for MyMesh variable"), GetNode); SetStaticMeshNode = BlueprintEditorPromotionUtils::AddSetStaticMeshNode(BlueprintObject, EventGraph); Test->TestNotNull(TEXT("Added Set Static Mesh node"), SetStaticMeshNode); UEdGraphPin* GetExecPin = GetNode->FindPin(TEXT("MyMesh")); UEdGraphPin* SetStaticMeshSelfPin = SetStaticMeshNode->FindPin(UEdGraphSchema_K2::PN_Self); GetExecPin->MakeLinkTo(SetStaticMeshSelfPin); Test->TestTrue(TEXT("Connected Get MyMesh node to Set Static Mesh node"), GetExecPin->LinkedTo.Contains(SetStaticMeshSelfPin)); UEdGraphPin* SetStaticMeshMeshPin = SetStaticMeshNode->FindPin(TEXT("NewMesh")); SetStaticMeshMeshPin->DefaultObject = SecondBlueprintMesh; Test->TestEqual(*FString::Printf(TEXT("Set Static Mesh default mesh updated to %s"), *SecondBlueprintMesh->GetName()), Cast(SetStaticMeshMeshPin->DefaultObject), SecondBlueprintMesh); //Connect SetStaticMeshMesh to PrintString UEdGraphPin* PrintStringThenPin = PrintNode->FindPin(UEdGraphSchema_K2::PN_Then); UEdGraphPin* SetStaticMeshExecPin = SetStaticMeshNode->FindPin(UEdGraphSchema_K2::PN_Execute); PrintStringThenPin->MakeLinkTo(SetStaticMeshExecPin); Test->TestTrue(TEXT("Connected Print String node to Set Static Mesh node"), PrintStringThenPin->LinkedTo.Contains(SetStaticMeshExecPin)); } return true; } /** * Blueprint Test Stage: Renaming custom event * Creates, renames, and then removes a custom event node */ bool Blueprint_RenameCustomEvent() { if (BlueprintObject) { UEdGraph* EventGraph = FBlueprintEditorUtils::FindEventGraph(BlueprintObject); CustomEventNode = BlueprintEditorPromotionUtils::CreateCustomEvent(BlueprintObject, EventGraph, TEXT("NewEvent")); Test->TestNotNull(TEXT("Custom event node created"), CustomEventNode); if (CustomEventNode) { //Rename the event const FString NewEventNodeName = TEXT("RenamedEvent"); CustomEventNode->Rename(*NewEventNodeName); Test->TestEqual(TEXT("Custom event rename succeeded"), CustomEventNode->GetName(), NewEventNodeName); EventGraph->RemoveNode(CustomEventNode); Test->TestFalse(TEXT("Blueprint EventGraph does not contain removed custom event node"), EventGraph->Nodes.Contains(CustomEventNode)); CustomEventNode = NULL; } } return true; } /** * Blueprint Test Stage: New function * Creates a new function graph and then hooks up a new AddParticleSystem inside it */ bool Blueprint_NewFunctions() { if (BlueprintObject) { CustomGraph = BlueprintEditorPromotionUtils::CreateNewFunctionGraph(BlueprintObject, TEXT("NewFunction")); Test->TestNotNull(TEXT("Created new function graph"), CustomGraph); AddParticleSystemNode = BlueprintEditorPromotionUtils::CreateAddComponentActionNode(BlueprintObject, CustomGraph, LoadedParticleSystem); Test->TestNotNull(TEXT("Created Add Particle System node"), AddParticleSystemNode); UEdGraphPin* ExecutePin = AddParticleSystemNode ? AddParticleSystemNode->FindPin(UEdGraphSchema_K2::PN_Execute) : NULL; //Find the input for the function graph TArray EntryNodes; CustomGraph->GetNodesOfClass(EntryNodes); UEdGraphNode* EntryNode = EntryNodes.Num() > 0 ? EntryNodes[0] : NULL; if (EntryNode && ExecutePin) { UEdGraphPin* EntryPin = EntryNode->FindPin(UEdGraphSchema_K2::PN_Then); EntryPin->MakeLinkTo(ExecutePin); Test->TestTrue(TEXT("Connected Add Particle System node to entry node"), EntryPin->LinkedTo.Contains(ExecutePin)); } BlueprintEditorPromotionUtils::CompileBlueprint(BlueprintObject); } return true; } /** * Blueprint Test Stage: Completing the blueprint * Adds a CallFunction node to call the custom function created in the previous step. */ bool Blueprint_CompleteBlueprint() { if (BlueprintObject) { UEdGraph* EventGraph = FBlueprintEditorUtils::FindEventGraph(BlueprintObject); UEdGraphPin* SetStaticMeshThenPin = SetStaticMeshNode->FindPin(UEdGraphSchema_K2::PN_Then); CallFunctionNode = BlueprintEditorPromotionUtils::AddCallFunctionGraphNode(BlueprintObject, EventGraph, TEXT("NewFunction"), SetStaticMeshThenPin); Test->TestNotNull(TEXT("Created Call Function node"), CallFunctionNode); if (CallFunctionNode) { Test->TestTrue(TEXT("Connected Set Static Mesh node to Call Function node"), SetStaticMeshThenPin->LinkedTo.Contains(CallFunctionNode->FindPin(UEdGraphSchema_K2::PN_Execute))); } BlueprintEditorPromotionUtils::CompileBlueprint(BlueprintObject); SaveBlueprint(); } return true; } }; } /** * Latent command to run the main build promotion test */ DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FRunPromotionTestCommand, TSharedPtr, BlueprintEditorTestInfo); bool FRunPromotionTestCommand::Update() { return BlueprintEditorTestInfo->Update(); } /** * Automation test that handles the blueprint editor promotion process */ bool FBlueprintEditorPromotionTest::RunTest(const FString& Parameters) { TSharedPtr BuildPromotionTest = MakeShareable(new BlueprintEditorPromotionTestHelper::FBlueprintEditorPromotionTestHelper()); BuildPromotionTest->Test = this; ADD_LATENT_AUTOMATION_COMMAND(FRunPromotionTestCommand(BuildPromotionTest)); return true; } #undef LOCTEXT_NAMESPACE #endif //WITH_DEV_AUTOMATION_TESTS