diff --git a/samples/mda_imagecontrol/Images/monitor.jpg b/samples/mda_imagecontrol/Images/monitor.jpg new file mode 100644 index 000000000..2eb8990ca Binary files /dev/null and b/samples/mda_imagecontrol/Images/monitor.jpg differ diff --git a/samples/mda_imagecontrol/Images/ring.jpg b/samples/mda_imagecontrol/Images/ring.jpg new file mode 100644 index 000000000..dfa1d5491 Binary files /dev/null and b/samples/mda_imagecontrol/Images/ring.jpg differ diff --git a/samples/mda_imagecontrol/MDA_ImageControl_1_0_0_1_managed.zip b/samples/mda_imagecontrol/MDA_ImageControl_1_0_0_1_managed.zip new file mode 100644 index 000000000..1bb9cd676 Binary files /dev/null and b/samples/mda_imagecontrol/MDA_ImageControl_1_0_0_1_managed.zip differ diff --git a/samples/mda_imagecontrol/README.md b/samples/mda_imagecontrol/README.md new file mode 100644 index 000000000..846fb6362 --- /dev/null +++ b/samples/mda_imagecontrol/README.md @@ -0,0 +1,31 @@ +# Overview + +This Power Apps Test Engine sample demonstrates how to assert and interact with the values of Image and Add Picture 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 testPlan.fx.yaml** + Update the YAML file to assert expected values of the Image and Add Picture 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. | + +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\mda_imagecontrol\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=cr693_imagecontrol_dbda3" \ No newline at end of file diff --git a/samples/mda_imagecontrol/testPlan.fx.yaml b/samples/mda_imagecontrol/testPlan.fx.yaml new file mode 100644 index 000000000..9a2b7f5eb --- /dev/null +++ b/samples/mda_imagecontrol/testPlan.fx.yaml @@ -0,0 +1,144 @@ +testSuite: + testSuiteName: MDA Custom Page Tests - Image Control, Add Picture Control + testSuiteDescription: Verify test cases for Image Control and Add Picture Control + persona: User1 + appLogicalName: NotNeeded + + testCases: + # Image1 Control Test Cases + - testCaseName: Test Image Control - OnSelect Event + testCaseDescription: Verify that the ImageRotation property rotates the image correctly. + testSteps: | + Select(Image1); + Assert(Label1.Text = "Image Control Clicked", "Check if the ImageRotation property is set to 90 degrees."); + + - testCaseName: Test Image Control - ImageRotation Property + testCaseDescription: Verify that the ImageRotation property rotates the image correctly. + testSteps: | + Select(ButtonCanvas1); + Assert(Image1.ImageRotation = "rotate90", "Check if the ImageRotation property is set to 90 degrees."); + + - testCaseName: Test Image Control - Visibility Property + testCaseDescription: Verify that the Visible property toggles the visibility of the Image control. + testSteps: | + SetProperty(Image1.Visible, false); + Assert(Image1.Visible = false, "Check if the Image control is hidden."); + SetProperty(Image1.Visible, true); + Assert(Image1.Visible = true, "Check if the Image control is visible."); + + - testCaseName: Test Image Control - Set and Retrieve Image Property + testCaseDescription: Verify that the Image property can be set and retrieved correctly. + testSteps: | + SetProperty(Image1.Image, "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg"); + Wait(Image1, "Image", "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg"); + Assert(Image1.Image = "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg" , "Check if Image property is correctly set."); + + - testCaseName: Test Image Control - Invalid Image URL + testCaseDescription: Verify the behavior when an invalid URL is set for the Image property. + testSteps: | + SetProperty(Image1.Image, ""); + Assert(Image1.Image = "" , "Check if an error is returned for the invalid image URL."); + + - testCaseName: Test Image Control - Tooltip Property + testCaseDescription: Verify that the Tooltip property can be set and retrieved correctly. + testSteps: | + SetProperty(Image1.Tooltip, "This is an image tooltip"); + Assert(Image1.Tooltip = "This is an image tooltip", "Check if the Tooltip property is correctly set."); + + # AddMediaButton1 Control Test Cases + - testCaseName: Test AddMediaButton1 - ChangePictureText Property + testCaseDescription: Verify that the ChangePictureText property updates the button text correctly. + testSteps: | + SetProperty(AddMediaButton1.ChangePictureText, "Upload a New Picture"); + Assert(AddMediaButton1.ChangePictureText = "Upload a New Picture", "Check if ChangePictureText is updated correctly."); + + - testCaseName: Test AddMediaButton1 - FileName Property + testCaseDescription: Verify that the FileName property displays the correct uploaded file name. + testSteps: | + SetProperty(AddMediaButton1.FileName, "ProductImageName1"); + Assert(AddMediaButton1.FileName = "ProductImageName1", "Check if FileName displays the correct uploaded file name."); + + - testCaseName: Test AddMediaButton1 - Select the file from local drive + testCaseDescription: Verify that the control correctly displays the selected image along with its file name. + testSteps: | + Select(AddMediaButton1, "--Physical Path of image--"); + Assert(AddMediaButton1.FileName = "ring.jpg", "Check if Image displays the correct uploaded file name."); + Select(AddMediaButton1, "--Physical Path of image--"); + Assert(AddMediaButton1.FileName = "monitor.jpg", "Check if Image displays the correct uploaded file name."); + + - testCaseName: Test AddMediaButton1 - PlaceholderText Property + testCaseDescription: Verify that the PlaceholderText property displays correctly before uploading an image. + testSteps: | + SetProperty(AddMediaButton1.PlaceholderText, "No image uploaded"); + Assert(AddMediaButton1.PlaceholderText = "No image uploaded", "Check if PlaceholderText displays correctly."); + + - testCaseName: Test AddMediaButton1 - Media Property + testCaseDescription: Verify that the Media property holds the uploaded media file. + testSteps: | + SetProperty(UploadedImage1.Image, "https://fakestoreapi.com/img/61mtL65D4cL._AC_SX679_.jpg"); + Assert(AddMediaButton1.Media <> Blank(), "Check if the Media property holds the uploaded file."); + + - testCaseName: Test AddMediaButton1 - UseMobileCamera Property + testCaseDescription: Verify that the UseMobileCamera property opens the device camera on mobile devices. + testSteps: | + SetProperty(AddMediaButton1.UseMobileCamera, true); + Assert(AddMediaButton1.UseMobileCamera = true, "Check if the UseMobileCamera property is enabled."); + + - testCaseName: Test AddMediaButton1 - Text Property + testCaseDescription: Verify that the Text property updates the button's display text. + testSteps: | + SetProperty(AddMediaButton1.Text, "Upload Photo"); + Assert(AddMediaButton1.Text = "Upload Photo", "Check if the Text property updates correctly."); + + - testCaseName: Test AddMediaButton1 - Tooltip Property + testCaseDescription: Verify that the Tooltip property displays the correct tooltip text on hover. + testSteps: | + SetProperty(AddMediaButton1.Tooltip, "Click to upload an image"); + Assert(AddMediaButton1.Tooltip = "Click to upload an image", "Check if Tooltip displays the correct text."); + + - testCaseName: Test AddMediaButton1 - Visible Property + testCaseDescription: Verify that the Visible property toggles the visibility of the AddMediaButton control. + testSteps: | + SetProperty(AddMediaButton1.Visible, false); + Assert(AddMediaButton1.Visible = false, "Check if the control is hidden."); + SetProperty(AddMediaButton1.Visible, true); + Assert(AddMediaButton1.Visible = true, "Check if the control is visible."); + + # UploadedImage1 Control Test Cases + - testCaseName: Test UploadedImage1 - Image Property + testCaseDescription: Verify that the Image property displays the uploaded image correctly. + testSteps: | + SetProperty(UploadedImage1.Image, "https://fakestoreapi.com/img/81Zt42ioCgL._AC_SX679_.jpg"); + Wait(UploadedImage1, "Image", "https://fakestoreapi.com/img/81Zt42ioCgL._AC_SX679_.jpg"); + Assert(UploadedImage1.Image = "https://fakestoreapi.com/img/81Zt42ioCgL._AC_SX679_.jpg", "Check if the uploaded image is displayed correctly."); + + - testCaseName: Test UploadedImage1 - Tooltip Property + testCaseDescription: Verify that the Tooltip property displays the correct tooltip text on hover. + testSteps: | + SetProperty(UploadedImage1.Tooltip, "Uploaded Image Preview"); + Assert(UploadedImage1.Tooltip = "Uploaded Image Preview", "Check if Tooltip displays correctly."); + + - testCaseName: Test UploadedImage1 - Visible Property + testCaseDescription: Verify that the Visible property toggles the visibility of the UploadedImage control. + testSteps: | + SetProperty(UploadedImage1.Visible, false); + Assert(UploadedImage1.Visible = false, "Check if the control is hidden."); + SetProperty(UploadedImage1.Visible, true); + Assert(UploadedImage1.Visible = true, "Check if the control is 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 diff --git a/src/Microsoft.PowerApps.TestEngine.Tests/Modules/TestEngineExtensionCheckerTests.cs b/src/Microsoft.PowerApps.TestEngine.Tests/Modules/TestEngineExtensionCheckerTests.cs index 49183340a..8adfd8393 100644 --- a/src/Microsoft.PowerApps.TestEngine.Tests/Modules/TestEngineExtensionCheckerTests.cs +++ b/src/Microsoft.PowerApps.TestEngine.Tests/Modules/TestEngineExtensionCheckerTests.cs @@ -86,7 +86,7 @@ public Task> LoadObjectModelAsync() throw new NotImplementedException(); }} - public Task SelectControlAsync(ItemPath itemPath) + public Task SelectControlAsync(ItemPath itemPath, string filePath = null) {{ throw new NotImplementedException(); }} diff --git a/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/Functions/SelectFunctionTests.cs b/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/Functions/SelectFunctionTests.cs index 8d681f4ba..7ec380c87 100644 --- a/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/Functions/SelectFunctionTests.cs +++ b/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/Functions/SelectFunctionTests.cs @@ -59,7 +59,7 @@ public void SelectFunctionThrowsOnNonPowerAppsRecordValuetTest() [Fact] public void SelectOneParamFunctionTest() { - MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny())).Returns(Task.FromResult(true)); + MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny(), null)).Returns(Task.FromResult(true)); LoggingTestHelper.SetupMock(MockLogger); var recordType = RecordType.Empty().Add("Text", FormulaType.String); @@ -75,7 +75,7 @@ public void SelectOneParamFunctionTest() var result = selectFunction.Execute(recordValue); Assert.IsType(result); - MockTestWebProvider.Verify(x => x.SelectControlAsync(It.Is((item) => item.ControlName == recordValue.Name)), Times.Once()); + MockTestWebProvider.Verify(x => x.SelectControlAsync(It.Is((item) => item.ControlName == recordValue.Name), null), Times.Once()); LoggingTestHelper.VerifyLogging(MockLogger, "------------------------------\n\n" + "Executing Select function.", LogLevel.Information, Times.Once()); LoggingTestHelper.VerifyLogging(MockLogger, "Successfully finished executing Select function.", LogLevel.Information, Times.Once()); Assert.Equal(1, updaterFunctionCallCount); @@ -84,7 +84,7 @@ public void SelectOneParamFunctionTest() [Fact] public void SelectTwoParamFunctionTest() { - MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny())).Returns(Task.FromResult(true)); + MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny(), null)).Returns(Task.FromResult(true)); LoggingTestHelper.SetupMock(MockLogger); var recordType = RecordType.Empty().Add("Gallery1", RecordType.Empty()); @@ -101,16 +101,16 @@ public void SelectTwoParamFunctionTest() var result = selectFunction.Execute(recordValue, rowOrColumn); Assert.IsType(result); - MockTestWebProvider.Verify(x => x.SelectControlAsync(It.Is((item) => item.ControlName == recordValue.Name)), Times.Once()); + MockTestWebProvider.Verify(x => x.SelectControlAsync(It.Is((item) => item.ControlName == recordValue.Name), null), Times.Once()); LoggingTestHelper.VerifyLogging(MockLogger, "------------------------------\n\n" + "Executing Select function.", LogLevel.Information, Times.Once()); LoggingTestHelper.VerifyLogging(MockLogger, "Successfully finished executing Select function.", LogLevel.Information, Times.Once()); Assert.Equal(1, updaterFunctionCallCount); } - + [Fact] public void SelectThreeParamFunctionTest() { - MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny())).Returns(Task.FromResult(true)); + MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny(), null)).Returns(Task.FromResult(true)); LoggingTestHelper.SetupMock(MockLogger); var parentRecordType = RecordType.Empty().Add("Gallery1", RecordType.Empty()); var childRecordType = RecordType.Empty().Add("Button1", RecordType.Empty()); @@ -129,7 +129,7 @@ public void SelectThreeParamFunctionTest() var result = selectFunction.Execute(parentValue, rowOrColumn, childValue); Assert.IsType(result); - MockTestWebProvider.Verify(x => x.SelectControlAsync(It.Is((item) => item.ControlName == childValue.Name)), Times.Once()); + MockTestWebProvider.Verify(x => x.SelectControlAsync(It.Is((item) => item.ControlName == childValue.Name), null), Times.Once()); LoggingTestHelper.VerifyLogging(MockLogger, "------------------------------\n\n" + "Executing Select function.", LogLevel.Information, Times.Once()); LoggingTestHelper.VerifyLogging(MockLogger, "Successfully finished executing Select function.", LogLevel.Information, Times.Once()); Assert.Equal(1, updaterFunctionCallCount); @@ -139,7 +139,7 @@ public void SelectThreeParamFunctionTest() public void SelectOneParamFunctionFailsTest() { LoggingTestHelper.SetupMock(MockLogger); - MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny())).Returns(Task.FromResult(false)); + MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny(), null)).Returns(Task.FromResult(false)); var recordType = RecordType.Empty().Add("Text", FormulaType.String); @@ -154,7 +154,7 @@ public void SelectOneParamFunctionFailsTest() var selectFunction = new SelectOneParamFunction(MockTestWebProvider.Object, updaterFunction, MockLogger.Object); Assert.ThrowsAny(() => selectFunction.Execute(recordValue)); - MockTestWebProvider.Verify(x => x.SelectControlAsync(It.Is((item) => item.ControlName == recordValue.Name)), Times.Once()); + MockTestWebProvider.Verify(x => x.SelectControlAsync(It.Is((item) => item.ControlName == recordValue.Name), null), Times.Once()); LoggingTestHelper.VerifyLogging(MockLogger, "------------------------------\n\n" + "Executing Select function.", LogLevel.Information, Times.Once()); LoggingTestHelper.VerifyLogging(MockLogger, "Control name: Button1", LogLevel.Trace, Times.Once()); LoggingTestHelper.VerifyLogging(MockLogger, "Unable to select control", LogLevel.Error, Times.Once()); @@ -164,7 +164,7 @@ public void SelectOneParamFunctionFailsTest() [Fact] public void SelectTwoParamFunctionFailsTest() { - MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny())).Returns(Task.FromResult(false)); + MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny(), null)).Returns(Task.FromResult(false)); LoggingTestHelper.SetupMock(MockLogger); var recordType = RecordType.Empty().Add("Gallery1", RecordType.Empty()); @@ -189,7 +189,7 @@ public void SelectTwoParamFunctionFailsTest() recordValue = new ControlRecordValue(recordType, MockTestWebProvider.Object, "Gallery1"); Assert.ThrowsAny(() => selectFunction.Execute(recordValue, rowOrColumn)); - MockTestWebProvider.Verify(x => x.SelectControlAsync(It.Is((item) => item.ControlName == recordValue.Name)), Times.Once()); + MockTestWebProvider.Verify(x => x.SelectControlAsync(It.Is((item) => item.ControlName == recordValue.Name), null), Times.Once()); LoggingTestHelper.VerifyLogging(MockLogger, "------------------------------\n\n" + "Executing Select function.", LogLevel.Information, Times.AtLeastOnce()); LoggingTestHelper.VerifyLogging(MockLogger, "Control name: Gallery1", LogLevel.Trace, Times.AtLeastOnce()); LoggingTestHelper.VerifyLogging(MockLogger, "Unable to select control", LogLevel.Error, Times.AtLeastOnce()); @@ -199,7 +199,7 @@ public void SelectTwoParamFunctionFailsTest() [Fact] public void SelectThreeParamFunctionFailsTest() { - MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny())).Returns(Task.FromResult(false)); + MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny(), null)).Returns(Task.FromResult(false)); LoggingTestHelper.SetupMock(MockLogger); var parentRecordType = RecordType.Empty().Add("Gallery1", RecordType.Empty()); var childRecordType = RecordType.Empty().Add("Button1", RecordType.Empty()); @@ -229,7 +229,7 @@ public void SelectThreeParamFunctionFailsTest() parentValue = new ControlRecordValue(parentRecordType, MockTestWebProvider.Object, "Gallery1"); childValue = new ControlRecordValue(childRecordType, MockTestWebProvider.Object, "Button1"); Assert.ThrowsAny(() => selectFunction.Execute(parentValue, rowOrColumn, childValue)); - MockTestWebProvider.Verify(x => x.SelectControlAsync(It.Is((item) => item.ControlName == childValue.Name)), Times.Once()); + MockTestWebProvider.Verify(x => x.SelectControlAsync(It.Is((item) => item.ControlName == childValue.Name), null), Times.Once()); LoggingTestHelper.VerifyLogging(MockLogger, "------------------------------\n\n" + "Executing Select function.", LogLevel.Information, Times.AtLeastOnce()); LoggingTestHelper.VerifyLogging(MockLogger, "Control name: Button1", LogLevel.Trace, Times.AtLeastOnce()); LoggingTestHelper.VerifyLogging(MockLogger, "Unable to select control", LogLevel.Error, Times.AtLeastOnce()); @@ -239,7 +239,7 @@ public void SelectThreeParamFunctionFailsTest() [Fact] public void SelectGalleryTest() { - MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny())).Returns(Task.FromResult(true)); + MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny(), null)).Returns(Task.FromResult(true)); LoggingTestHelper.SetupMock(MockLogger); var parentRecordType = RecordType.Empty().Add("Gallery1", RecordType.Empty()); var childRecordType = RecordType.Empty().Add("Button1", RecordType.Empty()); @@ -285,7 +285,7 @@ public void SelectGalleryTest() result = selectthreeParamsFunction.Execute(parentValue, rowOrColumn, childValue); Assert.IsType(result); - MockTestWebProvider.Verify(x => x.SelectControlAsync(It.Is((item) => item.ControlName == childValue.Name)), Times.Exactly(2)); + MockTestWebProvider.Verify(x => x.SelectControlAsync(It.Is((item) => item.ControlName == childValue.Name), null), Times.Exactly(2)); Assert.Equal(2, updaterFunctionCallCount); } } diff --git a/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/PowerFxEngineTests.cs b/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/PowerFxEngineTests.cs index 4e4337207..d8d79d5eb 100644 --- a/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/PowerFxEngineTests.cs +++ b/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/PowerFxEngineTests.cs @@ -78,7 +78,7 @@ public async Task UpdatePowerFxModelAsyncThrowsOnCantGetAppStatusTest() { var recordType = RecordType.Empty().Add("Text", FormulaType.String); var button1 = new ControlRecordValue(recordType, MockTestWebProvider.Object, "Button1"); - MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny())).Returns(Task.FromResult(true)); + MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny(), null)).Returns(Task.FromResult(true)); MockTestWebProvider.Setup(x => x.LoadObjectModelAsync()).Returns(Task.FromResult(new Dictionary() { { "Button1", button1 } })); MockTestWebProvider.Setup(x => x.CheckIsIdleAsync()).Returns(Task.FromResult(false)); @@ -362,7 +362,7 @@ public async Task ExecuteSelectFunctionTest() var recordType = RecordType.Empty().Add("Text", FormulaType.String); var button1 = new ControlRecordValue(recordType, MockTestWebProvider.Object, "Button1"); MockTestWebProvider.Setup(x => x.CheckProviderAsync()).Returns(Task.FromResult(true)); - MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny())).Returns(Task.FromResult(true)); + MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny(), null)).Returns(Task.FromResult(true)); MockTestWebProvider.Setup(x => x.LoadObjectModelAsync()).Returns(Task.FromResult(new Dictionary() { { "Button1", button1 } })); MockTestWebProvider.Setup(x => x.CheckIsIdleAsync()).Returns(Task.FromResult(true)); @@ -386,7 +386,7 @@ public async Task ExecuteSelectFunctionTest() [Fact] public async Task ExecuteSelectFunctionFailingTest() { - MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny())).Returns(Task.FromResult(false)); + MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny(), null)).Returns(Task.FromResult(false)); MockTestWebProvider.Setup(x => x.CheckProviderAsync()).Returns(Task.FromResult(true)); MockTestWebProvider.Setup(x => x.CheckIsIdleAsync()).Returns(Task.FromResult(true)); @@ -569,7 +569,7 @@ private async Task TestStepByStep() }); MockTestWebProvider.Setup(x => x.CheckProviderAsync()).Returns(Task.CompletedTask); MockTestWebProvider.Setup(x => x.CheckIsIdleAsync()).Returns(Task.FromResult(true)); - MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny())).Returns(Task.FromResult(true)); + MockTestWebProvider.Setup(x => x.SelectControlAsync(It.IsAny(), null)).Returns(Task.FromResult(true)); var oldUICulture = CultureInfo.CurrentUICulture; var frenchCulture = new CultureInfo("fr"); diff --git a/src/Microsoft.PowerApps.TestEngine/Helpers/NullCheckHelper.cs b/src/Microsoft.PowerApps.TestEngine/Helpers/NullCheckHelper.cs index 5f3c18b29..3ed6e11b4 100644 --- a/src/Microsoft.PowerApps.TestEngine/Helpers/NullCheckHelper.cs +++ b/src/Microsoft.PowerApps.TestEngine/Helpers/NullCheckHelper.cs @@ -104,5 +104,32 @@ public static void NullCheck(RecordValue obj, NumberValue rowOrColumn, ILogger l logger.LogDebug("No null arguments detected."); } } + + public static void NullCheck(RecordValue obj, StringValue filePath, ILogger logger) + { + bool encounteredError = false; + logger.LogDebug("Checking SetProperty function for null arguments."); + + if (obj == null) + { + logger.LogError("SetProperty function cannot take in a null object."); + encounteredError = true; + } + + if (filePath == null) + { + logger.LogError("SetProperty function cannot take in a null value for file path property name."); + encounteredError = true; + } + + if (encounteredError == true) + { + throw new ArgumentNullException(); + } + else + { + logger.LogDebug("No null arguments detected."); + } + } } } diff --git a/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/Select/SelectFileTwoParamsFunction.cs b/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/Select/SelectFileTwoParamsFunction.cs new file mode 100644 index 000000000..af43fd672 --- /dev/null +++ b/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/Select/SelectFileTwoParamsFunction.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Microsoft.Extensions.Logging; +using Microsoft.PowerApps.TestEngine.Helpers; +using Microsoft.PowerApps.TestEngine.Providers; +using Microsoft.PowerApps.TestEngine.Providers.PowerFxModel; +using Microsoft.PowerFx; +using Microsoft.PowerFx.Types; + +namespace Microsoft.PowerApps.TestEngine.PowerFx.Functions +{ + /// + /// This is the same functionality as the Power Apps Select File function With 2 Params + /// https://docs.microsoft.com/en-us/power-apps/maker/canvas-apps/functions/function-select + /// + public class SelectFileTwoParamsFunction : ReflectionFunction + { + private readonly ITestWebProvider _TestWebProvider; + private readonly Func _updateModelFunction; + private readonly ILogger _logger; + + public SelectFileTwoParamsFunction(ITestWebProvider TestWebProvider, Func updateModelFunction, ILogger logger) : base("Select", FormulaType.Blank, RecordType.Empty(), FormulaType.String) + { + _TestWebProvider = TestWebProvider; + _updateModelFunction = updateModelFunction; + _logger = logger; + } + + public BlankValue Execute(RecordValue obj, StringValue filePath) + { + SelectAsync(obj, filePath).Wait(); + + return FormulaValue.NewBlank(); + } + + private async Task SelectAsync(RecordValue obj, StringValue filePath) + { + _logger.LogInformation("------------------------------\n\n" + + "Executing Select function."); + + NullCheckHelper.NullCheck(obj, filePath, _logger); + + var powerAppControlModel = (ControlRecordValue)obj; + var result = await _TestWebProvider.SelectControlAsync(powerAppControlModel.GetItemPath(), filePath?.Value); + + if (!result) + { + _logger.LogTrace($"Control name: {powerAppControlModel.Name}"); + _logger.LogError("Unable to select control"); + throw new Exception(); + } + + await _updateModelFunction(); + + _logger.LogInformation("Successfully finished executing Select function."); + } + } +} diff --git a/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs b/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs index 13ff84731..141c2f84b 100644 --- a/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs +++ b/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs @@ -57,6 +57,7 @@ public void Setup() powerFxConfig.AddFunction(new SelectOneParamFunction(_testWebProvider, async () => await UpdatePowerFxModelAsync(), Logger)); powerFxConfig.AddFunction(new SelectTwoParamsFunction(_testWebProvider, async () => await UpdatePowerFxModelAsync(), Logger)); powerFxConfig.AddFunction(new SelectThreeParamsFunction(_testWebProvider, async () => await UpdatePowerFxModelAsync(), Logger)); + powerFxConfig.AddFunction(new SelectFileTwoParamsFunction(_testWebProvider, async () => await UpdatePowerFxModelAsync(), Logger)); powerFxConfig.AddFunction(new ScreenshotFunction(TestInfraFunctions, SingleTestInstanceState, _fileSystem, Logger)); powerFxConfig.AddFunction(new AssertWithoutMessageFunction(Logger)); powerFxConfig.AddFunction(new AssertFunction(Logger)); diff --git a/src/Microsoft.PowerApps.TestEngine/Providers/ITestWebProvider.cs b/src/Microsoft.PowerApps.TestEngine/Providers/ITestWebProvider.cs index 1b040b5e2..c7b26fead 100644 --- a/src/Microsoft.PowerApps.TestEngine/Providers/ITestWebProvider.cs +++ b/src/Microsoft.PowerApps.TestEngine/Providers/ITestWebProvider.cs @@ -53,8 +53,9 @@ public interface ITestWebProvider /// Runs the onSelect function of a control /// /// Path to the item + /// The physical file path for image file /// True if onSelect function was successfully executed. - public Task SelectControlAsync(ItemPath itemPath); + public Task SelectControlAsync(ItemPath itemPath, string filePath = null); /// /// Runs the setPropertyValue function of a control diff --git a/src/Microsoft.PowerApps.TestEngine/Providers/PowerFxModel/MDATypeMapping.cs b/src/Microsoft.PowerApps.TestEngine/Providers/PowerFxModel/MDATypeMapping.cs index 27c5f6097..27418f271 100644 --- a/src/Microsoft.PowerApps.TestEngine/Providers/PowerFxModel/MDATypeMapping.cs +++ b/src/Microsoft.PowerApps.TestEngine/Providers/PowerFxModel/MDATypeMapping.cs @@ -32,7 +32,7 @@ public MDATypeMapping() typeMappings.Add("g", FormulaType.Guid); typeMappings.Add("m", FormulaType.Decimal); typeMappings.Add("v", FormulaType.UntypedObject); - typeMappings.Add("i", FormulaType.Number); + typeMappings.Add("i", FormulaType.String); } /// diff --git a/src/Microsoft.PowerApps.TestEngine/TestInfra/ITestInfraFunctions.cs b/src/Microsoft.PowerApps.TestEngine/TestInfra/ITestInfraFunctions.cs index 702fc25fb..8aa246559 100644 --- a/src/Microsoft.PowerApps.TestEngine/TestInfra/ITestInfraFunctions.cs +++ b/src/Microsoft.PowerApps.TestEngine/TestInfra/ITestInfraFunctions.cs @@ -99,5 +99,13 @@ public interface ITestInfraFunctions /// Javascript expression to run /// Return value of javascript public Task RunJavascriptAsync(string jsExpression); + + /// + /// Triggers a click event on a control + /// + /// Control name to trigger event + /// The physical file path for image file + /// + public Task TriggerControlClickEvent(string controlName, string filePath); } } diff --git a/src/Microsoft.PowerApps.TestEngine/TestInfra/PlaywrightTestInfraFunctions.cs b/src/Microsoft.PowerApps.TestEngine/TestInfra/PlaywrightTestInfraFunctions.cs index b997cd5e4..779fd963c 100644 --- a/src/Microsoft.PowerApps.TestEngine/TestInfra/PlaywrightTestInfraFunctions.cs +++ b/src/Microsoft.PowerApps.TestEngine/TestInfra/PlaywrightTestInfraFunctions.cs @@ -1,6 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using System.Net; +using System.Runtime; +using System.Security; +using ICSharpCode.Decompiler.CSharp.Syntax; +using ICSharpCode.Decompiler.IL; +using ICSharpCode.Decompiler.TypeSystem; using Microsoft.Extensions.Logging; using Microsoft.Playwright; using Microsoft.PowerApps.TestEngine.Config; @@ -470,5 +479,31 @@ public async Task AddScriptContentAsync(string content) await Page.AddScriptTagAsync(new PageAddScriptTagOptions { Content = content }); } + + public async Task TriggerControlClickEvent(string controlName, string filePath) + { + ValidatePage(); + + if (!string.IsNullOrEmpty(filePath)) + { + try + { + //Add Picture Control + var fileChooser = await Page.RunAndWaitForFileChooserAsync(async () => + { + var match = Page.Locator($"[data-control-name='{controlName}']"); + await match.ClickAsync(); + }); + await fileChooser.SetFilesAsync(filePath); + return true; + } + catch (Exception ex) + { + _singleTestInstanceState.GetLogger().LogError($"Error triggering Add Picture control click event: {ex.Message}"); + return false; // Return false if there was an error + } + } + return false; + } } } diff --git a/src/testengine.provider.canvas/PowerAppFunctions.cs b/src/testengine.provider.canvas/PowerAppFunctions.cs index 6b701b980..dcfb2183d 100644 --- a/src/testengine.provider.canvas/PowerAppFunctions.cs +++ b/src/testengine.provider.canvas/PowerAppFunctions.cs @@ -239,14 +239,21 @@ public async Task> LoadObjectModelAsync() return controlDictionary; } - public async Task SelectControlAsync(ItemPath itemPath) + public async Task SelectControlAsync(ItemPath itemPath, string filePath = null) { try { ValidateItemPath(itemPath, false); - var itemPathString = JsonConvert.SerializeObject(itemPath); - var expression = $"PowerAppsTestEngine.select({itemPathString})"; - return await TestInfraFunctions.RunJavascriptAsync(expression); + if (!string.IsNullOrEmpty(filePath)) + { + return await TestInfraFunctions.TriggerControlClickEvent(itemPath.ControlName, filePath); + } + else + { + var itemPathString = JsonConvert.SerializeObject(itemPath); + var expression = $"PowerAppsTestEngine.select({itemPathString})"; + return await TestInfraFunctions.RunJavascriptAsync(expression); + } } catch (Exception ex) { diff --git a/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs b/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs index 48c400dcf..b02d4e2df 100644 --- a/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs +++ b/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs @@ -203,11 +203,12 @@ public string CheckTestEngineObject var nameValues = JsonConvert.DeserializeObject>>(propertiesString); if (nameValues.Any(k => k.Key == itemPath.PropertyName)) { - var value = nameValues.First(nv => nv.Key == itemPath.PropertyName).Value; + var value = nameValues.First(nv => nv.Key == itemPath.PropertyName).Value; switch (itemPath.PropertyName.ToLower()) { case "disabled": case "visible": + case "usemobilecamera": case "isprofilepicturevisible": case "islogovisible": case "istitlevisible": @@ -415,15 +416,22 @@ public async Task> LoadObjectModelAsync() return controlDictionary; } - public async Task SelectControlAsync(ItemPath itemPath) + public async Task SelectControlAsync(ItemPath itemPath, string filePath = null) { try { ValidateItemPath(itemPath, false); - var itemPathString = JsonConvert.SerializeObject(itemPath); - // TODO Select a choice item - var expression = $"PowerAppsTestEngine.select({itemPathString})"; - return await TestInfraFunctions.RunJavascriptAsync(expression); + if (!string.IsNullOrEmpty(filePath)) + { + return await TestInfraFunctions.TriggerControlClickEvent(itemPath.ControlName, filePath); + } + else + { + var itemPathString = JsonConvert.SerializeObject(itemPath); + // TODO Select a choice item + var expression = $"PowerAppsTestEngine.select({itemPathString})"; + return await TestInfraFunctions.RunJavascriptAsync(expression); + } } catch (Exception ex) { diff --git a/src/testengine.provider.powerapps.portal/PowerAppPortalProvider.cs b/src/testengine.provider.powerapps.portal/PowerAppPortalProvider.cs index c070b55ef..5233c88d7 100644 --- a/src/testengine.provider.powerapps.portal/PowerAppPortalProvider.cs +++ b/src/testengine.provider.powerapps.portal/PowerAppPortalProvider.cs @@ -136,7 +136,7 @@ public async Task> LoadObjectModelAsync() return controlDictionary; } - public async Task SelectControlAsync(ItemPath itemPath) + public async Task SelectControlAsync(ItemPath itemPath, string filePath = null) { // TODO return true;