diff --git a/.azurepipelines/azure-pipelines-1ES.yml b/.azurepipelines/azure-pipelines-1ES.yml new file mode 100644 index 000000000..fff77e038 --- /dev/null +++ b/.azurepipelines/azure-pipelines-1ES.yml @@ -0,0 +1,194 @@ +trigger: none +pr: none +resources: + repositories: + - repository: 1ESPipelineTemplates + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + parameters: + sdl: + codeSignValidation: + enabled: true + codeql: + ${{ if eq(variables['Build.SourceBranch'], variables['AllowedBranch']) }}: + enabledOnNonDefaultBranches: true + pool: + name: Azure-Pipelines-1ESPT-ExDShared + image: windows-2022 + os: windows + stages: + - stage: stage + jobs: + - job: Build_PowerAppsTestEngine + displayName: 'Build PowerAppsTestEngine Solution' + strategy: + matrix: + Debug: + BuildConfiguration: 'Debug' + Release: + BuildConfiguration: 'Release' + templateContext: + outputs: + - output: pipelineArtifact + condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) + artifactName: 'PowerApps.TestEngine ($(BuildConfiguration))' + targetPath: '$(Build.ArtifactStagingDirectory)' + - output: nuget + condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'), eq(variables['UpdateVer'], 'true')) + useDotNetTask: false # The default is false to use the NuGetCommand task. Set to true to use the DotNetCoreCLI task to publish packages. + packagesToPush: '$(Build.ArtifactStagingDirectory)/Microsoft.PowerApps.TestEngine.*.nupkg' + packageParentPath: '$(Build.ArtifactStagingDirectory)' + publishVstsFeed: $(InternalFeed) + nuGetFeedType: internal + allowPackageConflicts: true # Optional. NuGetCommand task only. + steps: + - script: | + echo "Hello $(myVariable)" + - task: UseDotNet@2 + displayName: 'Use dotnet sdk 8.0' + inputs: + version: 8.0.x + installationPath: '$(Agent.ToolsDirectory)/dotnet' + - task: DotNetCoreCLI@2 + displayName: 'Build and test' + inputs: + command: 'run' + projects: '$(Build.SourcesDirectory)/targets/targets.csproj' + arguments: '-- ci -c $(BuildConfiguration)' + - task: PublishTestResults@2 + inputs: + testResultsFormat: 'VSTest' + testResultsFiles: '**/*-*.trx' + searchFolder: '$(Build.SourcesDirectory)/obj/' + mergeTestResults: true + failTaskOnFailedTests: true + - task: EsrpCodeSigning@5 + displayName: 'ESRP sign' + condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) + inputs: + ConnectedServiceName: $(EsrpConServName) + AppRegistrationClientId: $(EsrpAppRegCliId) + AppRegistrationTenantId: $(EsrpAppRegTenId) + AuthAKVName: $(EsrpKVName) + AuthCertName: $(EsrpAuthCertName) + AuthSignCertName: $(EsrpAuthSignCertName) + FolderPath: '$(Build.SourcesDirectory)/bin/$(BuildConfiguration)/PowerAppsTestEngineWrapper/' + Pattern: '*.dll' + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-233863-SN", + "OperationCode": "StrongNameSign", + "Parameters": {}, + "ToolName": "sign", + "ToolVersion": "1.0" + }, + { + "KeyCode": "CP-233863-SN", + "OperationCode": "StrongNameVerify", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": {} + }, + { + "KeyCode": "CP-230012", + "OperationCode": "SigntoolSign", + "Parameters": { + "OpusName": "Microsoft", + "OpusInfo": "http://www.microsoft.com", + "Append": "/as", + "FileDigest": "/fd \"SHA256\"", + "PageHash": "/NPH", + "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + }, + "ToolName": "sign", + "ToolVersion": "1.0" + }, + { + "KeyCode": "CP-230012", + "OperationCode": "SigntoolVerify", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": {} + } + ] + - task: CopyFiles@2 + displayName: 'Copy Built Files to Artifact Staging Directory' + inputs: + SourceFolder: '$(Build.SourcesDirectory)/bin' + TargetFolder: '$(Build.ArtifactStagingDirectory)/buildoutput/bin' + # Include all files except abc.txt + Contents: | + **/* + !**/ThirdPartyNotices.txt + - task: CopyFiles@2 + displayName: 'Copy Built Files to Artifact Staging Directory' + inputs: + SourceFolder: '$(Build.SourcesDirectory)/obj' + TargetFolder: '$(Build.ArtifactStagingDirectory)/buildoutput/obj' + # Include all files except abc.txt + Contents: | + **/* + !**/ThirdPartyNotices.txt + - task: CopyFiles@2 + displayName: 'Copy Built Files to Artifact Staging Directory' + inputs: + SourceFolder: '$(Build.SourcesDirectory)/pkg' + TargetFolder: '$(Build.ArtifactStagingDirectory)/buildoutput/pkg' + # Include all files except abc.txt + Contents: | + **/* + !**/ThirdPartyNotices.txt + - task: DotNetCoreCLI@2 + displayName: 'Pack' + inputs: + command: 'run' + projects: '$(Build.SourcesDirectory)/targets/targets.csproj' + arguments: '-- pack-AlphaV2 -c $(BuildConfiguration) -o $(Build.ArtifactStagingDirectory)' + condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) + - task: EsrpCodeSigning@5 + displayName: 'ESRP sign nuget packages' + inputs: + ConnectedServiceName: $(EsrpConServName) + AppRegistrationClientId: $(EsrpAppRegCliId) + AppRegistrationTenantId: $(EsrpAppRegTenId) + AuthAKVName: $(EsrpKVName) + AuthCertName: $(EsrpAuthCertName) + AuthSignCertName: $(EsrpAuthSignCertName) + FolderPath: '$(Build.ArtifactStagingDirectory)' + Pattern: '*.nupkg' + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-401405", + "OperationCode": "NuGetSign", + "Parameters": {}, + "ToolName": "sign", + "ToolVersion": "1.0" + }, + { + "KeyCode": "CP-401405", + "OperationCode": "NuGetVerify", + "Parameters": {}, + "ToolName": "sign", + "ToolVersion": "1.0" + } + ] + condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) + - task: PublishSymbols@2 + displayName: 'Publish symbols' + condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'), eq(variables['UpdateVer'], 'true')) + continueOnError: true + enabled: True + inputs: + SearchPattern: '$(Build.SourcesDirectory)/bin/$(BuildConfiguration)/**/*.pdb' + SymbolServerType: TeamServices + SymbolsPath: http://symweb/ + CompressSymbols: true + IndexSources: True + SymbolsArtifactName: TestEngine_Symbols_$(Build.BuildNumber) \ No newline at end of file diff --git a/src/Microsoft.PowerApps.TestEngine/Microsoft.PowerApps.TestEngine.csproj b/src/Microsoft.PowerApps.TestEngine/Microsoft.PowerApps.TestEngine.csproj index dc5dea176..b84db77e6 100644 --- a/src/Microsoft.PowerApps.TestEngine/Microsoft.PowerApps.TestEngine.csproj +++ b/src/Microsoft.PowerApps.TestEngine/Microsoft.PowerApps.TestEngine.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + netstandard2.0 enable disable True @@ -42,14 +42,14 @@ - - + + - + diff --git a/src/Microsoft.PowerApps.TestEngine/TestEngine.cs b/src/Microsoft.PowerApps.TestEngine/TestEngine.cs index d5e4d58aa..d921dfda8 100644 --- a/src/Microsoft.PowerApps.TestEngine/TestEngine.cs +++ b/src/Microsoft.PowerApps.TestEngine/TestEngine.cs @@ -60,7 +60,7 @@ public TestEngine(ITestState state, /// Optional query parameters that would be passed to the Player URL for optional features or parameters. /// The full path where the test results are saved. /// Throws ArgumentNullException if any of testConfigFile, environmentId, tenantId or domain are missing or empty. - public async Task RunTestAsync(FileInfo testConfigFile, string environmentId, Guid tenantId, DirectoryInfo outputDirectory, string domain, string queryParams) + public async Task RunTestAsync(FileInfo testConfigFile, string environmentId, Guid? tenantId, DirectoryInfo outputDirectory, string domain, string queryParams) { // Set up test reporting var testRunId = _testReporter.CreateTestRun("Power Fx Test Runner", "User"); // TODO: determine if there are more meaningful values we can put here diff --git a/src/PowerAppsTestEngine.sln b/src/PowerAppsTestEngine.sln index bc65b427e..acd28e93e 100644 --- a/src/PowerAppsTestEngine.sln +++ b/src/PowerAppsTestEngine.sln @@ -82,6 +82,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "testengine.common.user", "t EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "testengine.common.user.tests", "testengine.common.user.tests\testengine.common.user.tests.csproj", "{52D935F1-3567-48B7-904F-1183F824A9FB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerAppsTestEngineWrapper", "PowerAppsTestEngineWrapper\PowerAppsTestEngineWrapper.csproj", "{CD9738C3-8FEB-4E4D-A392-5ABE74B0E748}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -204,6 +206,10 @@ Global {52D935F1-3567-48B7-904F-1183F824A9FB}.Debug|Any CPU.Build.0 = Debug|Any CPU {52D935F1-3567-48B7-904F-1183F824A9FB}.Release|Any CPU.ActiveCfg = Release|Any CPU {52D935F1-3567-48B7-904F-1183F824A9FB}.Release|Any CPU.Build.0 = Release|Any CPU + {CD9738C3-8FEB-4E4D-A392-5ABE74B0E748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD9738C3-8FEB-4E4D-A392-5ABE74B0E748}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD9738C3-8FEB-4E4D-A392-5ABE74B0E748}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD9738C3-8FEB-4E4D-A392-5ABE74B0E748}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/PowerAppsTestEngine/PowerAppsTestEngine.csproj b/src/PowerAppsTestEngine/PowerAppsTestEngine.csproj index b7a85d91b..8a4c49538 100644 --- a/src/PowerAppsTestEngine/PowerAppsTestEngine.csproj +++ b/src/PowerAppsTestEngine/PowerAppsTestEngine.csproj @@ -24,15 +24,29 @@ - - - - + + + + + + + + + + + + + + + - - - + + false + False + true + true + diff --git a/src/PowerAppsTestEngine/Program.cs b/src/PowerAppsTestEngine/Program.cs index 69e99c7a0..9f4098741 100644 --- a/src/PowerAppsTestEngine/Program.cs +++ b/src/PowerAppsTestEngine/Program.cs @@ -1,321 +1,4 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -using System.ComponentModel.Composition; -using System.ComponentModel.Composition.Hosting; -using System.Diagnostics; -using System.Text.RegularExpressions; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.PowerApps.TestEngine; -using Microsoft.PowerApps.TestEngine.Config; -using Microsoft.PowerApps.TestEngine.Modules; -using Microsoft.PowerApps.TestEngine.PowerFx; -using Microsoft.PowerApps.TestEngine.Providers; -using Microsoft.PowerApps.TestEngine.Reporting; -using Microsoft.PowerApps.TestEngine.System; -using Microsoft.PowerApps.TestEngine.TestInfra; -using Microsoft.PowerApps.TestEngine.Users; -using PowerAppsTestEngine; - -var switchMappings = new Dictionary() -{ - { "-i", "TestPlanFile" }, - { "-e", "EnvironmentId" }, - { "-t", "TenantId" }, - { "-o", "OutputDirectory" }, - { "-l", "LogLevel" }, - { "-q", "QueryParams" }, - { "-d", "Domain" }, - { "-m", "Modules" }, - { "-u", "UserAuth" }, - { "-p", "Provider" }, - { "-a", "UserAuthType"}, - { "-w", "Wait" }, - { "-r", "Record" } -}; - -var inputOptions = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("config.json", true) - .AddJsonFile("config.dev.json", true) - .AddCommandLine(args, switchMappings) - .Build() - .Get(); - -if (inputOptions == null) -{ - Console.WriteLine("[Critical Error]: Input options are null"); - return; -} -else -{ - - // If an empty field is put in via commandline, it won't register as empty - // It will cannabalize the next flag, and then ruin the next flag's operation - // Therefore, we have to abort the program in this instance - - if (!string.IsNullOrEmpty(inputOptions.TestPlanFile)) - { - if (inputOptions.TestPlanFile.Substring(0, 1) == "-") - { - Console.WriteLine("[Critical Error]: TestPlanFile field is blank."); - return; - } - } - - if (!string.IsNullOrEmpty(inputOptions.EnvironmentId)) - { - if (inputOptions.EnvironmentId.Substring(0, 1) == "-") - { - Console.WriteLine("[Critical Error]: EnvironmentId field is blank."); - return; - } - } - - if (!string.IsNullOrEmpty(inputOptions.TenantId)) - { - if (inputOptions.TenantId.Substring(0, 1) == "-") - { - Console.WriteLine("[Critical Error]: TenantId field is blank."); - return; - } - } - - if (!string.IsNullOrEmpty(inputOptions.OutputDirectory)) - { - if (inputOptions.OutputDirectory.Substring(0, 1) == "-") - { - Console.WriteLine("[Critical Error]: OutputDirectory field is blank."); - return; - } - } - - if (!string.IsNullOrEmpty(inputOptions.LogLevel)) - { - if (inputOptions.LogLevel.Substring(0, 1) == "-") - { - Console.WriteLine("[Critical Error]: LogLevel field is blank."); - return; - } - } - - if (!string.IsNullOrEmpty(inputOptions.Domain)) - { - if (inputOptions.Domain.Substring(0, 1) == "-") - { - Console.WriteLine("[Critical Error]: Domain field is blank."); - return; - } - } - - if (!string.IsNullOrEmpty(inputOptions.QueryParams)) - { - if (inputOptions.QueryParams.Substring(0, 1) == "-") - { - Console.WriteLine("[Critical Error]: QueryParams field is blank."); - return; - } - } - if (!string.IsNullOrEmpty(inputOptions.Wait) && inputOptions.Wait.ToLower() == "true") - { - Console.WriteLine("Waiting, press enter to continue. You can now optionally attach debugger to dotnet PowerAppsTestEngine.dll process now"); - Console.ReadLine(); - if (Debugger.IsAttached) - { - // Welcome to the debugger experience for Power Apps Test Engine - // - // Key classes you may want to investigate and add breakpoint inside to understand key components or : - // - SingleTestRunner.RunTestAsync that will run a single test case - // - PlaywrightTestInfraFunctions.SetupAsync for setup of Playwright state - // - PowerFxEngine.ExecuteWithRetryAsync that execute Power Fx test steps - // - Implementations or ITestWebProvider for Test Engine providers that get the state of the resource to be tested - // - Implementations of ITestEngineModule for Power Fx extensions - Debugger.Break(); - } - } - - - var logLevel = LogLevel.Information; // Default log level - if (string.IsNullOrEmpty(inputOptions.LogLevel) || !Enum.TryParse(inputOptions.LogLevel, true, out logLevel)) - { - Console.WriteLine($"Unable to parse log level: {inputOptions.LogLevel}, using default"); - logLevel = LogLevel.Information; - } - - var userAuth = "storagestate"; // Default to storage state - if (!string.IsNullOrEmpty(inputOptions.UserAuth)) - { - userAuth = inputOptions.UserAuth; - } - - var provider = "canvas"; - if (!string.IsNullOrEmpty(inputOptions.Provider)) - { - provider = inputOptions.Provider; - } - - var auth = "default"; - if (!string.IsNullOrEmpty(inputOptions.UserAuthType)) - { - auth = inputOptions.UserAuthType; - } - - try - { - using var loggerFactory = LoggerFactory.Create(loggingBuilder => loggingBuilder - .ClearProviders() - .AddFilter(l => l >= logLevel) - .AddProvider(new TestLoggerProvider(new FileSystem()))); - - var logger = loggerFactory.CreateLogger(); - - var serviceProvider = new ServiceCollection() - .AddSingleton(loggerFactory) - .AddSingleton() - .AddSingleton() - .AddScoped() - .AddScoped(sp => - { - var testState = sp.GetRequiredService(); - var userManagers = testState.GetTestEngineUserManager(); - if (userManagers.Count == 0) - { - testState.LoadExtensionModules(logger); - userManagers = testState.GetTestEngineUserManager(); - } - - var match = userManagers.Where(x => x.Name.Equals(userAuth)).FirstOrDefault(); - - if (match == null) - { - throw new InvalidDataException($"Unable to find user auth {userAuth}"); - } - - return match; - }) - .AddTransient(sp => - { - var testState = sp.GetRequiredService(); - var testWebProviders = testState.GetTestEngineWebProviders(); - if (testWebProviders.Count == 0) - { - testState.LoadExtensionModules(logger); - testWebProviders = testState.GetTestEngineWebProviders(); - } - - var match = testWebProviders.Where(x => x.Name.Equals(provider)).FirstOrDefault(); - - if (match == null) - { - throw new InvalidDataException($"Unable to find provider {provider}"); - } - - - return match; - }) - .AddSingleton(sp => - { - var testState = sp.GetRequiredService(); - var testAuthProviders = testState.GetTestEngineAuthProviders(); - if (testAuthProviders.Count == 0) - { - testState.LoadExtensionModules(logger); - testAuthProviders = testState.GetTestEngineAuthProviders(); - } - - var match = testAuthProviders.Where(x => x.Name.Equals(auth)).FirstOrDefault(); - - if (match == null) - { - match = new DefaultUserCertificateProvider(); - } - - return match; - }) - .AddSingleton() - .AddSingleton() - .AddScoped() - .AddScoped() - .AddScoped((sp) => sp.GetRequiredService().GetLogger()) - .AddSingleton() - .AddScoped() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .BuildServiceProvider(); - - TestEngine testEngine = serviceProvider.GetRequiredService(); - - // Default value for optional arguments is set before the class library is invoked. - // The class library expects actual types in its input arguments, so optional arguments - // to the Test Engine entry point function RunTestAsync must be checked for null values and their - // corresponding default values set beforehand. - var testPlanFile = new FileInfo(inputOptions.TestPlanFile); - var tenantId = Guid.Parse(inputOptions.TenantId); - var environmentId = inputOptions.EnvironmentId; - var domain = string.Empty; - var queryParams = ""; - - DirectoryInfo outputDirectory; - const string DefaultOutputDirectory = "TestOutput"; - var _fileSystem = serviceProvider.GetRequiredService(); - if (!string.IsNullOrEmpty(inputOptions.OutputDirectory)) - { - if (Path.IsPathRooted(inputOptions.OutputDirectory.Trim())) - { - Console.WriteLine("[Critical Error]: Please provide a relative path for the output."); - return; - } - else - { - outputDirectory = new DirectoryInfo(Path.Combine(_fileSystem.GetDefaultRootTestEngine(), inputOptions.OutputDirectory.Trim())); - } - } - else - { - outputDirectory = new DirectoryInfo(Path.Combine(_fileSystem.GetDefaultRootTestEngine(), DefaultOutputDirectory.Trim())); - } - - if (!string.IsNullOrEmpty(inputOptions.QueryParams)) - { - queryParams = inputOptions.QueryParams; - } - - if (!string.IsNullOrEmpty(inputOptions.Domain)) - { - domain = inputOptions.Domain; - } - - string modulePath = Path.GetDirectoryName(typeof(Program).Assembly.Location); - List modules = new List(); - if (!string.IsNullOrEmpty(inputOptions.Modules) && Directory.Exists(inputOptions.Modules)) - { - modulePath = inputOptions.Modules; - } - - ITestState state = serviceProvider.GetService(); - state.SetModulePath(modulePath); - - if (!string.IsNullOrEmpty(inputOptions.Record)) - { - state.SetRecordMode(); - } - - //setting defaults for optional parameters outside RunTestAsync - var testResult = await testEngine.RunTestAsync(testPlanFile, environmentId, tenantId, outputDirectory, domain, queryParams); - if (testResult != "InvalidOutputDirectory") - { - Console.WriteLine($"Test results can be found here: {testResult}"); - } - - } - catch (Exception ex) - { - Console.WriteLine("[Critical Error]: " + ex.Message); - Console.WriteLine(ex); - } -} - - +await PowerAppsTestEngineWrapper.Program.Main(args); diff --git a/src/PowerAppsTestEngine/InputOptions.cs b/src/PowerAppsTestEngineWrapper/InputOptions.cs similarity index 95% rename from src/PowerAppsTestEngine/InputOptions.cs rename to src/PowerAppsTestEngineWrapper/InputOptions.cs index b23f5d07b..5297bbcbe 100644 --- a/src/PowerAppsTestEngine/InputOptions.cs +++ b/src/PowerAppsTestEngineWrapper/InputOptions.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -namespace PowerAppsTestEngine +namespace PowerAppsTestEngineWrapper { public class InputOptions { diff --git a/src/PowerAppsTestEngineWrapper/PowerAppsTestEngineWrapper.csproj b/src/PowerAppsTestEngineWrapper/PowerAppsTestEngineWrapper.csproj new file mode 100644 index 000000000..214cd01fd --- /dev/null +++ b/src/PowerAppsTestEngineWrapper/PowerAppsTestEngineWrapper.csproj @@ -0,0 +1,110 @@ + + + + netstandard2.0 + enable + enable + True + + + + portable + true + + + + true + true + ../../35MSSharedLib1024.snk + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Microsoft + crmsdk,Microsoft + Microsoft.PowerApps.TestEngine + Alpha Release: Providing makers with a single automated testing platform for all Power Apps apps + + Notice: + This package is an ALPHA release. - Use at your own risk. + + Intial Alpha release of Microsoft.PowerAppsTestEngine + + © Microsoft Corporation. All rights reserved. + true + 1.0 + + + + false + False + true + true + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + true + lib\$(TargetFramework)\ + + + + + + PreserveNewest + + + PreserveNewest + + + \ No newline at end of file diff --git a/src/PowerAppsTestEngineWrapper/Program.cs b/src/PowerAppsTestEngineWrapper/Program.cs new file mode 100644 index 000000000..6aaf6b4d0 --- /dev/null +++ b/src/PowerAppsTestEngineWrapper/Program.cs @@ -0,0 +1,325 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Diagnostics; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.PowerApps.TestEngine; +using Microsoft.PowerApps.TestEngine.Config; +using Microsoft.PowerApps.TestEngine.Modules; +using Microsoft.PowerApps.TestEngine.PowerFx; +using Microsoft.PowerApps.TestEngine.Providers; +using Microsoft.PowerApps.TestEngine.Reporting; +using Microsoft.PowerApps.TestEngine.System; +using Microsoft.PowerApps.TestEngine.TestInfra; +using Microsoft.PowerApps.TestEngine.Users; + +namespace PowerAppsTestEngineWrapper +{ + public class Program + { + public static async Task Main(string[] args) + { + + var switchMappings = new Dictionary() + { + { "-i", "TestPlanFile" }, + { "-e", "EnvironmentId" }, + { "-t", "TenantId" }, + { "-o", "OutputDirectory" }, + { "-l", "LogLevel" }, + { "-q", "QueryParams" }, + { "-d", "Domain" }, + { "-m", "Modules" }, + { "-u", "UserAuth" }, + { "-p", "Provider" }, + { "-a", "UserAuthType"}, + { "-w", "Wait" }, + { "-r", "Record" } + }; + + var inputOptions = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("config.json", true) + .AddJsonFile("config.dev.json", true) + .AddCommandLine(args, switchMappings) + .Build() + .Get(); + + if (inputOptions == null) + { + Console.WriteLine("[Critical Error]: Input options are null"); + return; + } + else + { + + // If an empty field is put in via commandline, it won't register as empty + // It will cannabalize the next flag, and then ruin the next flag's operation + // Therefore, we have to abort the program in this instance + + if (!string.IsNullOrEmpty(inputOptions.TestPlanFile)) + { + if (inputOptions.TestPlanFile.Substring(0, 1) == "-") + { + Console.WriteLine("[Critical Error]: TestPlanFile field is blank."); + return; + } + } + + if (!string.IsNullOrEmpty(inputOptions.EnvironmentId)) + { + if (inputOptions.EnvironmentId.Substring(0, 1) == "-") + { + Console.WriteLine("[Critical Error]: EnvironmentId field is blank."); + return; + } + } + + if (!string.IsNullOrEmpty(inputOptions.TenantId)) + { + if (inputOptions.TenantId.Substring(0, 1) == "-") + { + Console.WriteLine("[Critical Error]: TenantId field is blank."); + return; + } + } + + if (!string.IsNullOrEmpty(inputOptions.OutputDirectory)) + { + if (inputOptions.OutputDirectory.Substring(0, 1) == "-") + { + Console.WriteLine("[Critical Error]: OutputDirectory field is blank."); + return; + } + } + + if (!string.IsNullOrEmpty(inputOptions.LogLevel)) + { + if (inputOptions.LogLevel.Substring(0, 1) == "-") + { + Console.WriteLine("[Critical Error]: LogLevel field is blank."); + return; + } + } + + if (!string.IsNullOrEmpty(inputOptions.Domain)) + { + if (inputOptions.Domain.Substring(0, 1) == "-") + { + Console.WriteLine("[Critical Error]: Domain field is blank."); + return; + } + } + + if (!string.IsNullOrEmpty(inputOptions.QueryParams)) + { + if (inputOptions.QueryParams.Substring(0, 1) == "-") + { + Console.WriteLine("[Critical Error]: QueryParams field is blank."); + return; + } + } + if (!string.IsNullOrEmpty(inputOptions.Wait) && inputOptions.Wait.ToLower() == "true") + { + Console.WriteLine("Waiting, press enter to continue. You can now optionally attach debugger to dotnet PowerAppsTestEngine.dll process now"); + Console.ReadLine(); + if (Debugger.IsAttached) + { + // Welcome to the debugger experience for Power Apps Test Engine + // + // Key classes you may want to investigate and add breakpoint inside to understand key components or : + // - SingleTestRunner.RunTestAsync that will run a single test case + // - PlaywrightTestInfraFunctions.SetupAsync for setup of Playwright state + // - PowerFxEngine.ExecuteWithRetryAsync that execute Power Fx test steps + // - Implementations or ITestWebProvider for Test Engine providers that get the state of the resource to be tested + // - Implementations of ITestEngineModule for Power Fx extensions + Debugger.Break(); + } + } + + + var logLevel = LogLevel.Information; // Default log level + if (string.IsNullOrEmpty(inputOptions.LogLevel) || !Enum.TryParse(inputOptions.LogLevel, true, out logLevel)) + { + Console.WriteLine($"Unable to parse log level: {inputOptions.LogLevel}, using default"); + logLevel = LogLevel.Information; + } + + var userAuth = "storagestate"; // Default to storage state + if (!string.IsNullOrEmpty(inputOptions.UserAuth)) + { + userAuth = inputOptions.UserAuth; + } + + var provider = "canvas"; + if (!string.IsNullOrEmpty(inputOptions.Provider)) + { + provider = inputOptions.Provider; + } + + var auth = "default"; + if (!string.IsNullOrEmpty(inputOptions.UserAuthType)) + { + auth = inputOptions.UserAuthType; + } + + try + { + using var loggerFactory = LoggerFactory.Create(loggingBuilder => loggingBuilder + .ClearProviders() + .AddFilter(l => l >= logLevel) + .AddProvider(new TestLoggerProvider(new FileSystem()))); + + var logger = loggerFactory.CreateLogger(); + + var serviceProvider = new ServiceCollection() + .AddSingleton(loggerFactory) + .AddSingleton() + .AddSingleton() + .AddScoped() + .AddScoped(sp => + { + var testState = sp.GetRequiredService(); + var userManagers = testState.GetTestEngineUserManager(); + if (userManagers.Count == 0) + { + testState.LoadExtensionModules(logger); + userManagers = testState.GetTestEngineUserManager(); + } + + var match = userManagers.Where(x => x.Name.Equals(userAuth)).FirstOrDefault(); + + if (match == null) + { + throw new InvalidDataException($"Unable to find user auth {userAuth}"); + } + + return match; + }) + .AddTransient(sp => + { + var testState = sp.GetRequiredService(); + var testWebProviders = testState.GetTestEngineWebProviders(); + if (testWebProviders.Count == 0) + { + testState.LoadExtensionModules(logger); + testWebProviders = testState.GetTestEngineWebProviders(); + } + + var match = testWebProviders.Where(x => x.Name.Equals(provider)).FirstOrDefault(); + + if (match == null) + { + throw new InvalidDataException($"Unable to find provider {provider}"); + } + + + return match; + }) + .AddSingleton(sp => + { + var testState = sp.GetRequiredService(); + var testAuthProviders = testState.GetTestEngineAuthProviders(); + if (testAuthProviders.Count == 0) + { + testState.LoadExtensionModules(logger); + testAuthProviders = testState.GetTestEngineAuthProviders(); + } + + var match = testAuthProviders.Where(x => x.Name.Equals(auth)).FirstOrDefault(); + + if (match == null) + { + match = new DefaultUserCertificateProvider(); + } + + return match; + }) + .AddSingleton() + .AddSingleton() + .AddScoped() + .AddScoped() + .AddScoped((sp) => sp.GetRequiredService().GetLogger()) + .AddSingleton() + .AddScoped() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .BuildServiceProvider(); + + TestEngine testEngine = serviceProvider.GetRequiredService(); + + // Default value for optional arguments is set before the class library is invoked. + // The class library expects actual types in its input arguments, so optional arguments + // to the Test Engine entry point function RunTestAsync must be checked for null values and their + // corresponding default values set beforehand. + var testPlanFile = new FileInfo(inputOptions.TestPlanFile); + var tenantId = Guid.Parse(inputOptions.TenantId); + var environmentId = inputOptions.EnvironmentId; + var domain = string.Empty; + var queryParams = ""; + + DirectoryInfo outputDirectory; + const string DefaultOutputDirectory = "TestOutput"; + var _fileSystem = serviceProvider.GetRequiredService(); + if (!string.IsNullOrEmpty(inputOptions.OutputDirectory)) + { + if (Path.IsPathRooted(inputOptions.OutputDirectory.Trim())) + { + Console.WriteLine("[Critical Error]: Please provide a relative path for the output."); + return; + } + else + { + outputDirectory = new DirectoryInfo(Path.Combine(_fileSystem.GetDefaultRootTestEngine(), inputOptions.OutputDirectory.Trim())); + } + } + else + { + outputDirectory = new DirectoryInfo(Path.Combine(_fileSystem.GetDefaultRootTestEngine(), DefaultOutputDirectory.Trim())); + } + + if (!string.IsNullOrEmpty(inputOptions.QueryParams)) + { + queryParams = inputOptions.QueryParams; + } + + if (!string.IsNullOrEmpty(inputOptions.Domain)) + { + domain = inputOptions.Domain; + } + + string modulePath = Path.GetDirectoryName(typeof(Program).Assembly.Location); + List modules = new List(); + if (!string.IsNullOrEmpty(inputOptions.Modules) && Directory.Exists(inputOptions.Modules)) + { + modulePath = inputOptions.Modules; + } + + ITestState state = serviceProvider.GetService(); + state.SetModulePath(modulePath); + + if (!string.IsNullOrEmpty(inputOptions.Record)) + { + state.SetRecordMode(); + } + + //setting defaults for optional parameters outside RunTestAsync + var testResult = await testEngine.RunTestAsync(testPlanFile, environmentId, tenantId, outputDirectory, domain, queryParams); + if (testResult != "InvalidOutputDirectory") + { + Console.WriteLine($"Test results can be found here: {testResult}"); + } + + } + catch (Exception ex) + { + Console.WriteLine("[Critical Error]: " + ex.Message); + Console.WriteLine(ex); + } + } + } + } +} diff --git a/src/PowerAppsTestEngineWrapper/app.config b/src/PowerAppsTestEngineWrapper/app.config new file mode 100644 index 000000000..4823ae093 --- /dev/null +++ b/src/PowerAppsTestEngineWrapper/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/PowerAppsTestEngine/config.json b/src/PowerAppsTestEngineWrapper/config.json similarity index 100% rename from src/PowerAppsTestEngine/config.json rename to src/PowerAppsTestEngineWrapper/config.json diff --git a/src/testengine.auth.certificatestore.tests/testengine.auth.certificatestore.tests.csproj b/src/testengine.auth.certificatestore.tests/testengine.auth.certificatestore.tests.csproj index d7330e097..c1bc72231 100644 --- a/src/testengine.auth.certificatestore.tests/testengine.auth.certificatestore.tests.csproj +++ b/src/testengine.auth.certificatestore.tests/testengine.auth.certificatestore.tests.csproj @@ -32,6 +32,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/testengine.auth.certificatestore/testengine.auth.certificatestore.csproj b/src/testengine.auth.certificatestore/testengine.auth.certificatestore.csproj index 615a482b0..609116737 100644 --- a/src/testengine.auth.certificatestore/testengine.auth.certificatestore.csproj +++ b/src/testengine.auth.certificatestore/testengine.auth.certificatestore.csproj @@ -1,7 +1,7 @@ - net8.0 + netstandard2.0 enable enable © Microsoft Corporation. All rights reserved. @@ -32,9 +32,5 @@ - - - - diff --git a/src/testengine.common.user/LoginState.cs b/src/testengine.common.user/LoginState.cs index 04d5717eb..2bb6bf52e 100644 --- a/src/testengine.common.user/LoginState.cs +++ b/src/testengine.common.user/LoginState.cs @@ -23,7 +23,7 @@ public class LoginState public string? MatchHost { get; set; } - public Func? CallbackDesiredUrlFound { get; set; } - public Func? CallbackErrorFound { get; set; } + public Func CallbackDesiredUrlFound { get; set; } = null; + public Func CallbackErrorFound { get; set; } = null; } } diff --git a/src/testengine.common.user/PowerPlatformLogin.cs b/src/testengine.common.user/PowerPlatformLogin.cs index 4040ac944..f20a510ed 100644 --- a/src/testengine.common.user/PowerPlatformLogin.cs +++ b/src/testengine.common.user/PowerPlatformLogin.cs @@ -32,7 +32,7 @@ public virtual async Task HandleCommonLoginState(LoginState state) { if (!state.Module.Settings.ContainsKey(ERROR_DIALOG_KEY)) { - state.Module.Settings.TryAdd(ERROR_DIALOG_KEY, title); + state.Module.Settings.Add(ERROR_DIALOG_KEY, title); } else { diff --git a/src/testengine.common.user/testengine.common.user.csproj b/src/testengine.common.user/testengine.common.user.csproj index 1045532fe..511a5fb14 100644 --- a/src/testengine.common.user/testengine.common.user.csproj +++ b/src/testengine.common.user/testengine.common.user.csproj @@ -1,7 +1,7 @@  - net8.0 + netstandard2.0 enable enable @@ -37,8 +37,4 @@ - - - - diff --git a/src/testengine.module.mda.tests/testengine.module.mda.tests.csproj b/src/testengine.module.mda.tests/testengine.module.mda.tests.csproj index c3d52a01a..7aeca2830 100644 --- a/src/testengine.module.mda.tests/testengine.module.mda.tests.csproj +++ b/src/testengine.module.mda.tests/testengine.module.mda.tests.csproj @@ -27,6 +27,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/testengine.module.mda/testengine.module.mda.csproj b/src/testengine.module.mda/testengine.module.mda.csproj index d694f415f..6cfef8e99 100644 --- a/src/testengine.module.mda/testengine.module.mda.csproj +++ b/src/testengine.module.mda/testengine.module.mda.csproj @@ -1,7 +1,7 @@  - net8.0 + netstandard2.0 enable enable Microsoft @@ -40,10 +40,10 @@ - + - + @@ -54,7 +54,4 @@ - - - diff --git a/src/testengine.module.pause.tests/testengine.module.pause.tests.csproj b/src/testengine.module.pause.tests/testengine.module.pause.tests.csproj index ff092b827..59fb32f4b 100644 --- a/src/testengine.module.pause.tests/testengine.module.pause.tests.csproj +++ b/src/testengine.module.pause.tests/testengine.module.pause.tests.csproj @@ -27,6 +27,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/testengine.module.pause/testengine.module.pause.csproj b/src/testengine.module.pause/testengine.module.pause.csproj index 1e3a3f7f9..bccca538e 100644 --- a/src/testengine.module.pause/testengine.module.pause.csproj +++ b/src/testengine.module.pause/testengine.module.pause.csproj @@ -1,7 +1,7 @@  - net8.0 + netstandard2.0 enable enable Microsoft @@ -40,10 +40,10 @@ - + - + @@ -54,7 +54,4 @@ - - - diff --git a/src/testengine.module.playwrightaction.tests/testengine.module.playwrightaction.tests.csproj b/src/testengine.module.playwrightaction.tests/testengine.module.playwrightaction.tests.csproj index 2ac168054..724d11033 100644 --- a/src/testengine.module.playwrightaction.tests/testengine.module.playwrightaction.tests.csproj +++ b/src/testengine.module.playwrightaction.tests/testengine.module.playwrightaction.tests.csproj @@ -22,6 +22,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/testengine.module.playwrightaction/testengine.module.playwrightaction.csproj b/src/testengine.module.playwrightaction/testengine.module.playwrightaction.csproj index cf15edc06..004b87e25 100644 --- a/src/testengine.module.playwrightaction/testengine.module.playwrightaction.csproj +++ b/src/testengine.module.playwrightaction/testengine.module.playwrightaction.csproj @@ -1,7 +1,7 @@  - net8.0 + netstandard2.0 enable enable @@ -29,11 +29,4 @@ - - - - diff --git a/src/testengine.module.playwrightscript.tests/testengine.module.playwrightscript.tests.csproj b/src/testengine.module.playwrightscript.tests/testengine.module.playwrightscript.tests.csproj index 1e3b41ffc..f6ca161c4 100644 --- a/src/testengine.module.playwrightscript.tests/testengine.module.playwrightscript.tests.csproj +++ b/src/testengine.module.playwrightscript.tests/testengine.module.playwrightscript.tests.csproj @@ -19,8 +19,8 @@ - - + + diff --git a/src/testengine.module.playwrightscript/testengine.module.playwrightscript.csproj b/src/testengine.module.playwrightscript/testengine.module.playwrightscript.csproj index 7a33d31e6..59fef6028 100644 --- a/src/testengine.module.playwrightscript/testengine.module.playwrightscript.csproj +++ b/src/testengine.module.playwrightscript/testengine.module.playwrightscript.csproj @@ -1,7 +1,7 @@  - net8.0 + netstandard2.0 enable enable true @@ -40,8 +40,4 @@ - - - - diff --git a/src/testengine.module.powerapps.portal.tests/testengine.module.powerapps.portal.tests.csproj b/src/testengine.module.powerapps.portal.tests/testengine.module.powerapps.portal.tests.csproj index c625fdd9e..a7b94c563 100644 --- a/src/testengine.module.powerapps.portal.tests/testengine.module.powerapps.portal.tests.csproj +++ b/src/testengine.module.powerapps.portal.tests/testengine.module.powerapps.portal.tests.csproj @@ -28,6 +28,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/testengine.module.powerapps.portal/testengine.module.powerapps.portal.csproj b/src/testengine.module.powerapps.portal/testengine.module.powerapps.portal.csproj index ef2c015bf..94631d8e6 100644 --- a/src/testengine.module.powerapps.portal/testengine.module.powerapps.portal.csproj +++ b/src/testengine.module.powerapps.portal/testengine.module.powerapps.portal.csproj @@ -1,6 +1,6 @@  - net8.0 + netstandard2.0 enable enable Microsoft @@ -47,10 +47,11 @@ - + - + + @@ -61,7 +62,4 @@ - - - diff --git a/src/testengine.module.sample/testengine.module.sample.csproj b/src/testengine.module.sample/testengine.module.sample.csproj index a27eb222b..2b6f427e1 100644 --- a/src/testengine.module.sample/testengine.module.sample.csproj +++ b/src/testengine.module.sample/testengine.module.sample.csproj @@ -1,6 +1,6 @@  - net8.0 + netstandard2.0 enable enable Microsoft @@ -33,8 +33,8 @@ - - + + @@ -45,7 +45,4 @@ - - - diff --git a/src/testengine.module.simulation/SimulateConnectorFunction.cs b/src/testengine.module.simulation/SimulateConnectorFunction.cs index d7df9cbf1..92930e2d1 100644 --- a/src/testengine.module.simulation/SimulateConnectorFunction.cs +++ b/src/testengine.module.simulation/SimulateConnectorFunction.cs @@ -6,7 +6,6 @@ using System.Web; using Microsoft.Extensions.Logging; using Microsoft.Playwright; -using Microsoft.Playwright.Core; using Microsoft.PowerApps.TestEngine.Config; using Microsoft.PowerApps.TestEngine.TestInfra; using Microsoft.PowerFx; @@ -15,7 +14,6 @@ using Microsoft.PowerFx.Types; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using static System.Runtime.InteropServices.JavaScript.JSType; namespace testengine.module { diff --git a/src/testengine.module.simulation/testengine.module.simulation.csproj b/src/testengine.module.simulation/testengine.module.simulation.csproj index e0b4ac722..d8b090a18 100644 --- a/src/testengine.module.simulation/testengine.module.simulation.csproj +++ b/src/testengine.module.simulation/testengine.module.simulation.csproj @@ -1,6 +1,6 @@ - net8.0 + netstandard2.0 enable enable @@ -21,10 +21,10 @@ - + - + @@ -35,7 +35,4 @@ - - - diff --git a/src/testengine.module.tests.common/testengine.module.tests.common.csproj b/src/testengine.module.tests.common/testengine.module.tests.common.csproj index 8b0ae1058..97b890aa1 100644 --- a/src/testengine.module.tests.common/testengine.module.tests.common.csproj +++ b/src/testengine.module.tests.common/testengine.module.tests.common.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/testengine.modules.simulation.tests/testengine.modules.simulation.tests.csproj b/src/testengine.modules.simulation.tests/testengine.modules.simulation.tests.csproj index 2d74832f7..b2b9693fb 100644 --- a/src/testengine.modules.simulation.tests/testengine.modules.simulation.tests.csproj +++ b/src/testengine.modules.simulation.tests/testengine.modules.simulation.tests.csproj @@ -27,6 +27,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/testengine.provider.canvas/testengine.provider.canvas.csproj b/src/testengine.provider.canvas/testengine.provider.canvas.csproj index 87fe7045d..b86a90334 100644 --- a/src/testengine.provider.canvas/testengine.provider.canvas.csproj +++ b/src/testengine.provider.canvas/testengine.provider.canvas.csproj @@ -1,6 +1,6 @@ - net8.0 + netstandard2.0 enable enable © Microsoft Corporation. All rights reserved. @@ -52,7 +52,6 @@ - diff --git a/src/testengine.provider.mda.tests/testengine.provider.mda.tests.csproj b/src/testengine.provider.mda.tests/testengine.provider.mda.tests.csproj index d059778dd..85397ea79 100644 --- a/src/testengine.provider.mda.tests/testengine.provider.mda.tests.csproj +++ b/src/testengine.provider.mda.tests/testengine.provider.mda.tests.csproj @@ -42,6 +42,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/testengine.provider.mda/ModelDrivenApplicationCanvasState.cs b/src/testengine.provider.mda/ModelDrivenApplicationCanvasState.cs index d58d59473..d93671bc3 100644 --- a/src/testengine.provider.mda/ModelDrivenApplicationCanvasState.cs +++ b/src/testengine.provider.mda/ModelDrivenApplicationCanvasState.cs @@ -1,4 +1,5 @@ using System.Dynamic; +using System.Threading.Tasks; using ICSharpCode.Decompiler.DebugInfo; using Microsoft.PowerApps.TestEngine.Config; using Microsoft.PowerApps.TestEngine.TestInfra; @@ -12,7 +13,7 @@ namespace testengine.provider.mda public class ModelDrivenApplicationCanvasState { public Dictionary VariableState { get; set; } = new Dictionary(); - public Dictionary CollectionState { get; set; } = new Dictionary(); + public Dictionary CollectionState { get; set; } = new Dictionary(); /// /// Query the state of the browser and update the Power Fx state @@ -90,7 +91,7 @@ public async Task ApplyChanges(ITestInfraFunctions testInfraFunctions, ModelDriv } else { - originalState.VariableState.TryAdd(variable, await originalState.ConvertToVariableState(newPowerFxVariableValue)); + originalState.VariableState.Add(variable, await originalState.ConvertToVariableState(newPowerFxVariableValue)); } } } @@ -121,7 +122,14 @@ public async Task ApplyChanges(ITestInfraFunctions testInfraFunctions, ModelDriv { // Add the new collction and cache a copy of the collection state originalEngine.UpdateVariable(collection, newPowerFxCollectionValue); - originalState.CollectionState.TryAdd(collection, await originalState.ConvertToVariableState(newPowerFxCollectionValue)); + if (originalState.CollectionState.ContainsKey(collection)) + { + originalState.CollectionState[collection] = await originalState.ConvertToVariableState(newPowerFxCollectionValue); + } + else + { + originalState.CollectionState.Add(collection, await originalState.ConvertToVariableState(newPowerFxCollectionValue)); + } } } } @@ -261,7 +269,10 @@ private void HandleCollectionValueChange(string collection, ModelDrivenApplicati if (value is ObjectRecordValue) { var objectValue = (ObjectRecordValue)value; - return JsonConvert.SerializeObject(objectValue.ToObject()); + if (objectValue.ToObject() is ExpandoObject expando) + { + return JsonConvert.SerializeObject(expando); + } } } return null; @@ -618,7 +629,7 @@ protected override bool TryGetField(FormulaType fieldType, string fieldName, out } // Override ToObject method - public override ExpandoObject ToObject() + public override Object ToObject() { // Convert the Dictionary to an ExpandoObject ExpandoObject expandoObject = new ExpandoObject(); diff --git a/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs b/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs index 4a53da302..bb5934431 100644 --- a/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs +++ b/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs @@ -372,7 +372,7 @@ public async Task EmbedMDAJSScripts(string resourceName, string embeddedScriptNa using (var memoryStream = new MemoryStream()) { await stream.CopyToAsync(memoryStream); - scriptHash = "sha256-" + Convert.ToBase64String(SHA256.HashData(memoryStream.ToArray())); + scriptHash = "sha256-" + Convert.ToBase64String(SHA256.Create().ComputeHash(memoryStream.ToArray())); } string scriptUrl = $"/{embeddedScriptName}?hash={scriptHash}"; var opt = new PageAddScriptTagOptions() diff --git a/src/testengine.provider.mda/testengine.provider.mda.csproj b/src/testengine.provider.mda/testengine.provider.mda.csproj index a0e9593bf..332c00e91 100644 --- a/src/testengine.provider.mda/testengine.provider.mda.csproj +++ b/src/testengine.provider.mda/testengine.provider.mda.csproj @@ -1,7 +1,7 @@ - net8.0 + netstandard2.0 enable enable © Microsoft Corporation. All rights reserved. @@ -32,6 +32,10 @@ + + + + @@ -48,7 +52,4 @@ - - - diff --git a/src/testengine.provider.powerapps.portal/testengine.provider.powerapps.portal.csproj b/src/testengine.provider.powerapps.portal/testengine.provider.powerapps.portal.csproj index fdfa2431f..068beb88c 100644 --- a/src/testengine.provider.powerapps.portal/testengine.provider.powerapps.portal.csproj +++ b/src/testengine.provider.powerapps.portal/testengine.provider.powerapps.portal.csproj @@ -1,7 +1,7 @@ - net8.0 + netstandard2.0 enable enable © Microsoft Corporation. All rights reserved. @@ -40,8 +40,4 @@ - - - - diff --git a/src/testengine.user.storagestate/StorageStateUserManagerModule.cs b/src/testengine.user.storagestate/StorageStateUserManagerModule.cs index 2d977ef20..fc6e4354a 100644 --- a/src/testengine.user.storagestate/StorageStateUserManagerModule.cs +++ b/src/testengine.user.storagestate/StorageStateUserManagerModule.cs @@ -262,6 +262,10 @@ public bool IsValidEmail(string emailAddress) { try { + if (string.IsNullOrEmpty(emailAddress)) + { + return false; + } var email = new MailAddress(emailAddress); return email.Address == emailAddress.Trim(); } @@ -271,7 +275,7 @@ public bool IsValidEmail(string emailAddress) } } - public string GetUserNameFromEmail(string? emailAddress) + public string GetUserNameFromEmail(string emailAddress) { if (string.IsNullOrEmpty(emailAddress)) { diff --git a/src/testengine.user.storagestate/testengine.user.storagestate.csproj b/src/testengine.user.storagestate/testengine.user.storagestate.csproj index 8027b06e0..9cb666a33 100644 --- a/src/testengine.user.storagestate/testengine.user.storagestate.csproj +++ b/src/testengine.user.storagestate/testengine.user.storagestate.csproj @@ -1,8 +1,8 @@  - net8.0 + netstandard2.0 enable - enable + disable @@ -35,8 +35,4 @@ - - - - diff --git a/targets/Program.cs b/targets/Program.cs index be4f72188..491f1c152 100644 --- a/targets/Program.cs +++ b/targets/Program.cs @@ -54,6 +54,8 @@ static void Main(string[] args) var solution = Path.Combine(SrcDir, "PowerAppsTestEngine.sln"); var project = Path.Combine(PATestEngineDir, "Microsoft.PowerApps.TestEngine.csproj"); + var projectTestEngine2 = Path.Combine(SrcDir, "PowerAppsTestEngineWrapper", "PowerAppsTestEngineWrapper.csproj"); + var customPackageId = "Microsoft.PowerApps.TestEngine"; Target("squeaky-clean", () => @@ -82,6 +84,9 @@ static void Main(string[] args) Target("pack", () => RunDotnet("pack", $"{EscapePath(project)} --configuration {options.Configuration} --no-build -o {options.OutputDirectory} -p:Packing=true", gitExists, LogDir)); + Target("pack-AlphaV2", + () => RunDotnet("pack", $"{EscapePath(projectTestEngine2)} --configuration {options.Configuration} --no-build -o {options.OutputDirectory} -p:Packing=true /p:PackageID={customPackageId}", gitExists, LogDir)); + Target("ci", DependsOn("squeaky-clean", "rebuild", "test"));