diff --git a/src/Microsoft.PowerApps.TestEngine/TestInfra/ITestInfraFunctions.cs b/src/Microsoft.PowerApps.TestEngine/TestInfra/ITestInfraFunctions.cs
index 8aa246559..1de04fc56 100644
--- a/src/Microsoft.PowerApps.TestEngine/TestInfra/ITestInfraFunctions.cs
+++ b/src/Microsoft.PowerApps.TestEngine/TestInfra/ITestInfraFunctions.cs
@@ -100,6 +100,15 @@ public interface ITestInfraFunctions
/// Return value of javascript
public Task RunJavascriptAsync(string jsExpression);
+ ///
+ /// Runs javascript inside a named frame (bypasses cross-origin restrictions via CDP)
+ ///
+ /// Expected return type
+ /// Javascript expression to run
+ /// Name of the frame to evaluate in
+ /// Return value of javascript
+ public Task RunJavascriptInFrameAsync(string jsExpression, string frameName);
+
///
/// Triggers a click event on a control
///
diff --git a/src/Microsoft.PowerApps.TestEngine/TestInfra/PlaywrightTestInfraFunctions.cs b/src/Microsoft.PowerApps.TestEngine/TestInfra/PlaywrightTestInfraFunctions.cs
index 32cedf7ca..d7d332be0 100644
--- a/src/Microsoft.PowerApps.TestEngine/TestInfra/PlaywrightTestInfraFunctions.cs
+++ b/src/Microsoft.PowerApps.TestEngine/TestInfra/PlaywrightTestInfraFunctions.cs
@@ -493,6 +493,14 @@ public async Task RunJavascriptAsync(string jsExpression)
return await Page.EvaluateAsync(jsExpression);
}
+ public async Task RunJavascriptInFrameAsync(string jsExpression, string frameName)
+ {
+ ValidatePage();
+ _singleTestInstanceState.GetLogger().LogDebug($"Run Javascript in frame '{frameName}': " + jsExpression);
+ var frame = Page.Frame(frameName);
+ return await frame.EvaluateAsync(jsExpression);
+ }
+
public async Task AddScriptContentAsync(string content)
{
ValidatePage();
diff --git a/src/PowerAppsTestEngineWrapper/PowerAppsTestEngineWrapper.csproj b/src/PowerAppsTestEngineWrapper/PowerAppsTestEngineWrapper.csproj
index 97a9e9da3..4f1c87bc9 100644
--- a/src/PowerAppsTestEngineWrapper/PowerAppsTestEngineWrapper.csproj
+++ b/src/PowerAppsTestEngineWrapper/PowerAppsTestEngineWrapper.csproj
@@ -1,7 +1,7 @@
- netstandard2.0
+ net8.0
enable
enable
True
@@ -34,7 +34,7 @@
-
+
@@ -44,9 +44,7 @@
-
- NU1701
-
+
diff --git a/src/testengine.provider.canvas/JS/PublishedAppTesting.js b/src/testengine.provider.canvas/JS/PublishedAppTesting.js
index 06177fd8b..8f9ee8b8d 100644
--- a/src/testengine.provider.canvas/JS/PublishedAppTesting.js
+++ b/src/testengine.provider.canvas/JS/PublishedAppTesting.js
@@ -5,6 +5,7 @@ function parseControl(controlName, controlObject) {
var propertiesList = [];
var properties = Object.keys(controlObject.modelProperties);
var controls = [];
+ var galleryChildNames = [];
if (controlObject.controlWidget.replicatedContextManager) {
var childrenControlContext = controlObject.controlWidget.replicatedContextManager.authoringAreaBindingContext.controlContexts;
var childControlNames = Object.keys(childrenControlContext);
@@ -12,6 +13,7 @@ function parseControl(controlName, controlObject) {
var childControlObject = childrenControlContext[childControlName];
var childControlsList = parseControl(childControlName, childControlObject);
controls = controls.concat(childControlsList);
+ galleryChildNames.push(childControlName);
});
}
@@ -32,6 +34,18 @@ function parseControl(controlName, controlObject) {
properties.forEach((propertyName) => {
var propertyType = controlObject.controlWidget.controlProperties[propertyName].propertyType;
+ // Power Apps runtime returns *[] / ![] for gallery AllItems/Items/Selected/Default
+ // regardless of the bound data source. Rebuild the type from the template child
+ // controls extracted above so that Index(Gallery.AllItems, N).ControlName.Prop works.
+ if (galleryChildNames.length > 0) {
+ var childTypeStr = galleryChildNames.map(function(n) { return n + ':v'; }).join(', ');
+ if (propertyType === '*[]' && (propertyName === 'AllItems' || propertyName === 'Items')) {
+ propertyType = '*[' + childTypeStr + ']';
+ } else if (propertyType === '![]' && (propertyName === 'Selected' || propertyName === 'Default')) {
+ propertyType = '![' + childTypeStr + ']';
+ }
+ }
+
propertiesList.push({ propertyName: propertyName, propertyType: propertyType });
})
diff --git a/src/testengine.provider.canvas/PowerAppFunctions.cs b/src/testengine.provider.canvas/PowerAppFunctions.cs
index ef719720e..c6fb90e9c 100644
--- a/src/testengine.provider.canvas/PowerAppFunctions.cs
+++ b/src/testengine.provider.canvas/PowerAppFunctions.cs
@@ -127,6 +127,42 @@ private async Task> LoadObjectModelAsyncH
if (jsObjectModel != null && jsObjectModel.Controls != null)
{
+ // Patch gallery AllItems/*Selected types: Power Apps runtime reports *[] for
+ // these regardless of the bound data source. Query child control names directly
+ // from the app frame (Playwright bypasses cross-origin restrictions via CDP).
+ foreach (var control in jsObjectModel.Controls)
+ {
+ var hasEmptyAllItems = control.Properties.Any(p => p.PropertyName == "AllItems" && p.PropertyType == "*[]");
+ if (!hasEmptyAllItems) continue;
+ try
+ {
+ var childNamesJson = await TestInfraFunctions.RunJavascriptInFrameAsync(
+ $@"(function() {{
+ try {{
+ var ctx = AppMagic.Controls.GlobalContextManager.bindingContext.controlContexts['{control.Name}'];
+ if (!ctx || !ctx.controlWidget || !ctx.controlWidget.replicatedContextManager) return '[]';
+ return JSON.stringify(Object.keys(ctx.controlWidget.replicatedContextManager.authoringAreaBindingContext.controlContexts));
+ }} catch(e) {{ return '[]'; }}
+ }})()",
+ PublishedAppIframeName);
+ var childNames = JsonConvert.DeserializeObject>(childNamesJson ?? "[]");
+ if (childNames == null || childNames.Count == 0) continue;
+ var childTypeStr = string.Join(", ", childNames.Select(n => $"{n}:v"));
+ SingleTestInstanceState.GetLogger().LogTrace($"Gallery '{control.Name}' children: {childTypeStr}");
+ foreach (var prop in control.Properties)
+ {
+ if (prop.PropertyType == "*[]" && (prop.PropertyName == "AllItems" || prop.PropertyName == "Items"))
+ prop.PropertyType = $"*[{childTypeStr}]";
+ else if (prop.PropertyType == "![]" && (prop.PropertyName == "Selected" || prop.PropertyName == "Default"))
+ prop.PropertyType = $"![{childTypeStr}]";
+ }
+ }
+ catch (Exception ex)
+ {
+ SingleTestInstanceState.GetLogger().LogTrace($"Gallery '{control.Name}' child lookup failed: {ex.Message}");
+ }
+ }
+
SingleTestInstanceState.GetLogger().LogTrace("Listing all skipped properties for each control.");
foreach (var control in jsObjectModel.Controls)