diff --git a/samples/mdagallerycontrols/MDA_GalleryControl_1_0_0_2_managed.zip b/samples/mdagallerycontrols/MDA_GalleryControl_1_0_0_2_managed.zip new file mode 100644 index 000000000..495a7b649 Binary files /dev/null and b/samples/mdagallerycontrols/MDA_GalleryControl_1_0_0_2_managed.zip differ diff --git a/samples/mdagallerycontrols/README.md b/samples/mdagallerycontrols/README.md new file mode 100644 index 000000000..39c764f7f --- /dev/null +++ b/samples/mdagallerycontrols/README.md @@ -0,0 +1,58 @@ +# Overview + +This Power Apps Test Engine sample illustrates how to assert and interact with both modern and classic gallery controls in a model-driven application form. It covers Horizontal, Vertical, and Flexible height Gallery controls. + +## Usage + +1. **Build the Test Engine Solution** + Ensure the Power Apps Test Engine solution is built and ready to be executed. + +2. **Get the URL of the Model-Driven Application Form** + Acquire the URL of the specific model-driven application form that you want to test. + +3. **Modify the verticalgallery_testPlan.fx** + Update the YAML file to assert expected values of the Horizontal, Vertical and Flexible height gallery controls. + + > **Note:** The controls are referenced using the [logical name](https://learn.microsoft.com/power-apps/developer/data-platform/entity-metadata#table-names). +4. **Update the Domain URL for Your Model-Driven Application** + + | URL Part | Description | + | ---------------------------------------------- | ------------------------------------------------------- | + | `appid=a1234567-cccc-44444-9999-a123456789123` | The unique identifier of your model-driven application. | + | `etn=` | The name of the entity being validated. | + | `id=26bafa27-ca7d-ee11-8179-0022482a91f4` | The unique identifier of the record being edited. | + | `pagetype=custom` | The type of page to open. | + | `UserAuth=storagestate` | The type of user authentication to use. | + | `UseStaticContext=True` | A flag indicating the use of a static context. | + +5. **Execute the Test for Custom Page** + Please replace the example URLs with your organization's URL. + + **Command for Modern Vertical Gallery Control:** + ```pwsh + cd bin\Debug\PowerAppsEngine + dotnet PowerAppsTestEngine.dll -i ..\..\..\samples\mdagallerycontrols\formtablecontroltestplan.fx.yaml -e 00000000-0000-0000-0000-11112223333 -t 11112222-3333-4444-5555-666677778888 -u browser -provider mda -d "https://orgfc708206.crm.dynamics.com/main.aspx?appid=65cdcc8e-54bc-ef11-a72f-000d3a12b0cb&pagetype=custom&name=cr693_mdagallerycontrol_846d9" + ``` + + **Command for Modern Horizontal Gallery Control:** + ```pwsh + cd bin\Debug\PowerAppsEngine + dotnet PowerAppsTestEngine.dll -i ..\..\..\samples\mdagallerycontrols\horizontalgallery_testPlan.fx.yaml -e 00000000-0000-0000-0000-11112223333 -t 11112222-3333-4444-5555-666677778888 -u browser -provider mda -d "https://orgfc708206.crm.dynamics.com/main.aspx?appid=65cdcc8e-54bc-ef11-a72f-000d3a12b0cb&pagetype=custom&name=cr7d6_productdetails_6f49a" + ``` + + **Command for Modern Flexible Height Gallery Control:** + ```pwsh + cd bin\Debug\PowerAppsEngine + dotnet PowerAppsTestEngine.dll -i ..\..\..\samples\mdagallerycontrols\flexiblegallery_testPlan.fx.yaml -e 00000000-0000-0000-0000-11112223333 -t 11112222-3333-4444-5555-666677778888 -u browser -provider mda -d "https://orgfc708206.crm.dynamics.com/main.aspx?appid=65cdcc8e-54bc-ef11-a72f-000d3a12b0cb&pagetype=custom&name=cr693_flexiblegallery_f5374" + ``` + + **Command for Classic - Blank Horizontal and Flexible Height Gallery Control:** + ```pwsh + cd bin\Debug\PowerAppsEngine + dotnet PowerAppsTestEngine.dll -i ..\..\..\samples\mdagallerycontrols\blankgallery_testPlan.fx.yaml -e 00000000-0000-0000-0000-11112223333 -t 11112222-3333-4444-5555-666677778888 -u browser -provider mda -d "https://orgfc708206.crm.dynamics.com/main.aspx?appid=65cdcc8e-54bc-ef11-a72f-000d3a12b0cb&pagetype=custom&name=cr7d6_blankhorizontalgallery_311c9" + ``` + + **Command for Classic - Blank Vertical Gallery Control:** + ```pwsh + cd bin\Debug\PowerAppsEngine + dotnet PowerAppsTestEngine.dll -i ..\..\..\samples\mdagallerycontrols\blankverticalgallery_testPlan.fx.yaml -e 00000000-0000-0000-0000-11112223333 -t 11112222-3333-4444-5555-666677778888 -u browser -provider mda -d "https://orgfc708206.crm.dynamics.com/main.aspx?appid=65cdcc8e-54bc-ef11-a72f-000d3a12b0cb&pagetype=custom&name=cr7d6_blankverticalgallery_a4937" diff --git a/samples/mdagallerycontrols/RunTests.ps1 b/samples/mdagallerycontrols/RunTests.ps1 new file mode 100644 index 000000000..9305eaa45 --- /dev/null +++ b/samples/mdagallerycontrols/RunTests.ps1 @@ -0,0 +1,33 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# Get current directory so we can reset back to it after running the tests +$currentDirectory = Get-Location + +$config = Get-Content -Path .\config.json -Raw | ConvertFrom-Json +$tenantId = $config.tenantId +$environmentId = $config.environmentId +$user1Email = $config.user1Email + +if ([string]::IsNullOrEmpty($environmentId)) { + Write-Error "Environment not configured. Please update config.json" + return +} + +# Build the latest debug version of Test Engine from source +Set-Location ..\..\src +dotnet build + +if ($config.installPlaywright) { + Start-Process -FilePath "pwsh" -ArgumentList "-Command `"..\bin\Debug\PowerAppsTestEngine\playwright.ps1 install`"" -Wait +} else { + Write-Host "Skipped playwright install" +} + +Set-Location ..\bin\Debug\PowerAppsTestEngine +$env:user1Email = $user1Email +# Run the tests for each user in the configuration file. +dotnet PowerAppsTestEngine.dll -u "storagestate" -p "mda" -a "none" -i "$currentDirectory\mdagallerycontrols_testPlan.fx.yaml" -t $tenantId -e $environmentId + +# Reset the location back to the original directory. +Set-Location $currentDirectory \ No newline at end of file diff --git a/samples/mdagallerycontrols/blankgallery_testPlan.fx.yaml b/samples/mdagallerycontrols/blankgallery_testPlan.fx.yaml new file mode 100644 index 000000000..015601c3a --- /dev/null +++ b/samples/mdagallerycontrols/blankgallery_testPlan.fx.yaml @@ -0,0 +1,105 @@ +# yaml-embedded-languages: powerfx +testSuite: + testSuiteName: MDA Custom Page tests - Gallery Controls + testSuiteDescription: Verify test cases for MDA Gallery Controls + persona: User1 + appLogicalName: NotNeeded + testCases: + - testCaseName: Test Gallery Control - Validate Selection by Row Number + testCaseDescription: Ensure that selecting an item in the Gallery control by specifying the row number updates the selected record accurately. + testSteps: | + Select(Gallery1, 3); + Assert(Index(Gallery1.AllItems, 3).Label1.Text = "Action Figure", "Verify the selected record's Title value is 'Action Figure'"); + Assert(Index(Gallery1.AllItems, 3).Label3.Text = "24.99", "Verify the selected record's Price value is '24.99'"); + Select(Gallery2, 3); + Assert(Index(Gallery2.AllItems, 3).Label6.Text = "Action Figure", "Verify the selected record's Title value is 'Action Figure'"); + Assert(Index(Gallery2.AllItems, 3).Label8.Text = "24.99", "Verify the selected record's Price value is '24.99'"); + + - testCaseName: Test Gallery Control - Validate Selection via Icon + testCaseDescription: Ensure that selecting an item in the Gallery control using an icon updates the selected record accurately. + testSteps: | + Select(Gallery1, 5, Icon1); + Assert(Index(Gallery1.AllItems, 5).Label1.Text = "Laptop", "Verify the selected record's Title value is 'Laptop'"); + Assert(Index(Gallery1.AllItems, 5).Label3.Text = "999.99", "Verify the selected record's Price value is '999.99'"); + Select(Gallery2, 5, Icon2); + Assert(Index(Gallery2.AllItems, 5).Label6.Text = "Laptop", "Verify the selected record's Title value is 'Laptop'"); + Assert(Index(Gallery2.AllItems, 5).Label8.Text = "999.99", "Verify the selected record's Price value is '999.99'"); + + - testCaseName: Populate gallery with data + testCaseDescription: Populate gallery with data and validate with count + testSteps: | + = + Assert(CountRows(Gallery1.Items) = 10, "Checking the Items count of the Horizontal Gallery control"); + Assert(CountRows(Gallery2.Items) = 10, "Checking the Items count of the Flexible Gallery control"); + + - testCaseName: Test Gallery Control - Search and Validate Result Count + testCaseDescription: Verify that searching the Gallery control using input from the text box updates the displayed items correctly. + testSteps: | + SetProperty(txtSearchInput.Value, "Jeans"); + Select(icnSearch); + Wait(Gallery1,"AllItemsCount", 1); + Wait(Gallery2,"AllItemsCount", 1); + Assert(Gallery1.AllItemsCount = 1, "Checking if the Horizontal Gallery displays the correct number of items after search"); + Assert(Gallery2.AllItemsCount = 1, "Checking if the Flexible Gallery displays the correct number of items after search"); + SetProperty(txtSearchInput.Value, ""); + SetProperty(txtSearchInput.Value, "e"); + Select(icnSearch); + Wait(Gallery1,"AllItemsCount", 7); + Wait(Gallery2,"AllItemsCount", 7); + Assert(Gallery1.AllItemsCount = 7, "Checking if the Horizontal Gallery displays the correct number of items after search"); + Assert(Gallery2.AllItemsCount = 7, "Checking if the Flexible Gallery displays the correct number of items after search"); + + + - testCaseName: Test Gallery Control - Validate ShowNavigation Property + testCaseDescription: Verify that the ShowNavigation property of the Gallery control is set and validated correctly. + testSteps: | + SetProperty(txtSearchInput.Value, ""); + SetProperty(Gallery1.ShowNavigation, false); + SetProperty(Gallery2.ShowNavigation, false); + Assert(Gallery1.ShowNavigation = false, "Verify the ShowNavigation property is set to false"); + Assert(Gallery2.ShowNavigation = false, "Verify the ShowNavigation property is set to false"); + SetProperty(Gallery1.ShowNavigation, true); + SetProperty(Gallery2.ShowNavigation, true); + Assert(Gallery1.ShowNavigation = true, "Verify the ShowNavigation property is set to true"); + Assert(Gallery2.ShowNavigation = true, "Verify the ShowNavigation property is set to true"); + + - testCaseName: Test Gallery Control - Validate Selectable Property + testCaseDescription: Verify that the Selectable property of the Gallery control is set and validated correctly. + testSteps: | + SetProperty(Gallery1.Selectable, true); + SetProperty(Gallery2.Selectable, true); + Assert(Gallery1.Selectable = true, "Verify the Selectable property is set to true"); + Assert(Gallery2.Selectable = true, "Verify the Selectable property is set to true"); + SetProperty(Gallery1.Selectable, false); + SetProperty(Gallery2.Selectable, false); + Assert(Gallery1.Selectable = false, "Verify the Selectable property is set to false"); + Assert(Gallery2.Selectable = false, "Verify the Selectable property is set to false"); + + - testCaseName: Test Gallery Control - Filter and Validate Single Item + testCaseDescription: Ensure that filtering the Gallery control by a specific title updates the displayed items correctly and validates the item count. + testSteps: | + SetProperty(Gallery1.Items, Filter(Gallery1.Items, 'cr7d6_title'="Coffee Maker")); + SetProperty(Gallery2.Items, Filter(Gallery2.Items, 'cr7d6_title'="Coffee Maker")); + Select(Gallery1, 1); + Select(Gallery2, 1); + Assert(Label1.Text = "Coffee Maker", "Checking the Items count of the Gallery control"); + Assert(Label6.Text = "Coffee Maker", "Checking the Items count of the Gallery control"); + + # Both examples provided below are effective. + # SetProperty(Gallery1.Items, Search(Gallery1.Items, "Action Figure", 'cr7d6_title')); + # SetProperty(Gallery1.Items, Filter(Gallery1.Items, Title6.Text="SmartPhone")); +testSettings: + headless: false + locale: "en-US" + recordVideo: true + extensionModules: + enable: true + browserConfigurations: + - browser: Chromium + channel: msedge + +environmentVariables: + users: + - personaName: User1 + emailKey: user1Email + passwordKey: NotNeeded diff --git a/samples/mdagallerycontrols/blankverticalgallery_testPlan.fx.yaml b/samples/mdagallerycontrols/blankverticalgallery_testPlan.fx.yaml new file mode 100644 index 000000000..221ec2910 --- /dev/null +++ b/samples/mdagallerycontrols/blankverticalgallery_testPlan.fx.yaml @@ -0,0 +1,111 @@ +# yaml-embedded-languages: powerfx +testSuite: + testSuiteName: MDA Custom Page tests - Gallery Controls + testSuiteDescription: Verify test cases for MDA Gallery Controls + persona: User1 + appLogicalName: NotNeeded + testCases: + - testCaseName: Test Gallery Control - Validate Selection by Row Number + testCaseDescription: Ensure that selecting an item in the Gallery control by specifying the row number updates the selected record accurately. + testSteps: | + Select(Gallery1, 3); + Assert(Index(Gallery1.AllItems, 3).TextInputCanvas2.Value = "Action Figure", "Verify the selected record's Title value is 'Action Figure'"); + Assert(Index(Gallery1.AllItems, 3).TextInputCanvas4.Value = "24.99", "Verify the selected record's Price value is '24.99'"); + + - testCaseName: Test Gallery Control - Validate Selection via Icon + testCaseDescription: Ensure that selecting an item in the Gallery control using an icon updates the selected record accurately. + testSteps: | + Select(Gallery1, 5, Icon1); + Assert(Index(Gallery1.AllItems, 5).TextInputCanvas2.Value = "Laptop", "Verify the selected record's Title value is 'Laptop'"); + Assert(Index(Gallery1.AllItems, 5).TextInputCanvas4.Value = "999.99", "Verify the selected record's Price value is '999.99'"); + Select(Gallery1, 5, Icon2); + + - testCaseName: Test Gallery Control - Select the row and edit the fieds value + testCaseDescription: Ensure that selecting an item in the Gallery control using an icon updates the selected record accurately. + testSteps: | + Select(Gallery1, 5, Icon1); + SetProperty(Index(Gallery1.AllItems, 5).TextInputCanvas2.Value, "Laptop1"); + SetProperty(Index(Gallery1.AllItems, 5).TextInputCanvas4.Value, "1000.00"); + SetProperty(Index(Gallery1.AllItems, 5).ComboboxCanvas1.DefaultSelectedItems, Table({Id:472770002,Value:"Home"})); + Assert(Index(Gallery1.AllItems, 5).TextInputCanvas2.Value = "Laptop1", "Verify the selected record's Title value is 'Laptop1'"); + Assert(Index(Gallery1.AllItems, 5).TextInputCanvas4.Value = "1000.00", "Verify the selected record's Price value is '1000.00'"); + Select(Gallery1, 5, Icon2); + + - testCaseName: Test Gallery Control - Validate Selection and Assertions + testCaseDescription: Ensure that selecting an item in the Gallery control using an icon updates the selected record accurately and validates its values. + testSteps: | + Select(Gallery1, 5, Icon1); + Assert((Gallery1.Selected).TextInputCanvas2.Value = "Laptop1", "Verify the selected record's Title value is 'Laptop1'"); + Assert((Gallery1.Selected).TextInputCanvas4.Value = "1000.00", "Verify the selected record's Price value is '1000.00'"); + Select(Gallery1, 5, Icon2); + + - testCaseName: Populate gallery with data + testCaseDescription: Populate gallery with data and validate with count + testSteps: | + = + Assert(CountRows(Gallery1.Items) = 10, "Checking the Items count of the Gallery control"); + Assert(CountRows(Gallery1.AllItems) = 10, "Verify the AllItemsCount property returns the correct number of items"); + + - testCaseName: Test Gallery Control - Search and Validate Result Count + testCaseDescription: Verify that searching the Gallery control using input from the text box updates the displayed items correctly. + testSteps: | + SetProperty(txtSearchInput.Value, "Jeans"); + Select(icnSearch); + Wait(Gallery1,"AllItemsCount", 1); + Assert(Gallery1.AllItemsCount = 1, "Checking if the Gallery displays the correct number of items after search"); + SetProperty(txtSearchInput.Value, ""); + SetProperty(txtSearchInput.Value, "e"); + Select(icnSearch); + Wait(Gallery1,"AllItemsCount", 7); + Assert(Gallery1.AllItemsCount = 7, "Checking if the Gallery displays the correct number of items after search"); + + + - testCaseName: Test Gallery Control - Validate ShowNavigation Property + testCaseDescription: Verify that the ShowNavigation property of the Gallery control is set and validated correctly. + testSteps: | + SetProperty(txtSearchInput.Value, ""); + SetProperty(Gallery1.ShowNavigation, false); + Assert(Gallery1.ShowNavigation = false, "Verify the ShowNavigation property is set to false"); + SetProperty(Gallery1.ShowNavigation, true); + Assert(Gallery1.ShowNavigation = true, "Verify the ShowNavigation property is set to true"); + + - testCaseName: Test Gallery Control - Validate Selectable Property + testCaseDescription: Verify that the Selectable property of the Gallery control is set and validated correctly. + testSteps: | + SetProperty(Gallery1.Selectable, true); + Assert(Gallery1.Selectable = true, "Verify the Selectable property is set to true"); + SetProperty(Gallery1.Selectable, false); + Assert(Gallery1.Selectable = false, "Verify the Selectable property is set to false"); + + - testCaseName: Test Gallery Control - Validate Default Property + testCaseDescription: Verify that the Default property of the Gallery control is set and validated correctly. + testSteps: | + SetProperty(Gallery1.Default, Last(Gallery1.Items)); + Assert((Gallery1.Selected).TextInputCanvas2.Value = "Cookbook", "Verify the Default property is set to the first item"); + + - testCaseName: Test Gallery Control - Filter and Validate Single Item + testCaseDescription: Ensure that filtering the Gallery control by a specific title updates the displayed items correctly and validates the item count. + testSteps: | + SetProperty(Gallery1.Items, Filter(Gallery1.Items, 'cr7d6_title'="Coffee Maker")); + Select(Gallery1, 1); + Assert(TextInputCanvas2.Value = "Coffee Maker", "Checking the Items count of the Gallery control"); + + # Both examples provided below are effective. + # SetProperty(Gallery1.Items, Search(Gallery1.Items, "Action Figure", 'cr7d6_title')); + # SetProperty(Gallery1.Items, Filter(Gallery1.Items, Title6.Text="SmartPhone")); + +testSettings: + headless: false + locale: "en-US" + recordVideo: true + extensionModules: + enable: true + browserConfigurations: + - browser: Chromium + channel: msedge + +environmentVariables: + users: + - personaName: User1 + emailKey: user1Email + passwordKey: NotNeeded diff --git a/samples/mdagallerycontrols/flexiblegallery_testPlan.fx.yaml b/samples/mdagallerycontrols/flexiblegallery_testPlan.fx.yaml new file mode 100644 index 000000000..03a6765ab --- /dev/null +++ b/samples/mdagallerycontrols/flexiblegallery_testPlan.fx.yaml @@ -0,0 +1,84 @@ +# yaml-embedded-languages: powerfx +testSuite: + testSuiteName: MDA Custom Page tests - Gallery Controls + testSuiteDescription: Verify test cases for MDA Gallery Controls + persona: User1 + appLogicalName: NotNeeded + testCases: + - testCaseName: Test Gallery Control - Validate Selection by Row Number + testCaseDescription: Ensure that selecting an item in the Gallery control by specifying the row number updates the selected record accurately. + testSteps: | + Select(Gallery1, 3); + Assert(Index(Gallery1.AllItems, 3).Title2.Text = "Action Figure", "Verify the selected record's Title value is 'Action Figure'"); + Assert(Index(Gallery1.AllItems, 3).Price2.Text = "24.99", "Verify the selected record's Price value is '24.99'"); + + - testCaseName: Test Gallery Control - Validate Selection via Icon + testCaseDescription: Ensure that selecting an item in the Gallery control using an icon updates the selected record accurately. + testSteps: | + Select(Gallery1, 5, Icon1); + Assert(Index(Gallery1.AllItems, 5).Title2.Text = "Laptop", "Verify the selected record's Title value is 'Laptop'"); + Assert(Index(Gallery1.AllItems, 5).Price2.Text = "999.99", "Verify the selected record's Price value is '999.99'"); + + - testCaseName: Populate gallery with data + testCaseDescription: Populate gallery with data and validate with count + testSteps: | + = + Assert(CountRows(Gallery1.Items) = 10, "Checking the Items count of the Gallery control"); + + - testCaseName: Test Gallery Control - Search and Validate Result Count + testCaseDescription: Verify that searching the Gallery control using input from the text box updates the displayed items correctly. + testSteps: | + SetProperty(txtSearchInput.Value, "Jeans"); + Select(icnSearch); + Wait(Gallery1,"AllItemsCount", 1); + Assert(Gallery1.AllItemsCount = 1, "Checking if the Gallery displays the correct number of items after search"); + SetProperty(txtSearchInput.Value, ""); + SetProperty(txtSearchInput.Value, "e"); + Select(icnSearch); + Wait(Gallery1,"AllItemsCount", 7); + Assert(Gallery1.AllItemsCount = 7, "Checking if the Gallery displays the correct number of items after search"); + + + - testCaseName: Test Gallery Control - Validate ShowNavigation Property + testCaseDescription: Verify that the ShowNavigation property of the Gallery control is set and validated correctly. + testSteps: | + SetProperty(txtSearchInput.Value, ""); + SetProperty(Gallery1.ShowNavigation, false); + Assert(Gallery1.ShowNavigation = false, "Verify the ShowNavigation property is set to false"); + SetProperty(Gallery1.ShowNavigation, true); + Assert(Gallery1.ShowNavigation = true, "Verify the ShowNavigation property is set to true"); + + - testCaseName: Test Gallery Control - Validate Selectable Property + testCaseDescription: Verify that the Selectable property of the Gallery control is set and validated correctly. + testSteps: | + SetProperty(Gallery1.Selectable, true); + Assert(Gallery1.Selectable = true, "Verify the Selectable property is set to true"); + SetProperty(Gallery1.Selectable, false); + Assert(Gallery1.Selectable = false, "Verify the Selectable property is set to false"); + + - testCaseName: Test Gallery Control - Filter and Validate Single Item + testCaseDescription: Ensure that filtering the Gallery control by a specific title updates the displayed items correctly and validates the item count. + testSteps: | + SetProperty(Gallery1.Items, Filter(Gallery1.Items, 'cr7d6_title'="Coffee Maker")); + Select(Gallery1, 1); + Assert(Title2.Text = "Coffee Maker", "Checking the Items count of the Gallery control"); + + # Both examples provided below are effective. + # SetProperty(Gallery1.Items, Search(Gallery1.Items, "Action Figure", 'cr7d6_title')); + # SetProperty(Gallery1.Items, Filter(Gallery1.Items, Title6.Text="SmartPhone")); + +testSettings: + headless: false + locale: "en-US" + recordVideo: true + extensionModules: + enable: true + browserConfigurations: + - browser: Chromium + channel: msedge + +environmentVariables: + users: + - personaName: User1 + emailKey: user1Email + passwordKey: NotNeeded diff --git a/samples/mdagallerycontrols/horizontalgallery_testPlan.fx.yaml b/samples/mdagallerycontrols/horizontalgallery_testPlan.fx.yaml new file mode 100644 index 000000000..3c070d244 --- /dev/null +++ b/samples/mdagallerycontrols/horizontalgallery_testPlan.fx.yaml @@ -0,0 +1,83 @@ +# yaml-embedded-languages: powerfx +testSuite: + testSuiteName: MDA Custom Page tests - Gallery Controls + testSuiteDescription: Verify test cases for MDA Gallery Controls + persona: User1 + appLogicalName: NotNeeded + testCases: + - testCaseName: Test Gallery Control - Validate Selection by Row Number + testCaseDescription: Ensure that selecting an item in the Gallery control by specifying the row number updates the selected record accurately. + testSteps: | + Select(Gallery1, 3); + Assert(Index(Gallery1.AllItems, 3).Title2.Text = "Action Figure", "Verify the selected record's Title value is 'Action Figure'"); + Assert(Index(Gallery1.AllItems, 3).Subtitle2.Text = "24.99", "Verify the selected record's Price value is '24.99'"); + + - testCaseName: Test Gallery Control - Validate Selection via Icon + testCaseDescription: Ensure that selecting an item in the Gallery control using an icon updates the selected record accurately. + testSteps: | + Select(Gallery1, 5, Icon1); + Assert(Index(Gallery1.AllItems, 5).Title2.Text = "Laptop", "Verify the selected record's Title value is 'Laptop'"); + Assert(Index(Gallery1.AllItems, 5).Subtitle2.Text = "999.99", "Verify the selected record's Price value is '999.99'"); + + - testCaseName: Populate gallery with data + testCaseDescription: Populate gallery with data and validate with count + testSteps: | + = + Assert(CountRows(Gallery1.Items) = 10, "Checking the Items count of the Gallery control"); + + - testCaseName: Test Gallery Control - Search and Validate Result Count + testCaseDescription: Verify that searching the Gallery control using input from the text box updates the displayed items correctly. + testSteps: | + SetProperty(txtSearchInput.Value, "Jeans"); + Select(icnSearch); + Wait(Gallery1,"AllItemsCount", 1); + Assert(Gallery1.AllItemsCount = 1, "Checking if the Gallery displays the correct number of items after search"); + SetProperty(txtSearchInput.Value, ""); + SetProperty(txtSearchInput.Value, "e"); + Select(icnSearch); + Wait(Gallery1,"AllItemsCount", 7); + Assert(Gallery1.AllItemsCount = 7, "Checking if the Gallery displays the correct number of items after search"); + + + - testCaseName: Test Gallery Control - Validate ShowNavigation Property + testCaseDescription: Verify that the ShowNavigation property of the Gallery control is set and validated correctly. + testSteps: | + SetProperty(txtSearchInput.Value, ""); + SetProperty(Gallery1.ShowNavigation, false); + Assert(Gallery1.ShowNavigation = false, "Verify the ShowNavigation property is set to false"); + SetProperty(Gallery1.ShowNavigation, true); + Assert(Gallery1.ShowNavigation = true, "Verify the ShowNavigation property is set to true"); + + - testCaseName: Test Gallery Control - Validate Selectable Property + testCaseDescription: Verify that the Selectable property of the Gallery control is set and validated correctly. + testSteps: | + SetProperty(Gallery1.Selectable, true); + Assert(Gallery1.Selectable = true, "Verify the Selectable property is set to true"); + SetProperty(Gallery1.Selectable, false); + Assert(Gallery1.Selectable = false, "Verify the Selectable property is set to false"); + + - testCaseName: Test Gallery Control - Filter and Validate Single Item + testCaseDescription: Ensure that filtering the Gallery control by a specific title updates the displayed items correctly and validates the item count. + testSteps: | + SetProperty(Gallery1.Items, Filter(Gallery1.Items, 'cr7d6_title'="Coffee Maker")); + Select(Gallery1, 1); + Assert(Title2.Text = "Coffee Maker", "Checking the Items count of the Gallery control"); + + # Both examples provided below are effective. + # SetProperty(Gallery1.Items, Search(Gallery1.Items, "Action Figure", 'cr7d6_title')); + # SetProperty(Gallery1.Items, Filter(Gallery1.Items, Title6.Text="SmartPhone")); +testSettings: + headless: false + locale: "en-US" + recordVideo: true + extensionModules: + enable: true + browserConfigurations: + - browser: Chromium + channel: msedge + +environmentVariables: + users: + - personaName: User1 + emailKey: user1Email + passwordKey: NotNeeded diff --git a/samples/mdagallerycontrols/verticalgallery_testPlan.fx.yaml b/samples/mdagallerycontrols/verticalgallery_testPlan.fx.yaml new file mode 100644 index 000000000..06207ab90 --- /dev/null +++ b/samples/mdagallerycontrols/verticalgallery_testPlan.fx.yaml @@ -0,0 +1,104 @@ +# yaml-embedded-languages: powerfx +testSuite: + testSuiteName: MDA Custom Page tests - Gallery Controls + testSuiteDescription: Verify test cases for MDA Gallery Controls + persona: User1 + appLogicalName: NotNeeded + testCases: + - testCaseName: Test Gallery Control - Validate Selection by Row Number + testCaseDescription: Ensure that selecting an item in the Gallery control by specifying the row number updates the selected record accurately. + testSteps: | + Select(Gallery1, 3); + Assert(DataCardValue9.Value = "Action Figure", "Verify the selected record's Title value is 'Action Figure'"); + Assert(DataCardValue6.Value = "24.99", "Verify the selected record's Price value is '24.99'"); + + - testCaseName: Test Gallery Control - Validate Selection via Icon + testCaseDescription: Ensure that selecting an item in the Gallery control using an icon updates the selected record accurately. + testSteps: | + Select(Gallery1, 5, Icon1); + Assert(DataCardValue9.Value = "Laptop", "Verify the selected record's Title value is 'Laptop'"); + Assert(DataCardValue6.Value = "999.99", "Verify the selected record's Price value is '999.99'"); + + - testCaseName: Populate gallery with data + testCaseDescription: Populate gallery with data and validate with count + testSteps: | + = + Assert(CountRows(Gallery1.Items) = 10, "Checking the Items count of the Gallery control"); + + - testCaseName: Test Gallery Control - Search and Validate Result Count + testCaseDescription: Verify that searching the Gallery control using input from the text box updates the displayed items correctly. + testSteps: | + SetProperty(txtSearchInput.Value, "Jeans"); + Select(icnSearch); + Wait(Gallery1,"AllItemsCount", 1); + Assert(Gallery1.AllItemsCount = 1, "Checking if the Gallery displays the correct number of items after search"); + SetProperty(txtSearchInput.Value, ""); + SetProperty(txtSearchInput.Value, "e"); + Select(icnSearch); + Wait(Gallery1,"AllItemsCount", 7); + Assert(Gallery1.AllItemsCount = 7, "Checking if the Gallery displays the correct number of items after search"); + + - testCaseName: Test Gallery Control - Search, Update Field Value, and Validate Result + testCaseDescription: Verify that searching the Gallery control using input from the text box updates the displayed items correctly, allows updating a field value, and validates the updated result. + testSteps: | + SetProperty(txtSearchInput.Value, ""); + Wait(Gallery1,"AllItemsCount", 10); + SetProperty(txtSearchInput.Value, "Puzzle"); + Select(icnSearch); + Wait(Gallery1,"AllItemsCount", 1); + Select(Gallery1, 1); + Wait(DataCardValue9,"Value", "Puzzle"); + SetProperty(DataCardValue6.Value, "20.00"); + Select(btnSubmit); + SetProperty(txtSearchInput.Value, ""); + SetProperty(txtSearchInput.Value, "Puzzle"); + Select(icnSearch); + Wait(Gallery1,"AllItemsCount", 1); + Select(Gallery1, 1); + Wait(DataCardValue9,"Value", "Puzzle"); + Assert(DataCardValue6.Value = "20.00", "Checking if the Gallery displays the correct number of items after search"); + SetProperty(txtSearchInput.Value, ""); + Select(icnSearch); + + - testCaseName: Test Gallery Control - Validate ShowNavigation Property + testCaseDescription: Verify that the ShowNavigation property of the Gallery control is set and validated correctly. + testSteps: | + SetProperty(Gallery1.ShowNavigation, false); + Assert(Gallery1.ShowNavigation = false, "Verify the ShowNavigation property is set to false"); + SetProperty(Gallery1.ShowNavigation, true); + Assert(Gallery1.ShowNavigation = true, "Verify the ShowNavigation property is set to true"); + + - testCaseName: Test Gallery Control - Validate Selectable Property + testCaseDescription: Verify that the Selectable property of the Gallery control is set and validated correctly. + testSteps: | + SetProperty(Gallery1.Selectable, true); + Assert(Gallery1.Selectable = true, "Verify the Selectable property is set to true"); + SetProperty(Gallery1.Selectable, false); + Assert(Gallery1.Selectable = false, "Verify the Selectable property is set to false"); + + - testCaseName: Test Gallery Control - Filter and Validate Single Item + testCaseDescription: Ensure that filtering the Gallery control by a specific title updates the displayed items correctly and validates the item count. + testSteps: | + SetProperty(Gallery1.Items, Filter(Gallery1.Items, 'cr7d6_title'="Coffee Maker")); + Select(Gallery1, 1); + Assert(Title6.Text = "Coffee Maker", "Checking the Items count of the Gallery control"); + + # Both examples provided below are effective. + # SetProperty(Gallery1.Items, Search(Gallery1.Items, "Action Figure", 'cr7d6_title')); + # SetProperty(Gallery1.Items, Filter(Gallery1.Items, Title6.Text="SmartPhone")); + +testSettings: + headless: false + locale: "en-US" + recordVideo: true + extensionModules: + enable: true + browserConfigurations: + - browser: Chromium + channel: msedge + +environmentVariables: + users: + - personaName: User1 + emailKey: user1Email + passwordKey: NotNeeded diff --git a/src/Microsoft.PowerApps.TestEngine.Tests/Provider/PowerFXModel/ControlRecordValueTests.cs b/src/Microsoft.PowerApps.TestEngine.Tests/Provider/PowerFXModel/ControlRecordValueTests.cs index 8b07131ef..01e8d05df 100644 --- a/src/Microsoft.PowerApps.TestEngine.Tests/Provider/PowerFXModel/ControlRecordValueTests.cs +++ b/src/Microsoft.PowerApps.TestEngine.Tests/Provider/PowerFXModel/ControlRecordValueTests.cs @@ -116,9 +116,9 @@ public void GalleryControlRecordValueTest() Assert.Null(labelRecordValue.GetItemPath().Index); Assert.Null(labelRecordValue.GetItemPath().PropertyName); Assert.NotNull(labelRecordValue.GetItemPath().ParentControl); - Assert.Equal(galleryName, labelRecordValue.GetItemPath().ParentControl.ParentControl.ControlName); - Assert.Equal(i, labelRecordValue.GetItemPath().ParentControl.ParentControl.Index); - Assert.Equal(allItemsName, labelRecordValue.GetItemPath().ParentControl.ParentControl.PropertyName); + Assert.Equal(galleryName, labelRecordValue.GetItemPath().ParentControl.ControlName); + Assert.Equal(i, labelRecordValue.GetItemPath().ParentControl.Index); + Assert.Equal(allItemsName, labelRecordValue.GetItemPath().ParentControl.PropertyName); // Index(Gallery1.AllItems, i).Label1.Text Assert.Equal(labelText, (labelRecordValue.GetField("Text") as StringValue).Value); diff --git a/src/Microsoft.PowerApps.TestEngine/Providers/PowerFxModel/ControlRecordValue.cs b/src/Microsoft.PowerApps.TestEngine/Providers/PowerFxModel/ControlRecordValue.cs index 265888513..721a435f6 100644 --- a/src/Microsoft.PowerApps.TestEngine/Providers/PowerFxModel/ControlRecordValue.cs +++ b/src/Microsoft.PowerApps.TestEngine/Providers/PowerFxModel/ControlRecordValue.cs @@ -88,7 +88,7 @@ protected override bool TryGetField(FormulaType fieldType, string fieldName, out else if (fieldType is RecordType) { var recordType = fieldType as RecordType; - if (string.IsNullOrEmpty(_name)) + if (_parentItemPath != null) { // We reach here if we are referencing a child item in a Gallery. Eg. Index(Gallery1.AllItems).Label1 (fieldName = Label1) result = new ControlRecordValue(recordType, _testWebProvider, fieldName, _parentItemPath); diff --git a/src/Microsoft.PowerApps.TestEngine/Providers/PowerFxModel/MDATypeMapping.cs b/src/Microsoft.PowerApps.TestEngine/Providers/PowerFxModel/MDATypeMapping.cs index 27418f271..f06911b8e 100644 --- a/src/Microsoft.PowerApps.TestEngine/Providers/PowerFxModel/MDATypeMapping.cs +++ b/src/Microsoft.PowerApps.TestEngine/Providers/PowerFxModel/MDATypeMapping.cs @@ -31,8 +31,8 @@ public MDATypeMapping() typeMappings.Add("Z", FormulaType.DateTimeNoTimeZone); typeMappings.Add("g", FormulaType.Guid); typeMappings.Add("m", FormulaType.Decimal); - typeMappings.Add("v", FormulaType.UntypedObject); typeMappings.Add("i", FormulaType.String); + typeMappings.Add("$", FormulaType.Decimal); } /// @@ -99,10 +99,11 @@ public bool TryGetType(string typeString, out FormulaType formulaType) // Either Table value - Example: *[Gallery2:v, Icon2:v, Label4:v] // Or Record value - Example: ![Gallery2:v, Icon2:v, Label4:v] var subTypes = GetSubTypes(typeString); + FormulaType subFormulaType; foreach (var subType in subTypes) { - if (TryGetType(subType.PropertyType, out var subFormulaType)) + if (TryGetType(subType.PropertyName, out subFormulaType) || TryGetType(subType.PropertyType, out subFormulaType)) { recordType = recordType.Add(new NamedFormulaType(subType.PropertyName, subFormulaType)); } diff --git a/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs b/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs index f6586c78b..11472d285 100644 --- a/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs +++ b/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs @@ -8,6 +8,7 @@ using System.Runtime.CompilerServices; using System.Security; using System.Security.Cryptography; +using System.Text.Json.Nodes; using Microsoft.Extensions.Logging; using Microsoft.Playwright; using Microsoft.PowerApps.TestEngine.Config; @@ -182,7 +183,7 @@ public string CheckTestEngineObject // Return the value return (T)(object)("{PropertyValue: " + value.ToString() + "}"); } - } + } } if (itemPath.PropertyName.ToLower() == "text" && (await TestInfraFunctions.RunJavascriptAsync("PowerAppsTestEngine.pageType()"))?.ToString() == "entityrecord") @@ -215,6 +216,8 @@ public string CheckTestEngineObject case "checked": case "autoplay": case "showtitle": + case "shownavigation": + case "selectable": return (T)(object)("{PropertyValue: " + value.ToString().ToLower() + "}"); default: switch (value.GetType().ToString()) @@ -314,6 +317,8 @@ private async Task> LoadObjectModelAsyncH SingleTestInstanceState.GetLogger().LogTrace(skipMessage); } + TypeMapping.AddMapping(control.Name, controlType); + var controlValue = new ControlRecordValue(controlType, this, control.Name); controlDictionary.Add(control.Name, controlValue); @@ -508,7 +513,7 @@ public async Task SetPropertyDateAsync(ItemPath itemPath, DateValue value) throw; } } - + public async Task SetPropertyRecordAsync(ItemPath itemPath, RecordValue value) { try @@ -550,6 +555,7 @@ private string FormatValue(FormulaValue value) NumberValue numberValue => numberValue.Value.ToString(), DecimalValue decimalValue => decimalValue.Value.ToString(), BooleanValue booleanValue => booleanValue.Value.ToString().ToLower(), + GuidValue guidValue => $"\"{guidValue.Value}\"", // Assume all dates should be in UTC DateValue dateValue => $"\"{dateValue.GetConvertedValue(TimeZoneInfo.Utc).ToString("o")}\"", // ISO 8601 format DateTimeValue dateTimeValue => $"\"{dateTimeValue.GetConvertedValue(TimeZoneInfo.Utc).ToString("o")}\"", // ISO 8601 format diff --git a/src/testengine.provider.mda/PowerAppsTestEngineMDACustom.js b/src/testengine.provider.mda/PowerAppsTestEngineMDACustom.js index aa07faba5..249e8d3e4 100644 --- a/src/testengine.provider.mda/PowerAppsTestEngineMDACustom.js +++ b/src/testengine.provider.mda/PowerAppsTestEngineMDACustom.js @@ -209,7 +209,13 @@ class PowerAppsModelDrivenCanvas { var control = appMagic.AuthoringTool.Runtime.getGlobalBindingContext().controlContexts[itemPath.controlName]; if (typeof control === "undefined") { - return null; + var childControlContext = PowerAppsModelDrivenCanvas.FindControlContextForChildControl(itemPath.controlName); + if (childControlContext) { + control = childControlContext; + } + else { + return null; + } } var property = control.modelProperties[itemPath.propertyName]; @@ -220,26 +226,75 @@ class PowerAppsModelDrivenCanvas { var value = property.getValue(); - if (Array.isArray(value)) { - return value.length; + if (value != null) { + if (Array.isArray(value)) { + return value.length; + } + + return value.dataSource.data.length; } + else { + var replicatedContexts = appMagic.Controls.GlobalContextManager.bindingContext.replicatedContexts; + + if (itemPath.parentControl && itemPath.parentControl.index !== null) { + // Nested gallery - Power Apps only supports one level of nesting so we don't have to go recursively to find it + // Get parent replicated context + var parentControlContext = appMagic.AuthoringTool.Runtime.getNamedControl(itemPath.parentControl.controlName).controlContext; + var parentReplicatedContext = parentControlContext._replicatedContext.bindingContextAt(itemPath.parentControl.index); + replicatedContexts = parentReplicatedContext.replicatedContexts; + } + + var controlContext = appMagic.AuthoringTool.Runtime.getGlobalBindingContext().controlContexts[itemPath.controlName]; + var replicatedContext = controlContext._replicatedContext; - return value.dataSource.data.length; + if (!replicatedContext) { + // This is not a gallery + throw "Not a gallery, no item count available. Most likely a control"; + } + + var managerId = replicatedContext.manager.managerId; + return replicatedContexts[managerId].getBindingContextCount(); + } + } + + static getGallerySelectedRowBindingContext(itemPath) { + // Gallery control selected row binding context + var appMagic = PowerAppsModelDrivenCanvas.getAppMagic(); + var bindingContext = appMagic.Controls?.GlobalContextManager?.bindingContext; + var controlContext = appMagic.AuthoringTool.Runtime.getGlobalBindingContext().controlContexts[itemPath.controlName]; + var replicatedContext = controlContext._replicatedContext; + + if (replicatedContext) { + var managerId = replicatedContext.manager.managerId; + return bindingContext.replicatedContexts[managerId].getSelectedBindingContext(); + } + else { + return bindingContext; + } } static getBindingContext(itemPath) { - var appMagic = PowerAppsModelDrivenCanvas.getAppMagic() + var appMagic = PowerAppsModelDrivenCanvas.getAppMagic(); var bindingContext = appMagic.Controls?.GlobalContextManager?.bindingContext; if (itemPath.parentControl) { - // Control is inside a component or gallery - bindingContext = PowerAppsModelDrivenCanvas.getBindingContext(itemPath.parentControl); + // Control is inside a component or gallery + if (itemPath.parentControl.index !== null) { + //get Bindingcontext by index number or row number + bindingContext = PowerAppsModelDrivenCanvas.getBindingContext(itemPath.parentControl); + } + else { + //get binding context by selected row + bindingContext = PowerAppsModelDrivenCanvas.getGallerySelectedRowBindingContext(itemPath.parentControl); + } } - + if (typeof itemPath.index !== 'undefined' && itemPath.index !== null) { - // Gallery control - var managerId = OpenAjax.widget.byId(itemPath.controlName).OpenAjax.getAuthoringControlContext()._replicatedContext.manager.managerId; + // Gallery control + var controlContext = appMagic.AuthoringTool.Runtime.getGlobalBindingContext().controlContexts[itemPath.controlName]; + var replicatedContext = controlContext._replicatedContext; + var managerId = replicatedContext.manager.managerId; return bindingContext.replicatedContexts[managerId].bindingContextAt(itemPath.index); } @@ -264,12 +319,30 @@ class PowerAppsModelDrivenCanvas { if (controlContext.modelProperties[itemPath.propertyName]) { propertyValue = controlContext.modelProperties[itemPath.propertyName]?.getValue(); - if ((typeof propertyValue !== "undefined") && (propertyValue !== null) && (typeof propertyValue.dataSource !== "undefined") && (typeof propertyValue.dataSource.data !== "undefined")) { + if ((typeof propertyValue !== "undefined") && (propertyValue !== null) && (typeof propertyValue.dataSource !== "undefined") && (typeof propertyValue.dataSource.data !== "undefined")) { // TODO: Transform data to display data propertyValue = propertyValue.dataSource.data; } } } + else { + try { + var childControlContext = PowerAppsModelDrivenCanvas.FindControlContextForChildControl(itemPath.controlName); + if (childControlContext) { + if (childControlContext.modelProperties[itemPath.propertyName]) { + propertyValue = childControlContext.modelProperties[itemPath.propertyName]?.getValue(); + + if ((typeof propertyValue !== "undefined") && (propertyValue !== null) && (typeof propertyValue.dataSource !== "undefined") && (typeof propertyValue.dataSource.data !== "undefined")) { + // TODO: Transform data to display data + propertyValue = propertyValue.dataSource.data; + } + } + } + } + catch (error) { + return console.log(error); + } + } return { propertyValue: propertyValue @@ -317,12 +390,27 @@ class PowerAppsModelDrivenCanvas { return true; } - return appMagic.Functions.select(null, - appMagic.Controls.GlobalContextManager.bindingContext, - appMagic.AuthoringTool.Runtime.getNamedControl(itemPath.controlName), - null, - null, - screenId); + var controlContext = appMagic.AuthoringTool.Runtime.getGlobalBindingContext().controlContexts[itemPath.controlName]; + var replicatedContext = controlContext._replicatedContext; + + if (replicatedContext && itemPath.index != null && itemPath.index >= 0) { + // This is a gallery control click + var rowOrColumnNumber = itemPath.index + 1; + return appMagic.Functions.select(null, + appMagic.Controls.GlobalContextManager.bindingContext, + appMagic.AuthoringTool.Runtime.getNamedControl(itemPath.controlName), + rowOrColumnNumber, + null, + screenId); + } + else { + return appMagic.Functions.select(null, + appMagic.Controls.GlobalContextManager.bindingContext, + appMagic.AuthoringTool.Runtime.getNamedControl(itemPath.controlName), + null, + null, + screenId); + } } static setPropertyValueForControl(itemPath, value) { @@ -338,10 +426,58 @@ class PowerAppsModelDrivenCanvas { return true; } } + else { + try { + var childControlContext = PowerAppsModelDrivenCanvas.FindControlContextForChildControl(itemPath.controlName); + if (childControlContext) { + if (childControlContext.modelProperties[itemPath.propertyName]) { + propertyValue = childControlContext.modelProperties[itemPath.propertyName]?.setValue(value); + return true; + } + } + } + catch (error) { + return console.log(error); + } + } return false; } + static FindControlContextForChildControl(controlToFind) { + var appMagic = PowerAppsModelDrivenCanvas.getAppMagic(); + + var controlContext = appMagic.Controls.GlobalContextManager.bindingContext.controlContexts; + var controlNames = Object.keys(controlContext); + for (let controlName of controlNames) { + var control = controlContext[controlName]; + var bindingContext = PowerAppsModelDrivenCanvas.findControl(controlName, control, controlToFind); + if (bindingContext) { + return bindingContext; + } + } + return null; + } + + static findControl(controlName, controlObject, controlToFind) { + if (controlName === controlToFind) { + return controlObject; + } + + if (controlObject.controlWidget.replicatedContextManager) { + var childrenControlContext = controlObject.controlWidget.replicatedContextManager.authoringAreaBindingContext.controlContexts; + var childControlNames = Object.keys(childrenControlContext); + for (let childControlName of childControlNames) { + var childControlObject = childrenControlContext[childControlName]; + var foundControl = PowerAppsModelDrivenCanvas.findControl(childControlName, childControlObject, controlToFind); + if (foundControl) { + return foundControl; + } + } + } + return null; + } + static interactWithControl(itemPath, value) { var appMagic = PowerAppsModelDrivenCanvas.getAppMagic() @@ -350,8 +486,7 @@ class PowerAppsModelDrivenCanvas { return e._value; } - static buildControlObjectModel() - { + static buildControlObjectModel() { var appMagic = PowerAppsModelDrivenCanvas.getAppMagic() var controls = []; @@ -366,8 +501,7 @@ class PowerAppsModelDrivenCanvas { return JSON.stringify({ Controls: controls }); } - static getControlProperties(itemPath) - { + static getControlProperties(itemPath) { var data = []; var appMagic = PowerAppsModelDrivenCanvas.getAppMagic() @@ -393,16 +527,13 @@ class PowerAppsModelDrivenCanvas { }) } } - else - { + else { data.push( { Key: item.propertyName, - Value: PowerAppsModelDrivenCanvas.getPropertyValueFromControl({ controlName: itemPath.controlName, propertyName: item.propertyName })?.propertyValue + Value: PowerAppsModelDrivenCanvas.getPropertyValueFromControl(itemPath)?.propertyValue }) } - - }) } return JSON.stringify(data);