From 58353d3eb5fbf44e1bfb40fa9ba45ebd058845cf Mon Sep 17 00:00:00 2001
From: v-raghulraja <165115074+v-raghulraja@users.noreply.github.com>
Date: Fri, 22 Aug 2025 21:29:52 +0530
Subject: [PATCH 01/16] Pause Changes
---
samples/pause/Pause_testPlan.fx.yaml | 31 ++++++++++
.../PowerFx/Functions/PauseFunction.cs | 62 +++++++++++++++++++
.../PowerFx/PowerFxEngine.cs | 6 ++
3 files changed, 99 insertions(+)
create mode 100644 samples/pause/Pause_testPlan.fx.yaml
create mode 100644 src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/PauseFunction.cs
diff --git a/samples/pause/Pause_testPlan.fx.yaml b/samples/pause/Pause_testPlan.fx.yaml
new file mode 100644
index 000000000..67e9d8e36
--- /dev/null
+++ b/samples/pause/Pause_testPlan.fx.yaml
@@ -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
+ parameters:
+ enableCorePause: true
+
+environmentVariables:
+ users:
+ - personaName: User1
+ emailKey: user1Email
+ passwordKey: NotNeeded
diff --git a/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/PauseFunction.cs b/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/PauseFunction.cs
new file mode 100644
index 000000000..ef69e3cb0
--- /dev/null
+++ b/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/PauseFunction.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using Microsoft.Extensions.Logging;
+using Microsoft.PowerApps.TestEngine.Config;
+using Microsoft.PowerApps.TestEngine.TestInfra;
+using Microsoft.PowerFx;
+using Microsoft.PowerFx.Types;
+
+namespace Microsoft.PowerApps.TestEngine.PowerFx.Functions
+{
+ ///
+ /// This will pause the current test and allow the user to interact with the browser and inspect state when headless mode is false
+ ///
+ public class PauseFunction : ReflectionFunction
+ {
+ private readonly ITestInfraFunctions _testInfraFunctions;
+ private readonly ITestState _testState;
+ private readonly ILogger _logger;
+
+ public PauseFunction(ITestInfraFunctions testInfraFunctions, ITestState testState, ILogger logger)
+ : base("Pause", FormulaType.Blank)
+ {
+ _testInfraFunctions = testInfraFunctions;
+ _testState = testState;
+ _logger = logger;
+ }
+
+ public BlankValue Execute()
+ {
+ _logger.LogInformation("------------------------------\n\n" +
+ "Executing Pause function.");
+
+ var testSettings = _testState.GetTestSettings();
+
+ if (!IsPreviewEnabledInOriginalConfig(testSettings))
+ {
+ _logger.LogInformation("Pause function is disabled - Preview namespace not explicitly enabled in YAML configuration.");
+ return FormulaValue.NewBlank();
+ }
+
+ if (!testSettings.Headless)
+ {
+ var page = _testInfraFunctions.GetContext().Pages.First();
+ page.PauseAsync().Wait();
+ _logger.LogInformation("Successfully finished executing Pause function.");
+ }
+ else
+ {
+ _logger.LogInformation("Skip Pause function as in headless mode.");
+ }
+
+ return FormulaValue.NewBlank();
+ }
+
+ private bool IsPreviewEnabledInOriginalConfig(TestSettings testSettings)
+ {
+ return testSettings?.ExtensionModules?.Parameters?.ContainsKey("enableCorePause") == true &&
+ testSettings.ExtensionModules.Parameters["enableCorePause"]?.ToString().ToLower() == "true";
+ }
+ }
+}
diff --git a/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs b/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs
index dc6e97270..5509ca544 100644
--- a/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs
+++ b/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs
@@ -102,6 +102,7 @@ public void Setup(TestSettings settings)
powerFxConfig.AddFunction(new AssertNotErrorFunction(Logger));
powerFxConfig.AddFunction(new SetPropertyFunction(_testWebProvider, Logger));
powerFxConfig.AddFunction(new IsMatchFunction(Logger));
+ powerFxConfig.AddFunction(new PauseFunction(TestInfraFunctions, TestState, Logger));
if (settings != null && settings.ExtensionModules != null && settings.ExtensionModules.Enable)
{
@@ -112,6 +113,11 @@ public void Setup(TestSettings settings)
}
foreach (var module in modules)
{
+ if (module.GetType().Name.Contains("Pause"))
+ {
+ Logger.LogInformation("Skipping pause module as core Pause function is enabled");
+ continue;
+ }
module.RegisterPowerFxFunction(powerFxConfig, TestInfraFunctions, _testWebProvider, SingleTestInstanceState, TestState, _fileSystem);
}
}
From 95055a5feaa08ed148c6bde5e16e2803d0eacaaa Mon Sep 17 00:00:00 2001
From: v-raghulraja <165115074+v-raghulraja@users.noreply.github.com>
Date: Thu, 28 Aug 2025 19:28:44 +0530
Subject: [PATCH 02/16] namepsace changes
---
samples/pause/Pause_testPlan.fx.yaml | 4 ++--
.../PowerFx/Functions/PauseFunction.cs | 3 +--
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/samples/pause/Pause_testPlan.fx.yaml b/samples/pause/Pause_testPlan.fx.yaml
index 67e9d8e36..82071c911 100644
--- a/samples/pause/Pause_testPlan.fx.yaml
+++ b/samples/pause/Pause_testPlan.fx.yaml
@@ -21,8 +21,8 @@ testSettings:
channel: msedge
extensionModules:
enable: true
- parameters:
- enableCorePause: true
+ allowPowerFxNamespaces:
+ - Preview
environmentVariables:
users:
diff --git a/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/PauseFunction.cs b/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/PauseFunction.cs
index ef69e3cb0..1c2dbfdcc 100644
--- a/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/PauseFunction.cs
+++ b/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/PauseFunction.cs
@@ -55,8 +55,7 @@ public BlankValue Execute()
private bool IsPreviewEnabledInOriginalConfig(TestSettings testSettings)
{
- return testSettings?.ExtensionModules?.Parameters?.ContainsKey("enableCorePause") == true &&
- testSettings.ExtensionModules.Parameters["enableCorePause"]?.ToString().ToLower() == "true";
+ return testSettings?.ExtensionModules?.AllowPowerFxNamespaces?.Contains("Preview") == true;
}
}
}
From 0226a0ee02ee29c6dd2ed6ccce676dc3f7d8921d Mon Sep 17 00:00:00 2001
From: v-raghulraja <165115074+v-raghulraja@users.noreply.github.com>
Date: Fri, 5 Sep 2025 15:48:57 +0530
Subject: [PATCH 03/16] Pause module changes
---
.../Modules/TestEngineExtensionChecker.cs | 22 +++++++
.../PowerFx/Functions/PauseFunction.cs | 61 -------------------
.../PowerFx/PowerFxEngine.cs | 7 +--
src/testengine.module.pause/PauseFunction.cs | 18 ++++--
src/testengine.module.pause/PauseModule.cs | 21 ++++++-
5 files changed, 54 insertions(+), 75 deletions(-)
delete mode 100644 src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/PauseFunction.cs
diff --git a/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineExtensionChecker.cs b/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineExtensionChecker.cs
index 9174d4b20..3d2f92fb0 100644
--- a/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineExtensionChecker.cs
+++ b/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineExtensionChecker.cs
@@ -334,6 +334,21 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
stream.Position = 0;
ModuleDefinition module = ModuleDefinition.ReadModule(stream);
+ // 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)
+ {
+ // If PauseModule has IsPreviewNamespaceEnabled property, enable Preview namespace
+ // The property's value will be determined at runtime based on YAML settings
+ settings.AllowPowerFxNamespaces.Add(NAMESPACE_PREVIEW);
+ Logger?.LogInformation("Auto-enabled Preview namespace due to PauseModule.IsPreviewNamespaceEnabled property.");
+ }
+ }
+
// Get the source code of the assembly as will be used to check Power FX Namespaces
var code = DecompileModuleToCSharp(assembly);
@@ -382,6 +397,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)
diff --git a/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/PauseFunction.cs b/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/PauseFunction.cs
deleted file mode 100644
index 1c2dbfdcc..000000000
--- a/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/PauseFunction.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-using Microsoft.Extensions.Logging;
-using Microsoft.PowerApps.TestEngine.Config;
-using Microsoft.PowerApps.TestEngine.TestInfra;
-using Microsoft.PowerFx;
-using Microsoft.PowerFx.Types;
-
-namespace Microsoft.PowerApps.TestEngine.PowerFx.Functions
-{
- ///
- /// This will pause the current test and allow the user to interact with the browser and inspect state when headless mode is false
- ///
- public class PauseFunction : ReflectionFunction
- {
- private readonly ITestInfraFunctions _testInfraFunctions;
- private readonly ITestState _testState;
- private readonly ILogger _logger;
-
- public PauseFunction(ITestInfraFunctions testInfraFunctions, ITestState testState, ILogger logger)
- : base("Pause", FormulaType.Blank)
- {
- _testInfraFunctions = testInfraFunctions;
- _testState = testState;
- _logger = logger;
- }
-
- public BlankValue Execute()
- {
- _logger.LogInformation("------------------------------\n\n" +
- "Executing Pause function.");
-
- var testSettings = _testState.GetTestSettings();
-
- if (!IsPreviewEnabledInOriginalConfig(testSettings))
- {
- _logger.LogInformation("Pause function is disabled - Preview namespace not explicitly enabled in YAML configuration.");
- return FormulaValue.NewBlank();
- }
-
- if (!testSettings.Headless)
- {
- var page = _testInfraFunctions.GetContext().Pages.First();
- page.PauseAsync().Wait();
- _logger.LogInformation("Successfully finished executing Pause function.");
- }
- else
- {
- _logger.LogInformation("Skip Pause function as in headless mode.");
- }
-
- return FormulaValue.NewBlank();
- }
-
- private bool IsPreviewEnabledInOriginalConfig(TestSettings testSettings)
- {
- return testSettings?.ExtensionModules?.AllowPowerFxNamespaces?.Contains("Preview") == true;
- }
- }
-}
diff --git a/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs b/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs
index 5509ca544..72ec5cd94 100644
--- a/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs
+++ b/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs
@@ -102,7 +102,6 @@ public void Setup(TestSettings settings)
powerFxConfig.AddFunction(new AssertNotErrorFunction(Logger));
powerFxConfig.AddFunction(new SetPropertyFunction(_testWebProvider, Logger));
powerFxConfig.AddFunction(new IsMatchFunction(Logger));
- powerFxConfig.AddFunction(new PauseFunction(TestInfraFunctions, TestState, Logger));
if (settings != null && settings.ExtensionModules != null && settings.ExtensionModules.Enable)
{
@@ -113,11 +112,7 @@ public void Setup(TestSettings settings)
}
foreach (var module in modules)
{
- if (module.GetType().Name.Contains("Pause"))
- {
- Logger.LogInformation("Skipping pause module as core Pause function is enabled");
- continue;
- }
+ // Register all modules including pause module
module.RegisterPowerFxFunction(powerFxConfig, TestInfraFunctions, _testWebProvider, SingleTestInstanceState, TestState, _fileSystem);
}
}
diff --git a/src/testengine.module.pause/PauseFunction.cs b/src/testengine.module.pause/PauseFunction.cs
index 9cfeb81a6..e83b34973 100644
--- a/src/testengine.module.pause/PauseFunction.cs
+++ b/src/testengine.module.pause/PauseFunction.cs
@@ -5,7 +5,6 @@
using Microsoft.PowerApps.TestEngine.Config;
using Microsoft.PowerApps.TestEngine.TestInfra;
using Microsoft.PowerFx;
-using Microsoft.PowerFx.Core.Utils;
using Microsoft.PowerFx.Types;
namespace testengine.module
@@ -18,19 +17,28 @@ public class PauseFunction : ReflectionFunction
private readonly ITestInfraFunctions _testInfraFunctions;
private readonly ITestState _testState;
private readonly ILogger _logger;
+ private readonly PauseModule _pauseModule;
- public PauseFunction(ITestInfraFunctions testInfraFunctions, ITestState testState, ILogger logger)
- : base(DPath.Root.Append(new DName("Preview")), "Pause", FormulaType.Blank)
+ // Register in root (no Preview namespace)
+ public PauseFunction(ITestInfraFunctions testInfraFunctions, ITestState testState, ILogger logger, PauseModule pauseModule = null)
+ : base("Pause", FormulaType.Blank)
{
_testInfraFunctions = testInfraFunctions;
_testState = testState;
_logger = logger;
+ _pauseModule = pauseModule;
}
public BlankValue Execute()
{
- _logger.LogInformation("------------------------------\n\n" +
- "Executing Pause function.");
+ _logger.LogInformation("------------------------------\n\nExecuting Pause function.");
+
+ // Check if PauseModule's IsPreviewNamespaceEnabled property allows execution
+ if (_pauseModule != null && !_pauseModule.IsPreviewNamespaceEnabled)
+ {
+ _logger.LogInformation("Pause function is disabled - Preview namespace not enabled in PauseModule configuration.");
+ return FormulaValue.NewBlank();
+ }
if (!_testState.GetTestSettings().Headless)
{
diff --git a/src/testengine.module.pause/PauseModule.cs b/src/testengine.module.pause/PauseModule.cs
index c2ec41d6a..d2e4a3aa6 100644
--- a/src/testengine.module.pause/PauseModule.cs
+++ b/src/testengine.module.pause/PauseModule.cs
@@ -16,16 +16,31 @@ namespace testengine.module
[Export(typeof(ITestEngineModule))]
public class PauseModule : ITestEngineModule
{
+ ///
+ /// True when YAML testSettings.extensionModules.allowPowerFxNamespaces contains "Preview".
+ /// Read-only externally; set during registration when TestSettings are available.
+ ///
+ public bool IsPreviewNamespaceEnabled { get; private set; } = false;
+
public void ExtendBrowserContextOptions(BrowserNewContextOptions options, TestSettings settings)
{
-
+ // If called first, try to initialize from provided settings as well
+ if (settings != null && settings.ExtensionModules != null && settings.ExtensionModules.AllowPowerFxNamespaces != null)
+ {
+ IsPreviewNamespaceEnabled = settings.ExtensionModules.AllowPowerFxNamespaces.Contains("Preview");
+ }
}
public void RegisterPowerFxFunction(PowerFxConfig config, ITestInfraFunctions testInfraFunctions, ITestWebProvider testWebProvider, ISingleTestInstanceState singleTestInstanceState, ITestState testState, IFileSystem fileSystem)
{
+ // Initialize the read-only property from YAML via TestState
+ var testSettings = testState?.GetTestSettings();
+ IsPreviewNamespaceEnabled = testSettings?.ExtensionModules?.AllowPowerFxNamespaces?.Contains("Preview") == true;
+
ILogger logger = singleTestInstanceState.GetLogger();
- config.AddFunction(new PauseFunction(testInfraFunctions, testState, logger));
- logger.LogInformation("Registered Pause()");
+ // Pass this PauseModule instance to PauseFunction so it can check IsPreviewNamespaceEnabled
+ config.AddFunction(new PauseFunction(testInfraFunctions, testState, logger, this));
+ logger.LogInformation($"Registered Pause() with Preview namespace enabled: {IsPreviewNamespaceEnabled}");
}
public async Task RegisterNetworkRoute(ITestState state, ISingleTestInstanceState singleTestInstanceState, IFileSystem fileSystem, IPage Page, NetworkRequestMock mock)
From 4ce8a3826fa2b136b0576e9d984160f2cda5f8a6 Mon Sep 17 00:00:00 2001
From: v-raghulraja <165115074+v-raghulraja@users.noreply.github.com>
Date: Thu, 11 Sep 2025 20:52:24 +0530
Subject: [PATCH 04/16] Preview Changes for action function only
---
.../Config/TestSettingExtensions.cs | 5 +
.../Modules/TestEngineExtensionChecker.cs | 216 +++++++++++++-----
.../PauseFunctionTests.cs | 35 +++
.../PauseModuleTests.cs | 4 +-
src/testengine.module.pause/PauseFunction.cs | 26 ++-
src/testengine.module.pause/PauseModule.cs | 60 +++--
6 files changed, 270 insertions(+), 76 deletions(-)
diff --git a/src/Microsoft.PowerApps.TestEngine/Config/TestSettingExtensions.cs b/src/Microsoft.PowerApps.TestEngine/Config/TestSettingExtensions.cs
index 45ea971d7..e0f5cbbd3 100644
--- a/src/Microsoft.PowerApps.TestEngine/Config/TestSettingExtensions.cs
+++ b/src/Microsoft.PowerApps.TestEngine/Config/TestSettingExtensions.cs
@@ -60,6 +60,11 @@ public class TestSettingExtensions
public HashSet DenyPowerFxNamespaces { get; set; } = new HashSet();
+ //
+ // List of action class names (or wildcard patterns) that are allowed to be registered in the root namespace
+ //
+ public HashSet AllowActionsInRoot { get; set; } = new HashSet() { "PauseFunction" };
+
///
/// Additional optional parameters for extension modules
///
diff --git a/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineExtensionChecker.cs b/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineExtensionChecker.cs
index 3d2f92fb0..f3c5b685f 100644
--- a/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineExtensionChecker.cs
+++ b/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineExtensionChecker.cs
@@ -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))
@@ -334,22 +326,29 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
stream.Position = 0;
ModuleDefinition module = ModuleDefinition.ReadModule(stream);
- // 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)
- {
- // If PauseModule has IsPreviewNamespaceEnabled property, enable Preview namespace
- // The property's value will be determined at runtime based on YAML settings
- settings.AllowPowerFxNamespaces.Add(NAMESPACE_PREVIEW);
- Logger?.LogInformation("Auto-enabled Preview namespace due to PauseModule.IsPreviewNamespaceEnabled property.");
- }
- }
-
- // Get the source code of the assembly as will be used to check Power FX Namespaces
+ // 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"); // Local flag indicating PauseModule declares IsPreviewNamespaceEnabled
+ 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);
foreach (TypeDefinition type in module.GetAllTypes())
@@ -365,38 +364,45 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
{
if (CheckPropertyArrayContainsValue(type, "Namespaces", out var values))
{
- foreach (var name in values)
+ // For provider types, always allow Preview namespace (preview namespace checks apply to actions only)
+ var allowedForProvider = settings.AllowPowerFxNamespaces.ToList();
+ if (!allowedForProvider.Contains(NAMESPACE_PREVIEW))
{
- // Check against deny list using regular expressions
- if (settings.DenyPowerFxNamespaces.Any(pattern => Regex.IsMatch(name, WildcardToRegex(pattern))))
- {
- Logger.LogInformation($"Deny Power FX Namespace {name} for {type.Name}");
- return false;
- }
-
- // 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.Add(NAMESPACE_PREVIEW);
+ }
+
+ foreach (var name in values)
+ {
+ // Check against deny list using regular expressions
+ if (settings.DenyPowerFxNamespaces.Any(pattern => Regex.IsMatch(name, WildcardToRegex(pattern))))
+ {
+ Logger.LogInformation($"Deny Power FX Namespace {name} for {type.Name}");
+ return false;
+ }
+
+ // Check against deny wildcard and allow list using regular expressions
+ if (settings.DenyPowerFxNamespaces.Any(pattern => 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;
- }
+ {
+ 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))) &&
+ // Check against allow list using regular expressions
+ if (!allowedForProvider.Any(pattern => Regex.IsMatch(name, WildcardToRegex(pattern))) &&
name != NAMESPACE_TEST_ENGINE)
- {
- Logger.LogInformation($"Not allow Power FX Namespace {name} for {type.Name}");
- return false;
- }
- }
- }
- }
-
- // Extension Module Check are based on constructor
- if (type.BaseType != null && type.BaseType.Name == "ReflectionFunction")
- {
+ {
+ Logger.LogInformation($"Not allow Power FX Namespace {name} for {type.Name}");
+ return false;
+ }
+ }
+ }
+ }
+
+ // 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)
{
@@ -461,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
@@ -469,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)
)
))
{
@@ -479,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();
+ 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;
diff --git a/src/testengine.module.pause.tests/PauseFunctionTests.cs b/src/testengine.module.pause.tests/PauseFunctionTests.cs
index 8ac6b45d0..e5f6ce9f2 100644
--- a/src/testengine.module.pause.tests/PauseFunctionTests.cs
+++ b/src/testengine.module.pause.tests/PauseFunctionTests.cs
@@ -101,5 +101,40 @@ public void SkipExecute()
It.IsAny(),
It.IsAny>()), Times.AtLeastOnce);
}
+
+ [Fact]
+ public void PauseExecuteWithPreviewNamespaceTracking()
+ {
+ // Arrange - Test that Preview namespace tracking works correctly
+ var pauseModuleMock = new Mock();
+ pauseModuleMock.SetupGet(p => p.IsPreviewNamespaceEnabled).Returns(true);
+
+ var module = new PauseFunction(MockTestInfraFunctions.Object, MockTestState.Object, MockLogger.Object, pauseModuleMock.Object);
+ var settings = new TestSettings() { Headless = false };
+ var mockContext = new Mock(MockBehavior.Strict);
+ var mockPage = new Mock(MockBehavior.Strict);
+
+ MockTestState.Setup(x => x.GetTestSettings()).Returns(settings);
+ MockTestInfraFunctions.Setup(x => x.GetContext()).Returns(mockContext.Object);
+ mockContext.Setup(x => x.Pages).Returns(new List() { mockPage.Object });
+ mockPage.Setup(x => x.PauseAsync()).Returns(Task.CompletedTask);
+
+ MockLogger.Setup(x => x.Log(
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ (Func)It.IsAny