Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions samples/pause/Pause_testPlan.fx.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
testSuite:
testSuiteName: Pause Function Tests
testSuiteDescription: Verifies that the Pause function works correctly
persona: User1
appLogicalName: mda_input_controls_app

testCases:
- testCaseName: Test Pause Function - Non-Headless Mode
testCaseDescription: Tests that Pause function works when headless is false
testSteps: |
=
Screenshot("before_pause.png");
Pause();
Screenshot("after_pause.png");
Assert(true, "Test continued after Pause function");

testSettings:
headless: false
browserConfigurations:
- browser: Chromium
channel: msedge
extensionModules:
enable: true
allowPowerFxNamespaces:
- Preview

environmentVariables:
users:
- personaName: User1
emailKey: user1Email
passwordKey: NotNeeded
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ public class TestSettingExtensions
public HashSet<string> DenyPowerFxNamespaces { get; set; } = new HashSet<string>();


// <summary>
// List of action class names (or wildcard patterns) that are allowed to be registered in the root namespace
// </summary>
public HashSet<string> AllowActionsInRoot { get; set; } = new HashSet<string>() { "PauseFunction" };
Comment thread
v-raghulraja marked this conversation as resolved.
Outdated

/// <summary>
/// Additional optional parameters for extension modules
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,14 +313,6 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
{
var isValid = true;

#if DEBUG
// Add Experimenal namespaces in Debug compile if it has not been added in allow list
if (!settings.AllowPowerFxNamespaces.Contains(NAMESPACE_PREVIEW))
{
settings.AllowPowerFxNamespaces.Add(NAMESPACE_PREVIEW);
}
#endif

#if RELEASE
// Add Deprecated namespaces in Release compile if it has not been added in deny list
if (!settings.DenyPowerFxNamespaces.Contains(NAMESPACE_DEPRECATED))
Expand All @@ -334,6 +326,28 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
stream.Position = 0;
ModuleDefinition module = ModuleDefinition.ReadModule(stream);

// Detect if this assembly contains provider types so we can allow Preview for provider assemblies
var assemblyHasProvider = module.GetAllTypes().Any(t =>
t.Interfaces.Any(i => i.InterfaceType.FullName == typeof(Providers.ITestWebProvider).FullName) ||
t.Interfaces.Any(i => i.InterfaceType.FullName == typeof(Users.IUserManager).FullName) ||
t.Interfaces.Any(i => i.InterfaceType.FullName == typeof(Config.IUserCertificateProvider).FullName)
);

// Check if PauseModule exists and inspect its IsPreviewNamespaceEnabled property
var pauseModule = module.Types.FirstOrDefault(t => t.Name == "PauseModule");
if (pauseModule != null)
{
// Check if the PauseModule has IsPreviewNamespaceEnabled property
var previewProperty = pauseModule.Properties.FirstOrDefault(p => p.Name == "IsPreviewNamespaceEnabled");
if (previewProperty != null)
{
// Do not modify the global settings here. Instead record that PauseModule exposes the preview toggle.
// The property's value will be determined at runtime based on YAML settings; use the flag to
// selectively allow the Preview namespace for providers only.
Logger?.LogInformation("Detected PauseModule.IsPreviewNamespaceEnabled; preview semantics will be applied per-type.");
}
}

// Get the source code of the assembly as will be used to check Power FX Namespaces
var code = DecompileModuleToCSharp(assembly);

Expand All @@ -350,6 +364,13 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
{
if (CheckPropertyArrayContainsValue(type, "Namespaces", out var values))
{
// For provider types, always allow Preview namespace (preview namespace checks apply to actions only)
Comment thread
v-raghulraja marked this conversation as resolved.
Outdated
var allowedForProvider = settings.AllowPowerFxNamespaces.ToList();
if (!allowedForProvider.Contains(NAMESPACE_PREVIEW))
{
allowedForProvider.Add(NAMESPACE_PREVIEW);
}

foreach (var name in values)
{
// Check against deny list using regular expressions
Expand All @@ -361,15 +382,15 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s

// Check against deny wildcard and allow list using regular expressions
if (settings.DenyPowerFxNamespaces.Any(pattern => pattern == "*") &&
(!settings.AllowPowerFxNamespaces.Any(pattern => Regex.IsMatch(name, WildcardToRegex(pattern))) &&
(!allowedForProvider.Any(pattern => Regex.IsMatch(name, WildcardToRegex(pattern))) &&
name != NAMESPACE_TEST_ENGINE))
{
Logger.LogInformation($"Deny Power FX Namespace {name} for {type.Name}");
return false;
}

// Check against allow list using regular expressions
if (!settings.AllowPowerFxNamespaces.Any(pattern => Regex.IsMatch(name, WildcardToRegex(pattern))) &&
if (!allowedForProvider.Any(pattern => Regex.IsMatch(name, WildcardToRegex(pattern))) &&
name != NAMESPACE_TEST_ENGINE)
{
Logger.LogInformation($"Not allow Power FX Namespace {name} for {type.Name}");
Expand All @@ -382,6 +403,13 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
// Extension Module Check are based on constructor
if (type.BaseType != null && type.BaseType.Name == "ReflectionFunction")
{
// Special handling for PauseFunction - allow root namespace when PauseModule is present
if (type.Name == "PauseFunction" && pauseModule != null)
{
Logger?.LogInformation($"Allowing PauseFunction in root namespace due to PauseModule presence.");
continue; // Skip namespace validation for PauseFunction
}

var constructors = type.GetConstructors();

if (constructors.Count() == 0)
Expand Down Expand Up @@ -439,6 +467,13 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
return false;
}

// For functions defined in assemblies that are providers, allow Preview namespace
var allowedForFunction = settings.AllowPowerFxNamespaces.ToList();
if (assemblyHasProvider && !allowedForFunction.Contains(NAMESPACE_PREVIEW))
{
allowedForFunction.Add(NAMESPACE_PREVIEW);
}

if (settings.DenyPowerFxNamespaces.Contains(name))
{
// Deny list match
Expand All @@ -447,8 +482,8 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
}

if ((settings.DenyPowerFxNamespaces.Contains("*") && (
!settings.AllowPowerFxNamespaces.Contains(name) ||
(!settings.AllowPowerFxNamespaces.Contains(name) && name != NAMESPACE_TEST_ENGINE)
!allowedForFunction.Contains(name) ||
(!allowedForFunction.Contains(name) && name != NAMESPACE_TEST_ENGINE)
)
))
{
Expand All @@ -457,13 +492,104 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
return false;
}

if (!settings.AllowPowerFxNamespaces.Contains(name) && name != NAMESPACE_TEST_ENGINE)
if (!allowedForFunction.Contains(name) && name != NAMESPACE_TEST_ENGINE)
{
Logger.LogInformation($"Do not allow Power FX Namespace {name} for {type.Name}");
// Not in allow list or the Reserved TestEngine namespace
return false;
}
}

// Special validation for ReflectionAction types. Actions must not declare the Preview namespace
if (type.BaseType != null && type.BaseType.Name == "ReflectionAction")
{
// If PauseModule is present, allow certain actions to be declared in the root namespace (skip namespace validation)
if (pauseModule != null)
{
// Check configured allow-list of action class names/wildcards
var allowActions = settings.AllowActionsInRoot ?? new HashSet<string>();
var isAllowedAction = allowActions.Any(pattern => Regex.IsMatch(type.Name, WildcardToRegex(pattern)));
if (isAllowedAction)
{
Logger?.LogInformation($"Allowing action {type.Name} in root namespace due to PauseModule presence and AllowActionsInRoot setting.");
continue; // Skip namespace validation for this action
}
}

var constructors = type.GetConstructors();

if (constructors.Count() == 0)
{
Logger.LogInformation($"No constructor defined for {type.Name}. Found {constructors.Count()} expected 1 or more");
return false;
}

var constructor = constructors.Where(c => c.HasBody).FirstOrDefault();

if (constructor == null || !constructor.HasBody)
{
Logger.LogInformation($"No constructor body defined for {type.Name}");
return false;
}

var baseCall = constructor.Body.Instructions?.FirstOrDefault(i => i.OpCode == OpCodes.Call && i.Operand is MethodReference && ((MethodReference)i.Operand).Name == ".ctor");
if (baseCall == null)
{
Logger.LogInformation($"No base constructor defined for {type.Name}");
return false;
}

MethodReference baseConstructor = (MethodReference)baseCall.Operand;
if (baseConstructor.Parameters?.Count() < 2)
{
Logger.LogInformation($"No not enough parameters for {type.Name}");
return false;
}

if (baseConstructor.Parameters[0].ParameterType.FullName != "Microsoft.PowerFx.Core.Utils.DPath")
{
Logger.LogInformation($"No Power FX Namespace for {type.Name}");
return false;
}

// Extract namespace from decompiled source
var actionName = GetPowerFxNamespace(type.Name, code);
if (string.IsNullOrEmpty(actionName))
{
Logger.LogInformation($"No Power FX Namespace found for {type.Name}");
return false;
}

// Actions must not use the Preview namespace
if (string.Equals(actionName, NAMESPACE_PREVIEW, StringComparison.OrdinalIgnoreCase))
{
Logger.LogInformation($"Deny Preview Power FX Namespace {actionName} for action {type.Name}");
return false;
}

// Continue with the same allow/deny validation as functions
if (settings.DenyPowerFxNamespaces.Contains(actionName))
{
Logger.LogInformation($"Deny Power FX Namespace {actionName} for {type.Name}");
return false;
}

if ((settings.DenyPowerFxNamespaces.Contains("*") && (
!settings.AllowPowerFxNamespaces.Contains(actionName) ||
(!settings.AllowPowerFxNamespaces.Contains(actionName) && actionName != NAMESPACE_TEST_ENGINE)
)
))
{
Logger.LogInformation($"Deny Power FX Namespace {actionName} for {type.Name}");
return false;
}

if (!settings.AllowPowerFxNamespaces.Contains(actionName) && actionName != NAMESPACE_TEST_ENGINE)
{
Logger.LogInformation($"Do not allow Power FX Namespace {actionName} for {type.Name}");
return false;
}
}
}
}
return isValid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ public void Setup(TestSettings settings)
}
foreach (var module in modules)
{
// Register all modules including pause module
module.RegisterPowerFxFunction(powerFxConfig, TestInfraFunctions, _testWebProvider, SingleTestInstanceState, TestState, _fileSystem);
}
}
Expand Down
Loading
Loading