Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ public interface ITestInfraFunctions
/// <returns>Return value of javascript</returns>
public Task<T> RunJavascriptAsync<T>(string jsExpression);

/// <summary>
/// Runs javascript inside a named frame (bypasses cross-origin restrictions via CDP)
/// </summary>
/// <typeparam name="T">Expected return type</typeparam>
/// <param name="jsExpression">Javascript expression to run</param>
/// <param name="frameName">Name of the frame to evaluate in</param>
/// <returns>Return value of javascript</returns>
public Task<T> RunJavascriptInFrameAsync<T>(string jsExpression, string frameName);

/// <summary>
/// Triggers a click event on a control
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,14 @@ public async Task<T> RunJavascriptAsync<T>(string jsExpression)
return await Page.EvaluateAsync<T>(jsExpression);
}

public async Task<T> RunJavascriptInFrameAsync<T>(string jsExpression, string frameName)
{
ValidatePage();
_singleTestInstanceState.GetLogger().LogDebug($"Run Javascript in frame '{frameName}': " + jsExpression);
var frame = Page.Frame(frameName);
return await frame.EvaluateAsync<T>(jsExpression);
}

public async Task AddScriptContentAsync(string content)
{
ValidatePage();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
Expand Down Expand Up @@ -34,7 +34,7 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NuGet.Configuration" Version="6.11.1" />
<PackageReference Include="System.ComponentModel.Composition" Version="5.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />

<PackageReference Include="YamlDotNet" Version="16.1.3" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
Expand All @@ -44,9 +44,7 @@
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="System.Formats.Asn1" Version="8.0.1" />
<PackageReference Include="Microsoft.PowerPlatform.Dataverse.Client" Version="1.2.2">
<NoWarn>NU1701</NoWarn>
</PackageReference>
<PackageReference Include="Microsoft.PowerPlatform.Dataverse.Client" Version="1.2.2" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>

Expand Down
14 changes: 14 additions & 0 deletions src/testengine.provider.canvas/JS/PublishedAppTesting.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ 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);
childControlNames.forEach((childControlName) => {
var childControlObject = childrenControlContext[childControlName];
var childControlsList = parseControl(childControlName, childControlObject);
controls = controls.concat(childControlsList);
galleryChildNames.push(childControlName);
});
}

Expand All @@ -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 });
})

Expand Down
36 changes: 36 additions & 0 deletions src/testengine.provider.canvas/PowerAppFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,42 @@ private async Task<Dictionary<string, ControlRecordValue>> 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<string>(
$@"(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<List<string>>(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)
Expand Down