Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable PowerApps MDA Classic Input controls for Custom Pages with Test Engine Support #501

Open
wants to merge 5 commits into
base: integration
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
@@ -0,0 +1,168 @@
testSuite:
testSuiteName: Classic Input Controls
testSuiteDescription: Verifies that the classic input controls work correctly.
persona: User1
appLogicalName: classic_input_controls_app

testCases:
- testCaseName: Test Label Property
testCaseDescription: Verify that the label's Text property can be set and retrieved correctly.
testSteps: |
SetProperty(Label1.Text, "Check it");
Assert(Label1.Text = "Check it", "Expected Label1.Text to be 'Check it'");

- testCaseName: Test Label Empty Text
testCaseDescription: Verify that the label's Text property can be set to an empty string.
testSteps: |
SetProperty(Label1.Text, "");
Assert(Label1.Text = "", "Expected Label1.Text to be empty");

- testCaseName: Test Label Long Text
testCaseDescription: Verify that the label's Text property can handle long text.
testSteps: |
SetProperty(Label1.Text, "This is a very long text to check if the label can handle it without any issues.");
Assert(Label1.Text = "This is a very long text to check if the label can handle it without any issues.", "Expected Label1.Text to be the long text");

- testCaseName: Test Label Numeric Text
testCaseDescription: Verify that the label's Text property can handle numeric text.
testSteps: |
SetProperty(Label1.Text, "1234567890");
Assert(Label1.Text = "1234567890", "Expected Label1.Text to be numeric text");

- testCaseName: Test Label Special Characters
testCaseDescription: Verify the Label control updates its Text property correctly with special characters at runtime.
testSteps: |
SetProperty(Label1.Text, "Hello@#$%^&*!");
Assert(Label1.Text = "Hello@#$%^&*!", "Ensuring the Label displays special characters correctly.");

- testCaseName: Test TextInput Sample Text
testCaseDescription: Verify that the text box accepts and displays input correctly.
testSteps: |
SetProperty(TextInput1.Value, "Sample Text");
Assert(TextInput1.Value = "Sample Text", "Verify text box displays the input text correctly");

- testCaseName: Test TextInput Empty
testCaseDescription: Verify that the TextInput control can be set to an empty string at runtime.
testSteps: |
SetProperty(TextInput1.Value, "");
Assert(TextInput1.Value = "", "Expected TextInput1.Value to be empty.");

- testCaseName: Test TextInput Long Text
testCaseDescription: Verify that the TextInput control can handle long text at runtime.
testSteps: |
SetProperty(TextInput1.Value, "This is a very long text to check if the TextInput can handle it without any issues.");
Assert(TextInput1.Value = "This is a very long text to check if the TextInput can handle it without any issues.", "Expected TextInput1.Value to be the long text.");

- testCaseName: Test TextInput Special Characters
testCaseDescription: Verify that the TextInput control updates its Value property correctly with special characters at runtime.
testSteps: |
SetProperty(TextInput1.Value, "Special@#$%^&*()!");
Assert(TextInput1.Value = "Special@#$%^&*()!", "Ensuring the TextInput displays special characters correctly.");

- testCaseName: Test TextInput Numeric Text
testCaseDescription: Verify that the TextInput control can handle numeric text at runtime.
testSteps: |
SetProperty(TextInput1.Value, "1234567890");
Assert(TextInput1.Value = "1234567890", "Expected TextInput1.Value to be numeric text.");

- testCaseName: Select Button Once
testCaseDescription: Verify that the button performs the correct action when selected once.
testSteps: |
Select(Button1);
Assert(Label1.Text = "Button Clicked!", "Verify button performs the correct action when selected once");

- testCaseName: Select Button Twice
testCaseDescription: Verify that the button performs the correct action when selected twice.
testSteps: |
Select(Button1);
Select(Button1);
Assert(Label1.Text = "Button Clicked!", "Verify button performs the correct action when selected twice");

- testCaseName: Test Visible Property
testCaseDescription: Verify that the visibility can be toggled correctly.
testSteps: |
SetProperty(Checkbox1.Visible, true);
Assert(Checkbox1.Visible = true, "Expected Checkbox1.Visible to be true");

- testCaseName: Test Checked Property
testCaseDescription: Verify that the checked state can be set and retrieved correctly.
testSteps: |
SetProperty(Checkbox1.Checked, true);
Assert(Checkbox1.Checked = true, "Expected Checkbox1.Checked to be true");

- testCaseName: Test SelectedItems Property
testCaseDescription: Verify that the SelectedItems property can be set and retrieved correctly.
testSteps: |
SetProperty('Combobox1'.SelectedItems, Table({'Value1':"Item 7",'Value2':7,'Value3':70},
{'Value1':"Item 10",'Value2':10,'Value3':100},{'Value1':"Item 12",'Value2':12,'Value3':120}));
Assert(CountRows('Combobox1'.SelectedItems) = 3, "Validated Successfully");

- testCaseName: Test SelectedDate Property
testCaseDescription: Verify that the SelectedDate property can be set and retrieved correctly.
testSteps: |
SetProperty(DatePicker1.SelectedDate, Date(2024,10,01));
Assert(DatePicker1.SelectedDate = Date(2024,10,01), "Checking the SelectedDate property");

- testCaseName: Test RadioGroup DefaultSelectedItems Property
testCaseDescription: Verify that the RadioGroup control's DefaultSelectedItems property can be set and retrieved correctly.
testSteps: |
SetProperty(RadioGroup1.DefaultSelectedItems, Table({Value1:"Item 7"}));
Assert(CountRows(RadioGroup1.SelectedItems) = 1, "Validated Successfully");

- testCaseName: Test RadioGroup Visible Property
testCaseDescription: Verify that the RadioGroup control's Visible property can be toggled correctly.
testSteps: |
SetProperty(RadioGroup1.Visible, true);
Assert(RadioGroup1.Visible = true, "RadioGroup1 is visible.");
SetProperty(RadioGroup1.Visible, false);
Assert(RadioGroup1.Visible = false, "RadioGroup1 is not visible.");

- testCaseName: Test RadioGroup Items Property
testCaseDescription: Verify that the RadioGroup control's Items property can be set and retrieved correctly.
testSteps: |
SetProperty(RadioGroup1.Items, Table({Value1:"Item 1"}, {Value1:"Item 2"}, {Value1:"Item 3"}));
Assert(CountRows(RadioGroup1.Items) = 3, "RadioGroup1 items count is 3.");

- testCaseName: Test Slider User Interactions
testCaseDescription: Verify that the Slider control's Value property can be set and retrieved correctly, and validate its Min and Max properties.
testSteps: |
SetProperty(Slider1.Value, 50);
Assert(Slider1.Value = 50, "Checking the Value property");
SetProperty(Slider1.Value, 25);
Assert(Slider1.Value = 25, "Checking the Value property");
SetProperty(Slider1.Value, 100);
Assert(Slider1.Value = 100, "Checking the Value property");
SetProperty(Slider1.Value, 75);
Assert(Slider1.Value = 75, "Checking the Value property");
SetProperty(Slider1.Min, 0);
Assert(Slider1.Min = 0, "Slider1 minimum value is set to 0.");
SetProperty(Slider1.Max, 100);
Assert(Slider1.Max = 100, "Slider1 maximum value is set to 100.");

- testCaseName: Test Toggle User Interactions
testCaseDescription: Verify that user interaction with the Toggle control is correctly reflected in its Checked and Visible properties.
testSteps: |
SetProperty(Toggle1.Checked, true);
Assert(Toggle1.Checked = true, "User action correctly toggled Toggle1 to on.");
SetProperty(Toggle1.Checked, false);
Assert(Toggle1.Checked = false, "User action correctly toggled Toggle1 to off.");
SetProperty(Toggle1.Visible, true);
Assert(Toggle1.Visible = true, "Toggle1 is visible.");
SetProperty(Toggle1.Visible, false);
Assert(Toggle1.Visible = false, "Toggle1 is not visible.");

testSettings:
headless: false
locale: "en-US"
recordVideo: true
extensionModules:
enable: true
browserConfigurations:
- browser: Chromium
channel: msedge

environmentVariables:
users:
- personaName: User1
emailKey: user1Email
passwordKey: NotNeeded
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
testSuite:
testSuiteName: ComboBox Data Load
testSuiteDescription: Load data into Combobox1
persona: User1
appLogicalName: NotNeeded
onTestSuiteStart: |
= Experimental.SimulateDataverse({
Action: "Query",
Entity: "cr693_combotable",
Then: Table(
{
'cr693_name': "Item 1",
'cr693_id': 3,
'cr693_combotableid': "8cd3faaa-97ac-4e78-8b71-16c82cabb856",
'createdon': "2024-12-02T17:52:45Z"
},
{
'cr693_name': "RR2",
'cr693_id': 4,
'cr693_combotableid': "ff58de6c-905d-457d-846b-3e0b2aa4c5fd",
'createdon': "2024-12-02T17:54:45Z"
},
{
'cr693_name': "RR3",
'cr693_id': 5,
'cr693_combotableid': "ff58de6c-905d-457d-846b-3e0b2aa4c5fe",
'createdon': "2024-12-02T17:54:45Z"
}
)
});

testCases:
- testCaseName: Load ComboBox Data
testCaseDescription: Verify data loaded into Combobox1
testSteps: |
= SetProperty(Combobox1.SelectedItems, Table(First(Combobox1.Items)));
Assert(CountRows(Combobox1.SelectedItems)=1, "True");

- testCaseName: Test ComboBox Search Functionality
testCaseDescription: Verify that the ComboBox can filter items based on the search query.
testSteps: |
= SetProperty(Combobox1.SelectedItems, Table());
SetProperty(Combobox1.SearchText, "Nonexistent");
Assert(CountRows(Combobox1.SelectedItems) = 0, "Expected no items to match the search query 'Nonexistent'.");

- testCaseName: Test ComboBox Multi-Search Functionality
testCaseDescription: Verify that the ComboBox can filter items based on multiple search queries.
testSteps: |
= SetProperty(Combobox1.SelectedItems, Table());
SetProperty(Combobox1.SearchText, "Item");
SetProperty(Combobox1.SearchText, "RR2");
Assert(CountRows(Filter(Combobox1.Items, "Item" in cr693_name || "RR2" in cr693_name)) = 2, "Expected two items to match the search queries 'Item' and 'RR2'.");

- testCaseName: Test ComboBox SelectMultiple
testCaseDescription: Verify that the ComboBox can select multiple items.
testSteps: |
= SetProperty(Combobox1.SelectMultiple, true);
SetProperty(Combobox1.SelectedItems, Table(First(Combobox1.Items), Last(Combobox1.Items)));
Assert(CountRows(Combobox1.SelectedItems) = 2, "Expected two items to be selected.");

testSettings:
headless: false
locale: "en-US"
recordVideo: true
extensionModules:
enable: true
browserConfigurations:
- browser: Chromium
channel: msedge
timeout: 480000

environmentVariables:
users:
- personaName: User1
emailKey: user1Email
passwordKey: NotNeeded
34 changes: 34 additions & 0 deletions samples/mdaclassicinputcontrols/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

# Overview

This Power Apps Test Engine sample demonstrates how to assert and interact with the values of classic input controls in a model-driven application form.

## 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 classicinputcontrols_testPlan.fx.yaml**
Update the YAML file to assert expected values of the shape 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=shape` | 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. |

5. **Execute the Test for Custom Page**
Change the example below to the URL of your organization:

```pwsh
cd bin\Debug\PowerAppsEngine
dotnet PowerAppsTestEngine.dll -i ..\..\..\samples\mdaclassicinputcontrols\classicinputcontrols_testPlan.fx.yaml -e 00000000-0000-0000-0000-11112223333 -t 11112222-3333-4444-5555-666677778888 -u browser -p mda -d "https://contoso.crm4.dynamics.com/main.aspx?appid=9e9c25f3-1851-ef11-bfe2-6045bd8f802c&pagetype=custom&name=cr7d6_displaycontrols_7009b"
```
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using System.Runtime.CompilerServices;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.PowerApps.TestEngine.Helpers;
using Microsoft.PowerFx.Types;
using Newtonsoft.Json;

Expand Down Expand Up @@ -111,7 +118,7 @@ protected override bool TryGetField(FormulaType fieldType, string fieldName, out

if (jsPropertyValueModel != null)
{
if (string.IsNullOrEmpty(jsPropertyValueModel.PropertyValue))
if (string.IsNullOrEmpty(jsPropertyValueModel.PropertyValue) && fieldType is not StringType)
{
result = null;
return false;
Expand Down Expand Up @@ -139,12 +146,12 @@ protected override bool TryGetField(FormulaType fieldType, string fieldName, out
}
else if (fieldType is DateTimeType)
{
double milliseconds;
long milliseconds;

// When converted from DateTime to a string, a value from Wait() gets roundtripped into a UTC Timestamp format
// The compiler does not register this format as a valid DateTime format
// Because of this, we have to manually convert it into a DateTime
if (double.TryParse(jsPropertyValueModel.PropertyValue, out milliseconds))
if (long.TryParse(jsPropertyValueModel.PropertyValue, out milliseconds))
{
var trueDateTime = new DateTime(1970, 1, 1, 0, 0, 0).AddMilliseconds(milliseconds);
result = DateTimeValue.New(trueDateTime);
Expand All @@ -160,14 +167,16 @@ protected override bool TryGetField(FormulaType fieldType, string fieldName, out
}
else if (fieldType is DateType)
{
double milliseconds;
long milliseconds;

// When converted from Date to a string, a value from Wait() gets roudntripped into a UTC Timestamp format
// The compiler does not register this format as a valid DateTime format
// Because of this, we have to manually convert it into a DateTime
if (double.TryParse(jsPropertyValueModel.PropertyValue, out milliseconds))
if (long.TryParse(jsPropertyValueModel.PropertyValue, out milliseconds))
{
var trueDateTime = new DateTime(1970, 1, 1, 0, 0, 0).AddMilliseconds(milliseconds);
var dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds(milliseconds);
DateTime trueDateTime = dateTimeOffset.LocalDateTime;
//var trueDateTime = new DateTime(1970, 1, 1, 0, 0, 0).AddMilliseconds(milliseconds);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented out line. Since seems like it is not needed, and this variable has been defined and initialized one line above.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

result = DateValue.NewDateOnly(trueDateTime.Date);
}
// When converted from DateTime to a string, a value from SetProperty() retains it's MMDDYYYY hh::mm::ss format
Expand Down
12 changes: 9 additions & 3 deletions src/testengine.provider.mda/ModelDrivenApplicationProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,17 @@ public string CheckTestEngineObject
{
case "disabled":
case "visible":
case "checked":
return (T)(object)("{PropertyValue: " + value.ToString().ToLower() + "}");
default:
switch (value.GetType().ToString())
{
case "System.String":

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if nameof(System.String) works properly here instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nameof(System.String) approach won't work here because it evaluates to "String" instead of the fully qualified type name "System.String". Since value.GetType().ToString() returns the fully qualified type name, using nameof would cause a mismatch. Consider using value is string for a cleaner and more type-safe approach.

var stringValue = value.ToString();
if (string.IsNullOrEmpty(stringValue))
{
return (T)(object)("{\"PropertyValue\": \"\"}");
}
return (T)(object)("{PropertyValue: '" + value.ToString() + "'}");
default:
return (T)(object)("{PropertyValue: " + value.ToString() + "}");
Expand Down Expand Up @@ -479,7 +485,7 @@ public async Task<bool> SetPropertyDateAsync(ItemPath itemPath, DateValue value)
// TODO - Set the Xrm SDK Value and update state for any JS to run
Copy link

@mcalderon00 mcalderon00 Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this // TODO comment still valid. Or can we remove it. This type of TODO comments (Tasks), is usually not useful, and I would prefer to have a work-item if something is missing to implement here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created work item #519


// Date.parse() parses the date to unix timestamp
var expression = $"PowerAppsTestEngine.setPropertyValue({itemPathString},{{{propertyNameString}:Date.parse(\"{recordValue}\")}})";
var expression = $"PowerAppsTestEngine.setPropertyValue({itemPathString},Date.parse(\"{recordValue}\"))";

return await TestInfraFunctions.RunJavascriptAsync<bool>(expression);
}
Expand All @@ -504,7 +510,7 @@ public async Task<bool> SetPropertyRecordAsync(ItemPath itemPath, RecordValue va
checkVal = FormatValue(value);
}

var expression = $"PowerAppsTestEngine.setPropertyValue({itemPathString},{{{propertyNameString}:{checkVal}}})";
var expression = $"PowerAppsTestEngine.setPropertyValue({itemPathString},{checkVal})";

return await TestInfraFunctions.RunJavascriptAsync<bool>(expression);
}
Expand Down Expand Up @@ -723,5 +729,5 @@ private static string GetQueryParametersForTestUrl(string tenantId, string addit
{
return $"?tenantId={tenantId}&source=testengine{additionalQueryParams}";
}
}
}
}
Loading