├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── pull_request_template.md ├── .gitignore ├── .vscode └── extensions.json ├── CONTRIBUTING.md ├── GitVersion.yml ├── LICENCE ├── README.md ├── azure-pipelines-pull-request.yml ├── azure-pipelines.yml ├── bindings ├── .editorconfig ├── Capgemini.PowerApps.SpecFlowBindings.sln ├── src │ ├── Capgemini.PowerApps.SpecFlowBindings.MSBuild │ │ ├── Capgemini.PowerApps.SpecFlowBindings.MSBuild.csproj │ │ └── ExtendDataFiles.cs │ └── Capgemini.PowerApps.SpecFlowBindings │ │ ├── Capgemini.PowerApps.SpecFlowBindings.csproj │ │ ├── Configuration │ │ ├── BrowserCredentialsYamlTypeConverter.cs │ │ ├── BrowserOptionsWithProfileSupport.cs │ │ ├── ClientCredentials.cs │ │ ├── ConfigHelper.cs │ │ ├── TestConfiguration.cs │ │ └── UserConfiguration.cs │ │ ├── Extensions │ │ ├── BrowserTypeExtensions.cs │ │ ├── DirectoryInfoExtensions.cs │ │ ├── DriverOptionsExtensions.cs │ │ ├── EntityExtensions.cs │ │ ├── FieldExtensions.cs │ │ ├── GridExtensions.cs │ │ ├── IListExtensions.cs │ │ ├── MultiSelectOptionSetExtensions.cs │ │ ├── StringExtensions.cs │ │ └── SubGridExtensions.cs │ │ ├── FileDataRepository.cs │ │ ├── Hooks │ │ ├── AfterScenarioHooks.cs │ │ └── BeforeRunHooks.cs │ │ ├── ITestDataRepository.cs │ │ ├── ITestDriver.cs │ │ ├── PowerAppsStepDefiner.cs │ │ ├── Steps │ │ ├── BusinessProcessFlowSteps.cs │ │ ├── CommandBarSteps.cs │ │ ├── DashboardSteps.cs │ │ ├── DataSteps.cs │ │ ├── DialogSteps.cs │ │ ├── EntitySteps.cs │ │ ├── EntitySubGridSteps.cs │ │ ├── GlobalSearchSteps.cs │ │ ├── GridSteps.cs │ │ ├── LoginSteps.cs │ │ ├── LookupDialogSteps.cs │ │ ├── LookupSteps.cs │ │ ├── NavigationSteps.cs │ │ ├── QuickCreateSteps.cs │ │ ├── RelatedGridSteps.cs │ │ ├── TimelineSteps.cs │ │ └── UtilitySteps.cs │ │ ├── TestDriver.cs │ │ ├── Transformations │ │ ├── EasyReproTransformations.cs │ │ └── UtilityTransformations.cs │ │ ├── build │ │ └── Capgemini.PowerApps.SpecFlowBindings.targets │ │ ├── content │ │ └── power-apps-bindings.yml │ │ └── icon.png └── tests │ ├── Capgemini.PowerApps.SpecFlowBindings.UiTests │ ├── .runsettings │ ├── BusinessProcessFlowSteps.feature │ ├── Capgemini.PowerApps.SpecFlowBindings.UiTests.csproj │ ├── CodeCoverage.runsettings │ ├── CommandBarSteps.feature │ ├── DashboardSteps.feature │ ├── Data │ │ ├── a contact.json │ │ ├── a contact │ │ │ └── that has been extended with @extend.json │ │ ├── a different team.json │ │ ├── a record configured for global search.json │ │ ├── a record referencing the record with an alias using @alias.bind.json │ │ ├── a record with a business process flow.json │ │ ├── a record with a subgrid and related records.json │ │ ├── a record with a timeline on the main form.json │ │ ├── a record with an alias.json │ │ ├── a secondary mock record.json │ │ ├── a team.json │ │ └── data decorated with faker moustache syntax.json │ ├── DataSteps.feature │ ├── DialogSteps.feature │ ├── EntitySteps.feature │ ├── EntitySubGridSteps.feature │ ├── GlobalSearchSteps.feature │ ├── GridSteps.feature │ ├── Hooks │ │ ├── AfterScenarioHooks.cs │ │ └── MockSolutionHooks.cs │ ├── LoginSteps.feature │ ├── LookupDialogSteps.feature │ ├── LookupSteps.feature │ ├── NavigationSteps.feature │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── QuickCreateSteps.feature │ ├── RelatedGridSteps.feature │ ├── Steps │ │ └── TestEnvironmentConfigSteps.cs │ ├── TimelineSteps.feature │ ├── power-apps-bindings.yml │ └── reqnroll.json │ └── sb_PowerAppsSpecFlowBindings_Mock │ ├── sb_PowerAppsSpecFlowBindings_Mock.cdsproj │ └── src │ ├── AppModuleSiteMaps │ └── sb_MockApp │ │ ├── AppModuleSiteMap.xml │ │ └── AppModuleSiteMap_managed.xml │ ├── AppModules │ └── sb_MockApp │ │ ├── AppModule.xml │ │ └── AppModule_managed.xml │ ├── Dashboards │ ├── {50687e45-f839-eb11-a813-000d3a0b97ca}.xml │ └── {6e43bd18-f839-eb11-a813-000d3a0b97ca}.xml │ ├── Entities │ ├── sb_MockRecord │ │ ├── Entity.xml │ │ ├── FormXml │ │ │ ├── card │ │ │ │ ├── {159912fd-c3fb-42c2-b058-922b3a8e3523}.xml │ │ │ │ └── {159912fd-c3fb-42c2-b058-922b3a8e3523}_managed.xml │ │ │ ├── main │ │ │ │ ├── {974e0b5b-0d50-40fd-b607-61bb0812dda0}.xml │ │ │ │ ├── {974e0b5b-0d50-40fd-b607-61bb0812dda0}_managed.xml │ │ │ │ ├── {e75d54f4-033a-eb11-a813-000d3a0b97ca}.xml │ │ │ │ └── {e75d54f4-033a-eb11-a813-000d3a0b97ca}_managed.xml │ │ │ └── quick │ │ │ │ ├── {6ac2352e-0322-4c77-b1e8-0f9ed141ef6b}.xml │ │ │ │ └── {6ac2352e-0322-4c77-b1e8-0f9ed141ef6b}_managed.xml │ │ ├── RibbonDiff.xml │ │ └── SavedQueries │ │ │ ├── {1f735b93-a07a-43bb-9df8-aa452cc8e44d}.xml │ │ │ ├── {31f43b0f-0029-406a-aa44-253697a2e30a}.xml │ │ │ ├── {3e00e84b-f203-4a23-a48b-0b1e13cf3cd9}.xml │ │ │ ├── {8bcb3608-d091-44d6-837e-733a09b9f30f}.xml │ │ │ ├── {8e4f2039-bded-4966-8bec-68c4994b931a}.xml │ │ │ ├── {b2f87496-0a45-41b3-ba94-d54dc90afe00}.xml │ │ │ └── {fe19a300-f739-eb11-bf68-000d3a0b851b}.xml │ ├── sb_SecondaryMockRecord │ │ ├── Entity.xml │ │ ├── FormXml │ │ │ ├── card │ │ │ │ ├── {1b1eb19c-f074-41e8-96dd-2f4504cc23d7}.xml │ │ │ │ └── {1b1eb19c-f074-41e8-96dd-2f4504cc23d7}_managed.xml │ │ │ ├── main │ │ │ │ ├── {64f94a07-433f-4a64-a290-16238d710ab3}.xml │ │ │ │ └── {64f94a07-433f-4a64-a290-16238d710ab3}_managed.xml │ │ │ ├── quick │ │ │ │ ├── {80e3b3be-baa1-4abc-9ff3-1ed82ed2d5aa}.xml │ │ │ │ └── {80e3b3be-baa1-4abc-9ff3-1ed82ed2d5aa}_managed.xml │ │ │ └── quickCreate │ │ │ │ ├── {5cdd3da8-0b3a-eb11-a813-000d3a0b97ca}.xml │ │ │ │ └── {5cdd3da8-0b3a-eb11-a813-000d3a0b97ca}_managed.xml │ │ ├── RibbonDiff.xml │ │ └── SavedQueries │ │ │ ├── {0f56be21-ac88-4347-b464-fd97bacfa84c}.xml │ │ │ ├── {22ea6a27-f739-eb11-bf68-000d3a0b851b}.xml │ │ │ ├── {42b8b4df-d57c-45fd-ba06-5f19234f35e1}.xml │ │ │ ├── {be892914-e72f-446b-b351-8bff8684e42d}.xml │ │ │ ├── {ca54c145-3bf1-4d00-99ae-3db3056f1372}.xml │ │ │ ├── {cbf2061b-2054-4bc9-80ab-bffdade82ceb}.xml │ │ │ └── {d81f44f7-26ec-484e-8435-dfd7649fac76}.xml │ ├── sb_primarybusinessprocessflow │ │ ├── Entity.xml │ │ ├── FormXml │ │ │ └── main │ │ │ │ ├── {f5a00a01-01ad-42b4-a39a-aecb626c0fb9}.xml │ │ │ │ └── {f5a00a01-01ad-42b4-a39a-aecb626c0fb9}_managed.xml │ │ ├── Formulas │ │ │ └── sb_primarybusinessprocessflow-bpf_duration.xaml │ │ ├── RibbonDiff.xml │ │ └── SavedQueries │ │ │ ├── {16ad7b3f-073a-eb11-a813-000d3a0b97ca}.xml │ │ │ ├── {19e7392f-cff5-4320-86d4-496f32b80583}.xml │ │ │ ├── {2bef1fb0-8cab-448d-aa81-0f72c09824ad}.xml │ │ │ ├── {48c12097-b8f9-4532-951d-dbb3d900b48c}.xml │ │ │ ├── {522b22fd-1dbc-4843-8c17-4f51514cde84}.xml │ │ │ ├── {61aa85f2-5063-4530-8ec1-251a4326ef01}.xml │ │ │ ├── {9cfb31fe-0842-48e3-b34e-3cd7b7425666}.xml │ │ │ ├── {ba760875-c5cd-4fe0-8e2f-17d9a1842656}.xml │ │ │ ├── {d8cdfb4a-b8f1-493b-94af-c01778a00efd}.xml │ │ │ └── {fa0636aa-9c5a-4f3c-ae17-2734cfdf336d}.xml │ └── sb_secondarybusinessprocessflow │ │ ├── Entity.xml │ │ ├── FormXml │ │ └── main │ │ │ ├── {c6b374b2-7d8f-44e1-b2a5-641d2765d7ef}.xml │ │ │ └── {c6b374b2-7d8f-44e1-b2a5-641d2765d7ef}_managed.xml │ │ ├── Formulas │ │ └── sb_secondarybusinessprocessflow-bpf_duration.xaml │ │ ├── RibbonDiff.xml │ │ └── SavedQueries │ │ ├── {00f6e446-66c0-42b9-aa57-3eef587a8d35}.xml │ │ ├── {1cf3fb71-073a-eb11-a813-000d3a0b97ca}.xml │ │ ├── {21d48f05-fad9-4fab-9de5-f31c95f88bcb}.xml │ │ ├── {282c6496-6c2a-4080-8235-701dee0cdeaf}.xml │ │ ├── {34c0c9da-73fc-4548-aef8-5782e1ec1484}.xml │ │ ├── {64057e18-a232-4fd2-8a6f-2cf8c99cd7ae}.xml │ │ ├── {7a94bf0c-7f33-40cc-bc33-345a0653527e}.xml │ │ ├── {7e8cb7b6-779f-4935-941b-11cc6c48e8e4}.xml │ │ ├── {8acd2346-ea45-42ed-8cd5-be3e2b038b87}.xml │ │ └── {e8ff9ab9-753d-4b5a-9a5c-2b8d567b9aef}.xml │ ├── OptionSets │ └── sb_choice.xml │ ├── Other │ ├── Customizations.xml │ ├── Relationships.xml │ ├── Relationships │ │ ├── Account.xml │ │ ├── BusinessUnit.xml │ │ ├── Contact.xml │ │ ├── Organization.xml │ │ ├── Owner.xml │ │ ├── ProcessStage.xml │ │ ├── SystemUser.xml │ │ ├── Team.xml │ │ ├── TransactionCurrency.xml │ │ ├── Workflow.xml │ │ ├── sb_MockRecord.xml │ │ └── sb_SecondaryMockRecord.xml │ └── Solution.xml │ ├── WebResources │ └── sb_ │ │ └── js │ │ ├── sb_mockrecord.form.js │ │ ├── sb_mockrecord.form.js.data.xml │ │ ├── sb_mockrecord.ribbon.js │ │ └── sb_mockrecord.ribbon.js.data.xml │ └── Workflows │ ├── MockBusinessProcessError-A35908FE-0FCD-4EB7-826C-405286DE4D9D.xaml │ ├── MockBusinessProcessError-A35908FE-0FCD-4EB7-826C-405286DE4D9D.xaml.data.xml │ ├── PrimaryBusinessProcessFlow-35D6A39A-C720-4D5F-8F11-4497DFF2F9E3.xaml │ ├── PrimaryBusinessProcessFlow-35D6A39A-C720-4D5F-8F11-4497DFF2F9E3.xaml.data.xml │ ├── SecondaryBusinessProcessFlow-D21A6986-EB11-429C-B4B1-BB747EEB0991.xaml │ └── SecondaryBusinessProcessFlow-D21A6986-EB11-429C-B4B1-BB747EEB0991.xaml.data.xml ├── driver ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── karma.conf.js ├── package-lock.json ├── package.json ├── src │ ├── data │ │ ├── createOptions.ts │ │ ├── dataManager.ts │ │ ├── deepInsertResponse.ts │ │ ├── deepInsertService.ts │ │ ├── fakerPreprocessor.ts │ │ ├── index.ts │ │ ├── preprocessor.ts │ │ ├── record.ts │ │ └── testRecord.ts │ ├── driver.ts │ ├── index.ts │ ├── repositories │ │ ├── authenticatedRecordRepository.ts │ │ ├── currentUserRecordRepository.ts │ │ ├── genericRecordRepository.ts │ │ ├── index.ts │ │ ├── metadataRepository.ts │ │ └── recordRepository.ts │ ├── requests │ │ ├── associateRequest.ts │ │ ├── index.ts │ │ └── request.ts │ └── types │ │ └── xrm.d.ts ├── test │ ├── data │ │ ├── dataManager.spec.ts │ │ ├── deepInsertService.spec.ts │ │ └── fakerPreprocessor.spec.ts │ ├── driver.spec.ts │ ├── repositories │ │ ├── authenticatedUserRecordRepository.spec.ts │ │ ├── currentUserRecordRepository.spec.ts │ │ └── metadataRepository.spec.ts │ └── requests │ │ └── associateRequest.spec.ts ├── tsconfig.json └── webpack.config.js ├── scripts ├── Remove-InvalidSymbolsPackageFiles.ps1 ├── Set-AllUserLocalesToUk.ps1 └── Sync-DataverseUsers.ps1 └── templates ├── build-and-test-job.yml └── build-and-test-stages.yml /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe the bug 11 | _A clear and concise description of what the bug is._ 12 | 13 | ## To reproduce 14 | 15 | _Steps to reproduce the behaviour._ 16 | 17 | ## Expected behaviour 18 | _A clear and concise description of what you expected to happen._ 19 | 20 | ## Screenshots 21 | _If applicable, add screenshots to help explain your problem._ 22 | 23 | ## Environment (please complete the following information): 24 | - Test framework [e.g. NUnit/MSTest/xUnit] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | - Power Apps environment version 28 | 29 | ## Additional context 30 | _Add any other context about the problem here._ 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Is your feature request related to a problem? Please describe 11 | _A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]_ 12 | 13 | ## Describe the solution you'd like 14 | _A clear and concise description of what you want to happen._ 15 | 16 | ## Additional context 17 | _Add any other context or screenshots about the feature request here._ 18 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | _Describe the problem or feature in addition to a link to the issue(s)._ 3 | 4 | ## Approach 5 | _How does this change address the problem?_ 6 | 7 | ## TODOs 8 | - [ ] Automated test coverage for new code 9 | - [ ] Documentation updated (if required) 10 | - [ ] Build and tests successful 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint" 4 | ] 5 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Please first discuss the change you wish to make via an issue before making a change. 4 | 5 | ## Pull request process 6 | 7 | 1. Ensure that there are automated tests that cover any changes 8 | 1. Update the README.md with details of any significant changes to functionality 9 | 1. Ensure that your commit messages increment the version using [GitVersion syntax](https://gitversion.readthedocs.io/en/latest/input/docs/more-info/version-increments/). If no message is found then the patch version will be incremented by default. 10 | 1. You may merge the pull request once it meets all of the required checks. If you do not have permision, a reviewer will do it for you -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: mainline -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT Licence 2 | 3 | Copyright (c) 2020 Capgemini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /azure-pipelines-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: $(GITVERSION_FullSemVer) 2 | 3 | trigger: none 4 | 5 | pr: 6 | - master 7 | 8 | pool: 9 | vmImage: 'windows-latest' 10 | 11 | stages: 12 | - template: templates/build-and-test-stages.yml -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | name: $(GITVERSION_FullSemVer) 2 | 3 | trigger: 4 | batch: true 5 | branches: 6 | include: 7 | - master 8 | 9 | pr: none 10 | 11 | pool: 12 | vmImage: windows-latest 13 | stages: 14 | - template: templates/build-and-test-stages.yml 15 | - stage: Publish 16 | displayName: Publish 17 | jobs: 18 | - job: PublishJob 19 | displayName: Publish 20 | variables: 21 | SemVer: $[ stageDependencies.BuildAndTest.BuildAndTestJob.outputs['OutputSemVerTask.SemVer'] ] 22 | steps: 23 | - checkout: none 24 | - download: current 25 | displayName: Download NuGet package artifact 26 | artifact: Capgemini.PowerApps.SpecFlowBindings 27 | - task: NuGetCommand@2 28 | displayName: Push to NuGet.org 29 | inputs: 30 | command: push 31 | packagesToPush: '$(Pipeline.Workspace)/Capgemini.PowerApps.SpecFlowBindings/*.nupkg' 32 | nuGetFeedType: external 33 | publishFeedCredentials: Capgemini_UK 34 | - task: GitHubRelease@1 35 | displayName: Create GitHub releaes 36 | condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) 37 | inputs: 38 | gitHubConnection: 'GitHub (ewingjm)' 39 | repositoryName: '$(Build.Repository.Name)' 40 | action: 'create' 41 | target: '$(Build.SourceVersion)' 42 | tagSource: 'userSpecifiedTag' 43 | tag: 'v$(SemVer)' 44 | releaseNotesSource: 'inline' 45 | assets: '$(Pipeline.Workspace)/Capgemini.PowerApps.SpecFlowBindings/*' 46 | isPreRelease: true 47 | changeLogCompareToRelease: 'lastNonDraftRelease' 48 | changeLogType: commitBased 49 | -------------------------------------------------------------------------------- /bindings/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # SA1633: File should have header 4 | dotnet_diagnostic.SA1633.severity = none 5 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings.MSBuild/Capgemini.PowerApps.SpecFlowBindings.MSBuild.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Configuration/BrowserCredentialsYamlTypeConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Configuration 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | using Microsoft.Dynamics365.UIAutomation.Browser; 7 | using YamlDotNet.Core; 8 | using YamlDotNet.Core.Events; 9 | using YamlDotNet.Serialization; 10 | 11 | /// 12 | /// for . 13 | /// 14 | public sealed class BrowserCredentialsYamlTypeConverter : IYamlTypeConverter 15 | { 16 | /// 17 | public bool Accepts(Type type) 18 | { 19 | return type == typeof(BrowserCredentials); 20 | } 21 | 22 | /// 23 | public object ReadYaml(IParser parser, Type type) 24 | { 25 | var dictionary = new Dictionary(); 26 | 27 | parser.Consume(); 28 | while (!parser.TryConsume(out _)) 29 | { 30 | dictionary.Add(parser.Consume().Value.ToLower(CultureInfo.CurrentCulture), parser.Consume().Value); 31 | } 32 | 33 | return new BrowserCredentials(dictionary["username"], dictionary["password"]); 34 | } 35 | 36 | /// 37 | public void WriteYaml(IEmitter emitter, object value, Type type) 38 | { 39 | throw new NotImplementedException(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Configuration/ClientCredentials.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Configuration 2 | { 3 | using YamlDotNet.Serialization; 4 | 5 | /// 6 | /// Configuration for the test application user. 7 | /// 8 | public class ClientCredentials 9 | { 10 | private string tenantId; 11 | private string clientId; 12 | private string clientSecret; 13 | 14 | /// 15 | /// Gets or sets the tenant ID of the test application user app registration. 16 | /// 17 | [YamlMember(Alias = "tenantId")] 18 | public string TenantId { get => ConfigHelper.GetEnvironmentVariableIfExists(this.tenantId); set => this.tenantId = value; } 19 | 20 | /// 21 | /// Gets or sets the client ID of the test application user app registration. 22 | /// 23 | [YamlMember(Alias = "clientId")] 24 | public string ClientId { get => ConfigHelper.GetEnvironmentVariableIfExists(this.clientId); set => this.clientId = value; } 25 | 26 | /// 27 | /// Gets or sets a client secret or the name of an environment variable containing the client secret of the test application user app registration. 28 | /// 29 | [YamlMember(Alias = "clientSecret")] 30 | public string ClientSecret { get => ConfigHelper.GetEnvironmentVariableIfExists(this.clientSecret); set => this.clientSecret = value; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Configuration/ConfigHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Configuration 2 | { 3 | using System; 4 | 5 | /// 6 | /// Helper methods for configuration classes. 7 | /// 8 | public static class ConfigHelper 9 | { 10 | /// 11 | /// Returns the value of an environment variable if it exists. Alternatively, returns the passed in value. 12 | /// 13 | /// The value which may be the name of an environment variable. 14 | /// The environment variable value (if found) or the passed in value. 15 | public static string GetEnvironmentVariableIfExists(string value) 16 | { 17 | if (string.IsNullOrEmpty(value)) 18 | { 19 | return value; 20 | } 21 | 22 | var environmentVariableValue = Environment.GetEnvironmentVariable(value); 23 | 24 | if (!string.IsNullOrEmpty(environmentVariableValue)) 25 | { 26 | return environmentVariableValue; 27 | } 28 | 29 | return value; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Configuration/UserConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Configuration 2 | { 3 | using YamlDotNet.Serialization; 4 | 5 | /// 6 | /// A user that tests can run as. 7 | /// 8 | public class UserConfiguration 9 | { 10 | private string username; 11 | private string password; 12 | private string otptoken; 13 | 14 | /// 15 | /// Gets or sets the username of the user. 16 | /// 17 | [YamlMember(Alias = "username")] 18 | public string Username { get => ConfigHelper.GetEnvironmentVariableIfExists(this.username); set => this.username = value; } 19 | 20 | /// 21 | /// Gets or sets the password of the user. 22 | /// 23 | [YamlMember(Alias = "password")] 24 | public string Password { get => ConfigHelper.GetEnvironmentVariableIfExists(this.password); set => this.password = value; } 25 | 26 | /// 27 | /// Gets or sets the alias of the user (used to retrieve from configuration). 28 | /// 29 | [YamlMember(Alias = "alias")] 30 | public string Alias { get; set; } 31 | 32 | /// 33 | /// Gets or sets the OTP token of the user. 34 | /// 35 | [YamlMember(Alias = "otptoken")] 36 | public string OtpToken { get => ConfigHelper.GetEnvironmentVariableIfExists(this.otptoken); set => this.otptoken = value; } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Extensions/BrowserTypeExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Extensions 2 | { 3 | using Microsoft.Dynamics365.UIAutomation.Browser; 4 | 5 | /// 6 | /// Provides extension methods on . 7 | /// 8 | public static class BrowserTypeExtensions 9 | { 10 | /// 11 | /// Determines if the given browser type supports profiles. 12 | /// 13 | /// The to check. 14 | /// true if the browser supports profiles otherwise false. 15 | public static bool SupportsProfiles(this BrowserType type) 16 | { 17 | switch (type) 18 | { 19 | case BrowserType.IE: 20 | return false; 21 | case BrowserType.Chrome: 22 | return true; 23 | case BrowserType.Firefox: 24 | return true; 25 | case BrowserType.Edge: 26 | return false; 27 | case BrowserType.Remote: 28 | return false; 29 | default: 30 | return false; 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Extensions/DirectoryInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Extensions 2 | { 3 | using System.IO; 4 | 5 | /// 6 | /// Extensions to the class. 7 | /// 8 | public static class DirectoryInfoExtensions 9 | { 10 | /// 11 | /// Copies the directory recursively to the target directory. 12 | /// 13 | /// The source directory. 14 | /// The target directory. 15 | public static void CopyTo(this DirectoryInfo source, DirectoryInfo target) 16 | { 17 | Directory.CreateDirectory(target.FullName); 18 | 19 | foreach (var fileInfo in source.GetFiles()) 20 | { 21 | fileInfo.CopyTo(Path.Combine(target.FullName, fileInfo.Name), true); 22 | } 23 | 24 | foreach (var subdirectory in source.GetDirectories()) 25 | { 26 | subdirectory.CopyTo(target.CreateSubdirectory(subdirectory.Name)); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Extensions/DriverOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Extensions 2 | { 3 | using OpenQA.Selenium; 4 | using OpenQA.Selenium.Chrome; 5 | using OpenQA.Selenium.Firefox; 6 | using OpenQA.Selenium.IE; 7 | 8 | /// 9 | /// Extensions to the class. 10 | /// 11 | public static class DriverOptionsExtensions 12 | { 13 | /// 14 | /// Adds a global capability to driver options. 15 | /// 16 | /// The driver options. 17 | /// The name of the capability. 18 | /// The value of the capability. 19 | internal static void AddGlobalCapability(this DriverOptions options, string name, object value) 20 | { 21 | switch (options) 22 | { 23 | case ChromeOptions chromeOptions: 24 | chromeOptions.AddAdditionalCapability(name, value, true); 25 | break; 26 | case FirefoxOptions firefoxOptions: 27 | firefoxOptions.AddAdditionalCapability(name, value, true); 28 | break; 29 | case InternetExplorerOptions internetExplorerOptions: 30 | internetExplorerOptions.AddAdditionalCapability(name, value, true); 31 | break; 32 | default: 33 | options.AddAdditionalCapability(name, value); 34 | break; 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Extensions/EntityExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Extensions 2 | { 3 | using System; 4 | using Microsoft.Dynamics365.UIAutomation.Api.UCI; 5 | using OpenQA.Selenium; 6 | 7 | /// 8 | /// Extensions to the class. 9 | /// 10 | public static class EntityExtensions 11 | { 12 | /// 13 | /// This is required as throws a when getting a hidden field. 14 | /// 15 | /// The entity. 16 | /// The Selenium WebDriver. 17 | /// The name of the field. 18 | /// Whether the field is visible. 19 | public static bool IsFieldVisible(this Entity entity, IWebDriver driver, string fieldName) 20 | { 21 | driver = driver ?? throw new ArgumentNullException(nameof(driver)); 22 | 23 | var elements = driver.FindElements(By.CssSelector($"div[data-id={fieldName}]")); 24 | 25 | return elements.Count > 0; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Extensions/FieldExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Extensions 2 | { 3 | using System; 4 | using System.Collections.ObjectModel; 5 | using System.Linq; 6 | using Microsoft.Dynamics365.UIAutomation.Api.UCI; 7 | using OpenQA.Selenium; 8 | 9 | /// 10 | /// Extensions to the class. 11 | /// 12 | public static class FieldExtensions 13 | { 14 | /// 15 | /// This is required as the property does not work. 16 | /// 17 | /// The field. 18 | /// The Selenium WebDriver. 19 | /// Whether the field is read-only. 20 | public static bool IsReadOnly(this Field field, IWebDriver driver) 21 | { 22 | field = field ?? throw new ArgumentNullException(nameof(field)); 23 | 24 | driver = driver ?? throw new ArgumentNullException(nameof(driver)); 25 | 26 | ReadOnlyCollection lockedIcons = driver.FindElements(By.CssSelector($"div[data-id={field.Name}-locked-iconWrapper]")); 27 | 28 | return lockedIcons.Any(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Extensions/GridExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Extensions 2 | { 3 | using System; 4 | using System.Linq; 5 | using Microsoft.Dynamics365.UIAutomation.Api.UCI; 6 | using Microsoft.Dynamics365.UIAutomation.Browser; 7 | using OpenQA.Selenium; 8 | 9 | /// 10 | /// Extensions to the class. 11 | /// 12 | public static class GridExtensions 13 | { 14 | /// 15 | /// Gets the index of a record by ID. 16 | /// Accessing grid item IDs or URLs via EasyRepro is currently broken (see https://github.com/microsoft/EasyRepro/issues/800). Use this method instead. 17 | /// 18 | /// The Grid. 19 | /// The Selenium WebDriver. 20 | /// The ID of the record. 21 | /// The index of the record. 22 | public static int GetRecordIndexById(this Grid grid, IWebDriver driver, Guid recordId) 23 | { 24 | if (driver is null) 25 | { 26 | throw new ArgumentNullException(nameof(driver)); 27 | } 28 | 29 | driver.WaitUntilAvailable(By.XPath(AppElements.Xpath[AppReference.Grid.Container])); 30 | 31 | var index = (long)driver.ExecuteScript($"return Object.keys(getCurrentXrmStatus().mainGrid._grid.getRows()._collection).indexOf(\"{recordId}\");"); 32 | if (index == -1) 33 | { 34 | throw new Exception($"A record with an ID of {recordId} is not present in the grid"); 35 | } 36 | 37 | return Convert.ToInt32(index); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Extensions/IListExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Extensions 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | /// 7 | /// Extensions for . 8 | /// 9 | internal static class IListExtensions 10 | { 11 | private static readonly Random Rand = new Random(); 12 | 13 | /// 14 | /// Randomises the order of a list. 15 | /// 16 | /// The type of list items. 17 | /// The list. 18 | /// The shuffled list. 19 | internal static IList Shuffle(this IList list) 20 | { 21 | var n = list.Count; 22 | 23 | while (n > 1) 24 | { 25 | n--; 26 | var k = Rand.Next(n + 1); 27 | T value = list[k]; 28 | list[k] = list[n]; 29 | list[n] = value; 30 | } 31 | 32 | return list; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Extensions 2 | { 3 | using System; 4 | using System.Globalization; 5 | using System.Text.RegularExpressions; 6 | 7 | /// 8 | /// Extensions to the class. 9 | /// 10 | public static class StringExtensions 11 | { 12 | /// 13 | /// Looks for templated text in string and replaces. 14 | /// 15 | /// The input string. 16 | /// The resulting string. 17 | public static string ReplaceTemplatedText(this string input) 18 | { 19 | input = input ?? throw new ArgumentNullException(nameof(input)); 20 | 21 | MatchCollection matches = Regex.Matches(input, "{{.*}}"); 22 | var random = new Random(); 23 | 24 | foreach (Match match in matches) 25 | { 26 | string innerText = match.Value.Substring(2, match.Value.Length - 4); 27 | string[] templateSplit = innerText.Split(':'); 28 | 29 | if (templateSplit[0].ToUpperInvariant() == "RANDNUM") 30 | { 31 | string[] randomBound = templateSplit[1].Split(','); 32 | int lowerBound = int.Parse(randomBound[0], CultureInfo.CurrentCulture); 33 | int upperBound = int.Parse(randomBound[1], CultureInfo.CurrentCulture); 34 | 35 | input = input 36 | .Remove( 37 | match.Index, 38 | match.Length) 39 | .Insert( 40 | match.Index, 41 | random 42 | .Next(lowerBound, upperBound) 43 | .ToString(CultureInfo.InvariantCulture)); 44 | } 45 | else 46 | { 47 | throw new NotImplementedException($"ReplaceTemplatedText does not support {templateSplit[0]}."); 48 | } 49 | } 50 | 51 | return input; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/FileDataRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings 2 | { 3 | using System.IO; 4 | using System.Reflection; 5 | 6 | /// 7 | /// Reads test data from JSON files. 8 | /// 9 | public class FileDataRepository : ITestDataRepository 10 | { 11 | private const string FileDirectory = "data"; 12 | private static readonly string RootDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 13 | 14 | /// 15 | public string GetTestData(string identifier) 16 | { 17 | return File.ReadAllText(Path.Combine(RootDirectory, FileDirectory, Path.GetExtension(identifier) == ".json" ? identifier : $"{identifier}.json")); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Hooks/BeforeRunHooks.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Hooks 2 | { 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Capgemini.PowerApps.SpecFlowBindings.Configuration; 8 | using Capgemini.PowerApps.SpecFlowBindings.Steps; 9 | using Microsoft.Dynamics365.UIAutomation.Api.UCI; 10 | using Microsoft.Dynamics365.UIAutomation.Browser; 11 | using Reqnroll; 12 | 13 | /// 14 | /// Hooks that run before the start of each run. 15 | /// 16 | [Binding] 17 | public class BeforeRunHooks : PowerAppsStepDefiner 18 | { 19 | /// 20 | /// Creates a new folder for the scenario and copies the session/cookies information from previous runs. 21 | /// 22 | [BeforeTestRun] 23 | public static void BaseProfileSetup() 24 | { 25 | if (!TestConfig.UseProfiles) 26 | { 27 | return; 28 | } 29 | 30 | Parallel.ForEach(UserProfileDirectories.Keys, (username) => 31 | { 32 | var profileDirectory = UserProfileDirectories[username]; 33 | var baseDirectory = Path.Combine(profileDirectory, "base"); 34 | 35 | // SpecFlow isolation settings may run scenarios in different processes or AppDomains and [BeforeTestRun] runs per thread. Lock statement insufficient. 36 | using (var mutex = new Mutex(true, $"{nameof(BaseProfileSetup)}-{username}", out var createdNew)) 37 | { 38 | if (!createdNew) 39 | { 40 | mutex.WaitOne(); 41 | } 42 | 43 | if (Directory.Exists(baseDirectory)) 44 | { 45 | mutex.ReleaseMutex(); 46 | return; 47 | } 48 | 49 | try 50 | { 51 | Directory.CreateDirectory(baseDirectory); 52 | 53 | var userBrowserOptions = (BrowserOptionsWithProfileSupport)TestConfig.BrowserOptions.Clone(); 54 | userBrowserOptions.ProfileDirectory = baseDirectory; 55 | userBrowserOptions.Headless = true; 56 | 57 | var webClient = new WebClient(userBrowserOptions); 58 | using (var app = new XrmApp(webClient)) 59 | { 60 | var user = TestConfig.Users.First(u => u.Username == username); 61 | app.OnlineLogin.Login(TestConfig.GetTestUrl(), user.Username.ToSecureString(), user.Password.ToSecureString()); 62 | } 63 | } 64 | finally 65 | { 66 | mutex.ReleaseMutex(); 67 | } 68 | } 69 | }); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/ITestDataRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings 2 | { 3 | /// 4 | /// Provides an interface for a test data repository. 5 | /// 6 | public interface ITestDataRepository 7 | { 8 | /// 9 | /// Retrieves the test data for a given identifier. 10 | /// 11 | /// The identifier to use. 12 | /// A JSON string representing the test data. 13 | string GetTestData(string identifier); 14 | } 15 | } -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/ITestDriver.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings 2 | { 3 | using Microsoft.Xrm.Sdk; 4 | 5 | /// 6 | /// An interface for a test driver used to perform setup and teardown via the Power Apps Client APIs. 7 | /// 8 | public interface ITestDriver 9 | { 10 | /// 11 | /// Injects the driver onto the current page. 12 | /// 13 | /// The application user auth token (if configured). 14 | void InjectOnPage(string authToken); 15 | 16 | /// 17 | /// Loads scenario test data. 18 | /// 19 | /// The data to load. 20 | /// The username of the user to impersonate. 21 | void LoadTestDataAsUser(string data, string username); 22 | 23 | /// 24 | /// Loads scenario test data. 25 | /// 26 | /// The data to load. 27 | void LoadTestData(string data); 28 | 29 | /// 30 | /// Deletes scenario test data. 31 | /// 32 | void DeleteTestData(); 33 | 34 | /// 35 | /// Open a test record. 36 | /// 37 | /// The alias of the record. 38 | void OpenTestRecord(string recordAlias); 39 | 40 | /// 41 | /// Gets an entity reference to a previously created test record. 42 | /// 43 | /// The alias of the test record. 44 | /// A reference to the created record. 45 | EntityReference GetTestRecordReference(string recordAlias); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Steps/CommandBarSteps.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Steps 2 | { 3 | using Capgemini.PowerApps.SpecFlowBindings.Extensions; 4 | using FluentAssertions; 5 | using Reqnroll; 6 | 7 | /// 8 | /// Step bindings relating to the command bar. 9 | /// 10 | [Binding] 11 | public class CommandBarSteps : PowerAppsStepDefiner 12 | { 13 | /// 14 | /// Selects a command with the given label. 15 | /// 16 | /// The label of the command. 17 | [When("I select the '(.*)' command")] 18 | public static void WhenISelectTheCommand(string commandName) 19 | { 20 | XrmApp.CommandBar.ClickCommand(commandName); 21 | } 22 | 23 | /// 24 | /// Selects a command under a flyout with the given label. 25 | /// 26 | /// The label of the command. 27 | /// The label of the flyout. 28 | [When("I select the '([^']+)' command under the '([^']+)' flyout")] 29 | public static void WhenISelectTheCommandUnderTheFlyout(string commandName, string flyoutName) 30 | { 31 | XrmApp.CommandBar.ClickCommand(flyoutName, commandName); 32 | } 33 | 34 | /// 35 | /// Asserts that a command is available in the command bar. 36 | /// 37 | /// The label of the command. 38 | [Then("I can see the '(.*)' command")] 39 | public static void ThenICanSeeTheCommand(string commandName) 40 | { 41 | XrmApp.CommandBar.GetCommandValues(true).Value.Should().Contain(commandName); 42 | } 43 | 44 | /// 45 | /// Asserts that a command is available in the command bar. 46 | /// 47 | /// The label of the command. 48 | [Then("I can not see the '(.*)' command")] 49 | public static void ThenICanNotSeeTheCommand(string commandName) 50 | { 51 | XrmApp.CommandBar.GetCommandValues(true).Value.Should().NotContain(commandName); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Steps/DashboardSteps.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Steps 2 | { 3 | using Reqnroll; 4 | 5 | /// 6 | /// Steps relating to dashboards. 7 | /// 8 | [Binding] 9 | public class DashboardSteps : PowerAppsStepDefiner 10 | { 11 | /// 12 | /// Selects a dashboard. 13 | /// 14 | /// Dashboard name. 15 | [When("I select the '(.*)' dashboard")] 16 | public static void WhenISelectTheDashboard(string dashboardName) 17 | { 18 | XrmApp.Dashboard.SelectDashboard(dashboardName); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Steps/DataSteps.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Steps 2 | { 3 | using System.Configuration; 4 | using Capgemini.PowerApps.SpecFlowBindings; 5 | using Microsoft.Dynamics365.UIAutomation.Browser; 6 | using Microsoft.Identity.Client; 7 | using Reqnroll; 8 | 9 | /// 10 | /// Test setup step bindings for data. 11 | /// 12 | [Binding] 13 | public class DataSteps : PowerAppsStepDefiner 14 | { 15 | /// 16 | /// Opens a test record. 17 | /// 18 | /// The alias of the test record. 19 | [Given(@"I have opened '(.*)'")] 20 | public static void GivenIHaveOpened(string alias) 21 | { 22 | TestDriver.OpenTestRecord(alias); 23 | 24 | Driver.WaitForTransaction(); 25 | } 26 | 27 | /// 28 | /// Creates a test record. 29 | /// 30 | /// The name of the file containing the test record. 31 | [Given(@"I have created '(.*)'")] 32 | public static void GivenIHaveCreated(string fileName) 33 | { 34 | TestDriver.LoadTestData(TestDataRepository.GetTestData(fileName)); 35 | } 36 | 37 | /// 38 | /// Creates a test record as a given user. 39 | /// 40 | /// The user alias. 41 | /// The name of the file containing the test record. 42 | [Given(@"'(.*)' has created '(.*)'")] 43 | public static void GivenIHaveCreated(string alias, string fileName) 44 | { 45 | TestDriver.LoadTestDataAsUser( 46 | TestDataRepository.GetTestData(fileName), 47 | TestConfig.GetUser(alias).Username); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Steps/GlobalSearchSteps.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Steps 2 | { 3 | using Reqnroll; 4 | 5 | /// 6 | /// Steps relating to global search. 7 | /// 8 | [Binding] 9 | public class GlobalSearchSteps : PowerAppsStepDefiner 10 | { 11 | /// 12 | /// Performs an advanced search using the filter attribute and a filter value. 13 | /// 14 | /// Attribute to filter by. 15 | /// Attribute filter value. 16 | [When("I apply a search filter using the filter '(.*)' with the filter value of '(.*)' group")] 17 | public static void WhenIPerformASearchAndFilterByValue(string filterBy, string filterValue) 18 | { 19 | XrmApp.GlobalSearch.Filter(filterBy, filterValue); 20 | } 21 | 22 | /// 23 | /// Performs a wildcard search using a particular filter value. 24 | /// 25 | /// Attribute filter value. 26 | [When("I apply a search filter using the filter '(.*)'")] 27 | public static void WhenIFilterWithValue(string filterValue) 28 | { 29 | XrmApp.GlobalSearch.FilterWith(filterValue); 30 | } 31 | 32 | /// 33 | /// Performs an advanced search using the filter attribute and a filter value. 34 | /// 35 | /// Attribute filter value. 36 | [When("I search globally using the filter '(.*)'")] 37 | public static void WhenIPerformASearch(string filterValue) 38 | { 39 | XrmApp.GlobalSearch.Search(filterValue); 40 | } 41 | 42 | /// 43 | /// Open a record from a global search at a certain row. 44 | /// 45 | /// Attribute to filter by. 46 | /// Attribute filter value. 47 | [When("I open a record from global search on the entity '(.*)' in the position of '(.*)'")] 48 | public static void WhenIOpenARecordFromSearchUsingIndex(string entityName, int recordIndex) 49 | { 50 | XrmApp.GlobalSearch.OpenRecord(entityName, recordIndex); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Steps/LookupDialogSteps.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Steps 2 | { 3 | using Microsoft.Dynamics365.UIAutomation.Browser; 4 | using OpenQA.Selenium; 5 | using Reqnroll; 6 | 7 | /// 8 | /// Step bindings related to lookup dialogs. 9 | /// 10 | [Binding] 11 | public class LookupDialogSteps : PowerAppsStepDefiner 12 | { 13 | /// 14 | /// Searches and Selects the first matching result. 15 | /// 16 | /// The term to search for. 17 | [When("I select '([^']+)' in the lookup dialog")] 18 | public static void WhenISelectInTheLookupDialog(string searchTerm) 19 | { 20 | var fieldContainer = Driver.WaitUntilAvailable(By.CssSelector("div[id=\"lookupDialogContainer\"] div[id=\"lookupDialogLookup\"]")); 21 | var input = fieldContainer.FindElement(By.TagName("input")); 22 | input.Click(); 23 | input.SendKeys(searchTerm); 24 | input.SendKeys(Keys.Enter); 25 | 26 | fieldContainer.WaitUntilAvailable(By.CssSelector("li[data-id*=\"LookupResultsPopup\"]")) 27 | .Click(); 28 | } 29 | 30 | /// 31 | /// Clicks the Add button. 32 | /// 33 | [When("I click Add in the lookup dialog")] 34 | public static void WhenIClickAddInTheLookupDialog() 35 | { 36 | var container = Driver.WaitUntilAvailable(By.CssSelector("div[id=\"lookupDialogFooterContainer\"]")); 37 | 38 | container.FindElement(By.CssSelector("button[data-id*=\"lookupDialogSaveBtn\"]")) 39 | .Click(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Steps/UtilitySteps.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Steps 2 | { 3 | using Capgemini.PowerApps.SpecFlowBindings; 4 | using Reqnroll; 5 | 6 | /// 7 | /// Steps providing various utilities. 8 | /// 9 | [Binding] 10 | public class UtilitySteps : PowerAppsStepDefiner 11 | { 12 | /// 13 | /// Waits for a given number of seconds. 14 | /// 15 | /// The number of seconds to wait. 16 | [When(@"I wait up to '(.*)' seconds")] 17 | public static void WhenIWaitUpToSeconds(int seconds) 18 | { 19 | XrmApp.ThinkTime(seconds * 1000); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Transformations/EasyReproTransformations.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Transformations 2 | { 3 | using Microsoft.Dynamics365.UIAutomation.Api.UCI; 4 | using Reqnroll; 5 | 6 | /// 7 | /// Transformations for EasyRepro types. 8 | /// 9 | [Binding] 10 | public static class EasyReproTransformations 11 | { 12 | /// 13 | /// Transforms a logical name into an option set. 14 | /// 15 | /// The logical name. 16 | /// The option set. 17 | [StepArgumentTransformation] 18 | public static OptionSet TransformFieldNameToOptionSet(string expression) 19 | { 20 | return new OptionSet { Name = expression }; 21 | } 22 | 23 | /// 24 | /// Transforms a logical name into a multi-value option set. 25 | /// 26 | /// The logical name. 27 | /// The multi-value option set. 28 | [StepArgumentTransformation] 29 | public static MultiValueOptionSet TransformFieldNameToMultiValueOptionSet(string expression) 30 | { 31 | return new MultiValueOptionSet { Name = expression }; 32 | } 33 | 34 | /// 35 | /// Transforms a logical name into an lookup item. 36 | /// 37 | /// The logical name. 38 | /// The lookup. 39 | [StepArgumentTransformation] 40 | public static LookupItem TransformFieldNameToLookupItem(string expression) 41 | { 42 | return new LookupItem { Name = expression }; 43 | } 44 | 45 | /// 46 | /// Transforms a logical name into a datetime item. 47 | /// 48 | /// The logical name. 49 | /// The datetime. 50 | [StepArgumentTransformation] 51 | public static DateTimeControl TransformFieldNameToDateTimeItem(string expression) 52 | { 53 | return new DateTimeControl(expression); 54 | } 55 | 56 | /// 57 | /// Transforms a logical name into a boolean item. 58 | /// 59 | /// The logical name. 60 | /// The boolean item. 61 | [StepArgumentTransformation] 62 | public static BooleanItem TransformFieldNameToBooleanItem(string expression) 63 | { 64 | return new BooleanItem { Name = expression }; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/Transformations/UtilityTransformations.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Transformations 2 | { 3 | using System; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using Reqnroll; 7 | 8 | /// 9 | /// General utility transformations. 10 | /// 11 | [Binding] 12 | public static class UtilityTransformations 13 | { 14 | /// 15 | /// Transforms a comma-separated list to a string array. 16 | /// 17 | /// The comma-separated list. 18 | /// A string array. 19 | [StepArgumentTransformation] 20 | public static string[] TransformCommaSeparatedStringsToArray(string expression) 21 | { 22 | return expression?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); 23 | } 24 | 25 | /// 26 | /// Transforms an ordinal number (e.g. 1st, 2nd, 3rd etc.) to a zero-based index. 27 | /// 28 | /// The ordinal number. 29 | /// The zero-based index. 30 | [StepArgumentTransformation(@"(\d+(?:(?:st)|(?:nd)|(?:rd)|(?:th)))")] 31 | public static int TransformOrdinalNumberToZeroBasedIndex(string expression) 32 | { 33 | return Convert.ToInt32(Regex.Match(expression, @"\d+").Value) - 1; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/build/Capgemini.PowerApps.SpecFlowBindings.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | driver.js 5 | PreserveNewest 6 | false 7 | 8 | 9 | power-apps-bindings.yml 10 | PreserveNewest 11 | false 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/content/power-apps-bindings.yml: -------------------------------------------------------------------------------- 1 | url: 2 | useProfiles: false 3 | browserOptions: 4 | browserType: 5 | users: 6 | - username: 7 | password: 8 | alias: -------------------------------------------------------------------------------- /bindings/src/Capgemini.PowerApps.SpecFlowBindings/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capgemini/powerapps-specflow-bindings/5a87748c3499c2343674032d2f9bc657f649a422/bindings/src/Capgemini.PowerApps.SpecFlowBindings/icon.png -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/.runsettings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 4 7 | MethodLevel 8 | 9 | 10 | -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/BusinessProcessFlowSteps.feature: -------------------------------------------------------------------------------- 1 | Feature: Business Process Flow Steps 2 | In order to automate business process flow interaction 3 | As a developer 4 | I want to use pre-existing business process flow bindings 5 | 6 | Background: 7 | Given I am logged in to the 'Mock App' app as 'an admin' 8 | And I have created 'a record with a business process flow' 9 | And I have opened 'the record' 10 | 11 | Scenario: Open and close business process flow 12 | When I select the 'First Stage' stage of the business process flow 13 | And I close the 'First Stage' stage of the business process flow 14 | 15 | Scenario: Set business process flow stage as active 16 | When I click next stage on the the 'First Stage' stage of the business process flow 17 | And I set the 'First Stage' stage of the business process flow as active 18 | 19 | Scenario: Click next stage on business process flow 20 | When I click next stage on the the 'First Stage' stage of the business process flow 21 | 22 | Scenario: Pin stage on business process flow 23 | When I pin the 'First Stage' stage of the business process flow 24 | 25 | Scenario Outline: Enter values on business process flow 26 | When I select the 'First Stage' stage of the business process flow 27 | When I enter '' into the '' field on the business process flow 28 | Then I can see a value of '' in the '' field on the business process flow 29 | 30 | Scenarios: 31 | | column | type | value | 32 | | sb_text | text | Some text | 33 | | sb_number | numeric | 10 | 34 | # | sb_yesno | boolean | false | Currently failing due to https://github.com/microsoft/EasyRepro/issues/1140 35 | #| sb_choice | optionset | Option A | 36 | # | sb_dateandtime | datetime | 1/1/2021 13:00 | Currently failing due to https://github.com/microsoft/EasyRepro/issues/1139 37 | #| sb_dateonly | datetime | 1/1/2021 | 38 | | sb_currency | currency | £10.00 | 39 | 40 | Scenario: Enter lookup value on business process flow 41 | Given I have created 'a secondary mock record' 42 | When I select the 'First Stage' stage of the business process flow 43 | And I enter 'A secondary mock record' into the 'sb_lookup' lookup field on the business process flow 44 | Then I can see a value of 'A secondary mock record' in the 'sb_lookup' lookup field on the business process flow -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/CodeCoverage.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | .*\.SpecFlowBindings.dll 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/CommandBarSteps.feature: -------------------------------------------------------------------------------- 1 | Feature: Command Bar Steps 2 | In order to automate command bar interaction 3 | As a developer 4 | I want to use pre-existing command bar bindings 5 | 6 | Background: 7 | Given I am logged in to the 'Mock App' app as 'an admin' 8 | When I open the 'Mock Records' sub area of the 'Primary Group' group 9 | 10 | Scenario: Assert a command is visible 11 | Then I can see the 'New' command 12 | 13 | Scenario: Assert a command not visible 14 | Then I can not see the 'Missing' command 15 | 16 | Scenario: Select a command 17 | When I select the 'Refresh' command 18 | 19 | @ignore # EasyRepro issue: https://github.com/microsoft/EasyRepro/issues/1087 20 | Scenario: Select a command under a flyout 21 | When I select the 'No Options Available' command under the 'Run Report' flyout -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/DashboardSteps.feature: -------------------------------------------------------------------------------- 1 | Feature: Dashboard Steps 2 | In order to automate dashboard interaction 3 | As a developer 4 | I want to use pre-existing dashboard bindings 5 | 6 | Scenario: Select a dashboard 7 | Given I am logged in to the 'Mock App' app as 'an admin' 8 | When I select the 'Secondary Dashboard' dashboard -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/Data/a contact.json: -------------------------------------------------------------------------------- 1 | { 2 | "@logicalName": "contact", 3 | "@alias": "the contact", 4 | "firstname": "John", 5 | "lastname": "Smith" 6 | } -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/Data/a contact/that has been extended with @extend.json: -------------------------------------------------------------------------------- 1 | { 2 | "@extends": "../a contact", 3 | "firstname": "Jane" 4 | } -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/Data/a different team.json: -------------------------------------------------------------------------------- 1 | { 2 | "@logicalName": "team", 3 | "@alias": "the team", 4 | "name": "A different team" 5 | } -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/Data/a record configured for global search.json: -------------------------------------------------------------------------------- 1 | { 2 | "@logicalName": "sb_mockrecord", 3 | "@alias": "the referenced record", 4 | "sb_name": "A record configured for global search" 5 | } -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/Data/a record referencing the record with an alias using @alias.bind.json: -------------------------------------------------------------------------------- 1 | { 2 | "@logicalName": "sb_secondarymockrecord", 3 | "@alias": "the referencing record", 4 | "sb_name": "The referencing record", 5 | "sb_Parent@alias.bind": "the referenced record" 6 | } -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/Data/a record with a business process flow.json: -------------------------------------------------------------------------------- 1 | { 2 | "@logicalName": "sb_mockrecord", 3 | "@alias": "the record", 4 | "sb_name": "A record with a business process flow", 5 | "sb_yesno": true 6 | } -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/Data/a record with a subgrid and related records.json: -------------------------------------------------------------------------------- 1 | { 2 | "@logicalName": "sb_mockrecord", 3 | "@alias": "the record", 4 | "sb_name": "A record with a subgrid and related records", 5 | "sb_MockRecord_Parent_SecondaryMockRecord": [ 6 | { 7 | "@alias": "the related record", 8 | "sb_name": "A related record", 9 | "sb_text": "Some text", 10 | "sb_number": 10, 11 | "sb_yesno": true, 12 | "sb_choice": 108550000, 13 | "sb_choices": "108550000, 108550001", 14 | "sb_dateandtime": "2021-01-01T13:00:00Z", 15 | "sb_dateonly": "2021-01-31", 16 | "sb_currency": 10.00 17 | }, 18 | { 19 | "@alias": "the additional related record", 20 | "sb_name": "An additional related record" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/Data/a record with a timeline on the main form.json: -------------------------------------------------------------------------------- 1 | { 2 | "@logicalName": "sb_mockrecord", 3 | "@alias": "the record", 4 | "sb_name": "A record with a timeline on the main form" 5 | } 6 | -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/Data/a record with an alias.json: -------------------------------------------------------------------------------- 1 | { 2 | "@logicalName": "sb_mockrecord", 3 | "@alias": "the referenced record", 4 | "sb_name": "The referenced record" 5 | } -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/Data/a secondary mock record.json: -------------------------------------------------------------------------------- 1 | { 2 | "@logicalName": "sb_secondarymockrecord", 3 | "@alias": "the secondary mock record", 4 | "sb_name": "A secondary mock record" 5 | } -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/Data/a team.json: -------------------------------------------------------------------------------- 1 | { 2 | "@logicalName": "team", 3 | "@alias": "the team", 4 | "name": "A team" 5 | } -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/Data/data decorated with faker moustache syntax.json: -------------------------------------------------------------------------------- 1 | { 2 | "@logicalName": "sb_mockrecord", 3 | "@alias": "the faked record", 4 | "sb_name": "{{random.word}}", 5 | "sb_text": "{{random.words}}", 6 | "sb_number@faker.number": "{{random.number}}", 7 | "sb_currency@faker.number": "{{finance.amount}}", 8 | "sb_dateonly@faker.date": "{{date.past}}", 9 | "sb_dateandtime@faker.datetime": "{{date.future}}", 10 | "sb_Lookup": { 11 | "@alias": "a nested faked record", 12 | "sb_name": "{{random.word}}" 13 | } 14 | } -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/DataSteps.feature: -------------------------------------------------------------------------------- 1 | Feature: Data Steps 2 | In order to automate data creation 3 | As a developer 4 | I want to use pre-existing data creation steps 5 | 6 | Background: 7 | Given I am logged in to the 'Mock App' app as 'an admin' 8 | 9 | Scenario: Set a lookup with an alias 10 | And I have created 'a record with an alias' 11 | And I have created 'a record referencing the record with an alias using @alias.bind' 12 | And I have opened 'the referencing record' 13 | Then I can see a value of 'The referenced record' in the 'sb_parent' lookup field 14 | 15 | Scenario: Generate data at run-time with faker 16 | And I have created 'data decorated with faker moustache syntax' 17 | And I have opened 'the faked record' 18 | 19 | Scenario: Generate data as a named user 20 | And 'an aliased user' has created 'a record with an alias' 21 | 22 | Scenario: Create data that extends from other data 23 | And I have created 'a contact that has been extended with @extend' -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/DialogSteps.feature: -------------------------------------------------------------------------------- 1 | Feature: Dialog Steps 2 | In order to automate dialog interaction 3 | As a developer 4 | I want to use pre-existing dialog bindings 5 | 6 | Background: 7 | Given I am logged in to the 'Mock App' app as 'an admin' 8 | And I have created 'a record with an alias' 9 | And I have opened 'the referenced record' 10 | 11 | Scenario: Confirm a confirmation dialog 12 | When I select the 'Show Confirmation Dialog' command 13 | And I confirm when presented with the confirmation dialog 14 | 15 | Scenario: Cancel a confirmation dialog 16 | When I select the 'Show Confirmation Dialog' command 17 | And I cancel when presented with the confirmation dialog 18 | 19 | Scenario: Assign to me on assign dialog 20 | When I select the 'Assign' command 21 | And I assign to me on the assign dialog 22 | 23 | Scenario: Assign to user on assign dialog 24 | When I select the 'Assign' command 25 | And I assign to a user named 'Power Apps Checker Application' on the assign dialog 26 | 27 | Scenario: Assign to team on assign dialog 28 | Given I have created 'a different team' 29 | When I select the 'Assign' command 30 | And I assign to a team named 'A different team' on the assign dialog 31 | 32 | Scenario: Close warning dialog 33 | When I select the 'Show Error Dialog' command 34 | And I close the warning dialog 35 | 36 | Scenario: Click OK on set state dialog 37 | When I select the 'Deactivate' command 38 | And I click ok on the set state dialog 39 | 40 | Scenario: Click cancel on set state dialog 41 | When I select the 'Deactivate' command 42 | And I click cancel on the set state dialog 43 | #TODO: 44 | #Scenario: Cancel publish dialog 45 | #Scenario: Confirm publish dialog 46 | #Scenario: Close opportunity as won 47 | #Scenario: Close opportunity as lost -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/GlobalSearchSteps.feature: -------------------------------------------------------------------------------- 1 | Feature: Global Search Steps 2 | In order to automate global search interaction 3 | As a developer 4 | I want to use pre-existing global search bindings 5 | 6 | Background: 7 | Given I am logged in to the 'Mock App' app as 'an admin' 8 | And I have created 'a record configured for global search' 9 | And the 'sb_mockrecord' entity is enabled for categorized search 10 | 11 | Scenario: Perform a global search 12 | When I search globally using the filter 'Record' 13 | 14 | Scenario: Apply a search filter 15 | When I search globally using the filter 'Record' 16 | And I apply a search filter using the filter 'Mock Record' 17 | 18 | Scenario: Open a record from global search results 19 | When I search globally using the filter 'A record configured for global search' 20 | And I open a record from global search on the entity 'Mock Records' in the position of '0' 21 | Then I am presented with a 'Information' form for the 'sb_mockrecord' entity -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/GridSteps.feature: -------------------------------------------------------------------------------- 1 | Feature: Grid Steps 2 | In order to automate grid interaction 3 | As a developer 4 | I want to use pre-existing grid bindings 5 | 6 | Background: 7 | Given I am logged in to the 'Mock App' app as 'an admin' 8 | 9 | Scenario: Switch grid view 10 | When I open the 'Mock Records' sub area of the 'Primary Group' group 11 | And I switch to the 'Inactive Mock Records' view in the grid 12 | 13 | Scenario: Open a grid record at a given position 14 | Given I have created 'a record with an alias' 15 | When I open the 'Mock Records' sub area of the 'Primary Group' group 16 | And I open the record at position '0' in the grid 17 | Then I am presented with a 'Information' form for the 'sb_mockrecord' entity 18 | 19 | Scenario: Perform grid search 20 | When I open the 'Mock Records' sub area of the 'Primary Group' group 21 | And I search for 'Some text' in the grid 22 | 23 | Scenario: Clear grid search 24 | When I open the 'Mock Records' sub area of the 'Primary Group' group 25 | And I search for 'Some text' in the grid 26 | And I clear the search in the grid 27 | 28 | Scenario: Sort a grid 29 | When I open the 'Mock Records' sub area of the 'Primary Group' group 30 | And I sort the 'Name' column in the grid using the 'Sort A to Z' option 31 | 32 | Scenario: Assert grid contains an aliased record 33 | Given I have created 'a record with an alias' 34 | When I open the 'Mock Records' sub area of the 'Primary Group' group 35 | Then the grid contains 'the referenced record' 36 | 37 | Scenario: Highlight multiple aliased records from a grid 38 | Given I have created 'a record with an alias' 39 | And I have created 'data decorated with faker moustache syntax' 40 | When I open the 'Mock Records' sub area of the 'Primary Group' group 41 | And I select the following records from the grid 42 | | Alias | 43 | | the referenced record | 44 | | the faked record | -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/Hooks/AfterScenarioHooks.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.UiTests.Hooks 2 | { 3 | using System.IO; 4 | using System.Reflection; 5 | using Capgemini.PowerApps.SpecFlowBindings; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using Reqnroll; 8 | 9 | /// 10 | /// After scenario hooks. 11 | /// 12 | [Binding] 13 | public class AfterScenarioHooks : PowerAppsStepDefiner 14 | { 15 | private readonly ScenarioContext scenarioContext; 16 | private readonly TestContext testContext; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The scenario context. 22 | public AfterScenarioHooks(ScenarioContext scenarioContext, TestContext testContext) 23 | { 24 | this.scenarioContext = scenarioContext; 25 | this.testContext = testContext; 26 | } 27 | 28 | /// 29 | /// Publishes the screenshot of the browser when a test fails. 30 | /// 31 | [AfterScenario(Order = 100)] 32 | public void PublishScreenshotForFailedScenario() 33 | { 34 | if (this.scenarioContext.ScenarioExecutionStatus == ScenarioExecutionStatus.TestError) 35 | { 36 | var rootFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 37 | var screenshotsFolder = Path.Combine(rootFolder, "screenshots"); 38 | 39 | var fileName = string.Concat(this.scenarioContext.ScenarioInfo.Title.Split(Path.GetInvalidFileNameChars())); 40 | this.testContext.AddResultFile(Path.Combine(screenshotsFolder, $"{fileName}.jpg")); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/LoginSteps.feature: -------------------------------------------------------------------------------- 1 | Feature: Login Steps 2 | In order to automate login interaction 3 | As a developer 4 | I want to use pre-existing login bindings 5 | 6 | Scenario: Login to a given app as a given user 7 | Given I am logged in to the 'Mock App' app as 'an admin' -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/LookupDialogSteps.feature: -------------------------------------------------------------------------------- 1 | Feature: Lookup Dialog Steps 2 | In order to automate lookup dialog interaction 3 | As a developer 4 | I want to use pre-existing lookup dialog bindings 5 | 6 | Background: 7 | Given I am logged in to the 'Mock App' app as 'an admin' 8 | And I have created 'a record with a subgrid and related records' 9 | And I have created 'a secondary mock record' 10 | And I have opened 'the record' 11 | When I click the 'Add Existing Secondary Mock Record' command on the 'subgrid' subgrid 12 | 13 | Scenario: Select the first result for a search term in a lookup dialog 14 | When I select 'A secondary mock record' in the lookup dialog 15 | 16 | Scenario: Click add button in a lookup dialog 17 | When I select 'A secondary mock record' in the lookup dialog 18 | And I click Add in the lookup dialog -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/LookupSteps.feature: -------------------------------------------------------------------------------- 1 | Feature: Lookup Steps 2 | In order to automate Lookup interaction 3 | As a developer 4 | I want to use pre-existing lookup bindings 5 | 6 | Background: 7 | Given I am logged in to the 'Mock App' app as 'an admin' 8 | When I open the sub area 'Mock Records' under the 'Primary Group' area 9 | And I select the 'New' command 10 | 11 | Scenario: Click new button in lookup 12 | When I search for 'Some text' in the 'sb_lookup' lookup 13 | And I click the new button in the lookup 14 | 15 | Scenario: Open a record at a given position in a lookup 16 | Given I have created 'a secondary mock record' 17 | When I search for 'A secondary mock record' in the 'sb_lookup' lookup 18 | And I open the record at position '0' in the lookup 19 | 20 | Scenario: Perform a search in a lookup 21 | When I search for 'Some text' in the 'sb_lookup' lookup 22 | 23 | Scenario: Switch view in a lookup 24 | When I search for 'Some text' in the 'sb_lookup' lookup 25 | And I switch to the 'Inactive Secondary Mock Records' view in the lookup 26 | 27 | Scenario: Open advanced lookup 28 | When I search for 'Some text' in the 'sb_lookup' lookup 29 | And I click to perform an advanced lookup on 'sb_lookup' lookup 30 | 31 | @ignore # EasyRepro issue: https://github.com/microsoft/EasyRepro/issues/1311 32 | Scenario: Select a related entity in a lookup 33 | When I search for '*' in the 'sb_customer' lookup 34 | And I select the related 'Contacts' entity in the lookup 35 | 36 | Scenario: Assert lookup search results only contain records the given names 37 | Given I have created 'a secondary mock record' 38 | When I search for 'A secondary mock record' in the 'sb_lookup' lookup 39 | Then I can see only the following records in the 'sb_lookup' lookup 40 | | Name | 41 | | A secondary mock record | 42 | 43 | Scenario: Open a record set in a lookup 44 | Given I have created 'data decorated with faker moustache syntax' 45 | And I have opened 'the faked record' 46 | When I select a related 'sb_lookup' lookup field 47 | Then I am presented with a 'Information' form for the 'sb_secondarymockrecord' entity -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/NavigationSteps.feature: -------------------------------------------------------------------------------- 1 | Feature: Navigation Steps 2 | In order to automate navigation interaction 3 | As a developer 4 | I want to use pre-existing navigation bindings 5 | 6 | Background: 7 | Given I am logged in to the 'Mock App' app as 'an admin' 8 | 9 | Scenario: Open global search 10 | When I open global search 11 | 12 | Scenario: Open entity quick create 13 | When I open a quick create for the 'Secondary Mock Record' entity 14 | 15 | Scenario: Open a subarea 16 | When I open the 'Mock Records' sub area of the 'Primary Group' group 17 | 18 | Scenario: Open an area 19 | When I open the 'Secondary Area' area 20 | 21 | Scenario: Sign out 22 | When I sign out 23 | 24 | Scenario: Assert an area is visible 25 | Then I see the 'Primary Area' area 26 | 27 | Scenario: Assert a group is visible 28 | Then I see the 'Primary Group' group 29 | 30 | Scenario: Assert a subarea is visible 31 | Then I see the 'Mock Records' subarea 32 | -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | [assembly: Parallelize(Workers = 0, Scope = ExecutionScope.ClassLevel)] -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/RelatedGridSteps.feature: -------------------------------------------------------------------------------- 1 | Feature: Related Grid Steps 2 | In order to automate related grid interaction 3 | As a developer 4 | I want to use pre-existing related grid bindings 5 | 6 | Background: 7 | Given I am logged in to the 'Mock App' app as 'an admin' 8 | And I have created 'a record with a subgrid and related records' 9 | And I have opened 'the record' 10 | 11 | @ignore # EasyRepo issue: https://github.com/microsoft/EasyRepro/issues/1310 12 | Scenario: Open record at a given position in a related grid 13 | When I open the related 'Secondary Mock Records' tab 14 | And I open the record at position '0' in the related grid 15 | 16 | Scenario: Open related tab 17 | When I open the related 'Activities' tab 18 | 19 | Scenario: Click command in a related grid 20 | When I open the related 'Activities' tab 21 | And I click the 'Add Existing Activity' button on the related grid 22 | 23 | Scenario: Assert a button is not visible on a flyout in a related grid 24 | When I open the related 'Activities' tab 25 | And I click the 'New Activity' button on the related grid 26 | Then I should not see a 'Missing' button in the flyout on the related grid 27 | 28 | Scenario: Assert a button is visible on a flyout in a related grid 29 | When I open the related 'Activities' tab 30 | And I click the 'New Activity' button on the related grid 31 | Then I should see a 'Task' button in the flyout on the related grid -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/Steps/TestEnvironmentConfigSteps.cs: -------------------------------------------------------------------------------- 1 | namespace Capgemini.PowerApps.SpecFlowBindings.Steps 2 | { 3 | using Microsoft.Xrm.Sdk; 4 | using Microsoft.Xrm.Tooling.Connector; 5 | using System.ServiceModel; 6 | using Reqnroll; 7 | 8 | /// 9 | /// Steps relating to configuring the test environment. 10 | /// 11 | [Binding] 12 | public class TestEnvironmentConfigSteps : PowerAppsStepDefiner 13 | { 14 | /// 15 | /// Enables categorized search for the given entity. 16 | /// 17 | /// The name of the entity. 18 | [Given("the '(.*)' entity is enabled for categorized search")] 19 | public static void GivenTheEntityIsEnabledForCategorizedSearch(string entityLogicalName) 20 | { 21 | using (var serviceClient = GetServiceClient()) 22 | { 23 | var request = new OrganizationRequest("SaveEntityGroupConfiguration"); 24 | request.Parameters["EntityGroupName"] = "Mobile Client Search"; 25 | request.Parameters["EntityGroupConfiguration"] = new QuickFindConfigurationCollection { new QuickFindConfiguration(entityLogicalName) }; 26 | 27 | try 28 | { 29 | serviceClient.Execute(request); 30 | } 31 | catch (FaultException ex) 32 | { 33 | // Invalid search entity 34 | if (ex.Detail.ErrorCode == -2147089919) 35 | { 36 | // Already configured as a search entity 37 | return; 38 | } 39 | } 40 | } 41 | } 42 | 43 | private static CrmServiceClient GetServiceClient() 44 | { 45 | var admin = TestConfig.GetUser("an admin"); 46 | 47 | return new CrmServiceClient( 48 | $"AuthType=OAuth; " + 49 | $"Username={admin.Username}; " + 50 | $"Password={admin.Password}; " + 51 | $"Url={TestConfig.GetTestUrl()}; " + 52 | $"AppId=51f81489-12ee-4a9e-aaae-a2591f45987d; " + 53 | $"RedirectUri=app://58145B91-0C36-4500-8554-080854F2AC97; " + 54 | $"LoginPrompt=Auto"); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/TimelineSteps.feature: -------------------------------------------------------------------------------- 1 | Feature: Timeline Steps 2 | In order to automate timeline interaction 3 | As a developer 4 | I want to use pre-existing timeline bindings 5 | 6 | Background: 7 | Given I am logged in to the 'Mock App' app as 'an admin' 8 | And I have created 'a record with a timeline on the main form' 9 | And I have opened 'the record' 10 | 11 | Scenario: Add appointment to timeline 12 | When I add an appointment to the timeline with the subject 'A subject', the description 'A description', the duration '1', and the location 'A location' 13 | 14 | @ignore # EasyRepro issue: https://github.com/microsoft/EasyRepro/issues/1075 15 | Scenario: Add note to timeline 16 | When I add a note to the timeline with the title 'A note' and the body 'A note's body' 17 | 18 | Scenario: Add phone call to timeline 19 | When I add a phone call to the timeline with the subject 'A subject', the description 'A description', the duration '1', and the number '07123456789' 20 | 21 | #Requires D365 CE app in target instance 22 | #Scenario: Add post to timeline 23 | # When I post 'A post' to the timeline 24 | Scenario: Add task to timeline 25 | When I add a task to the timeline with the subject 'A subject', the description 'A description' and the duration '1' 26 | 27 | Scenario: Add email to timeline 28 | Given I have created 'a contact' 29 | When I add an email to the timeline with the subject 'A subject', the duration '1', and the following contacts 30 | | Type | Name | 31 | | To | John Smith | 32 | 33 | Scenario: Click create for appointment on the timeline 34 | Given I have created 'a contact' 35 | When I click create for an 'appointment' on the timeline -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/power-apps-bindings.yml: -------------------------------------------------------------------------------- 1 | url: POWERAPPS_SPECFLOW_BINDINGS_TEST_URL 2 | useProfiles: true 3 | browserOptions: 4 | browserType: Chrome 5 | headless: true 6 | width: 1920 7 | height: 1080 8 | startMaximized: false 9 | driversPath: ChromeWebDriver 10 | additionalCapabilities: 11 | capabilityName: capabilityVaue 12 | applicationUser: 13 | tenantId: POWERAPPS_SPECFLOW_BINDINGS_TEST_TENANTID 14 | clientId: POWERAPPS_SPECFLOW_BINDINGS_TEST_CLIENTID 15 | clientSecret: POWERAPPS_SPECFLOW_BINDINGS_TEST_CLIENTSECRET 16 | users: 17 | - username: POWERAPPS_SPECFLOW_BINDINGS_TEST_ADMIN_USERNAME 18 | password: POWERAPPS_SPECFLOW_BINDINGS_TEST_ADMIN_PASSWORD 19 | alias: an admin 20 | - username: POWERAPPS_SPECFLOW_BINDINGS_TEST_ADMIN_USERNAME2 21 | password: POWERAPPS_SPECFLOW_BINDINGS_TEST_ADMIN_PASSWORD2 22 | alias: an admin 23 | - username: POWERAPPS_SPECFLOW_BINDINGS_TEST_ADMIN_USERNAME 24 | alias: an aliased user -------------------------------------------------------------------------------- /bindings/tests/Capgemini.PowerApps.SpecFlowBindings.UiTests/reqnroll.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindingCulture": { 3 | "language": "en-gb" 4 | }, 5 | "language": { 6 | "feature": "en-gb" 7 | }, 8 | "bindingAssemblies": [ 9 | { "assembly": "Capgemini.PowerApps.SpecFlowBindings" } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/sb_PowerAppsSpecFlowBindings_Mock.cdsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\PowerApps 5 | 6 | 7 | 8 | 9 | 10 | 11 | 854e57e1-86f3-4e78-9d5e-ae836992dc6a 12 | v4.6.2 13 | 14 | net462 15 | PackageReference 16 | src 17 | 18 | 19 | 20 | 21 | Managed 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | PreserveNewest 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/AppModules/sb_MockApp/AppModule.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | sb_MockApp 4 | 1.0.0.0 5 | 953b9fac-1e5e-e611-80d6-00155ded156f 6 | 7 | 0 8 | 1 9 | 1 10 | 4 11 | 0 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/AppModules/sb_MockApp/AppModule_managed.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | sb_MockApp 4 | 1.0.0.0 5 | 953b9fac-1e5e-e611-80d6-00155ded156f 6 | 7 | 0 8 | 1 9 | 1 10 | 4 11 | 0 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_MockRecord/FormXml/quick/{6ac2352e-0322-4c77-b1e8-0f9ed141ef6b}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | {6ac2352e-0322-4c77-b1e8-0f9ed141ef6b} 5 | 1.0 6 | 1 7 | 1 8 |
9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 |
18 | 19 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 1 47 | 1 48 | 49 | 50 | 51 |
52 |
-------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_MockRecord/FormXml/quick/{6ac2352e-0322-4c77-b1e8-0f9ed141ef6b}_managed.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | {6ac2352e-0322-4c77-b1e8-0f9ed141ef6b} 5 | 1.0 6 | 1 7 | 1 8 |
9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 |
18 | 19 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 1 47 | 1 48 | 49 | 50 | 51 |
52 |
-------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_MockRecord/SavedQueries/{1f735b93-a07a-43bb-9df8-aa452cc8e44d}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 0 9 | {1f735b93-a07a-43bb-9df8-aa452cc8e44d} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 1.0 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_MockRecord/SavedQueries/{31f43b0f-0029-406a-aa44-253697a2e30a}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 1 7 | 0 8 | 1 9 | {31f43b0f-0029-406a-aa44-253697a2e30a} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 4 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 1.0 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_MockRecord/SavedQueries/{3e00e84b-f203-4a23-a48b-0b1e13cf3cd9}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {3e00e84b-f203-4a23-a48b-0b1e13cf3cd9} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 1.0 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_MockRecord/SavedQueries/{8bcb3608-d091-44d6-837e-733a09b9f30f}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {8bcb3608-d091-44d6-837e-733a09b9f30f} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 1.0 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_MockRecord/SavedQueries/{8e4f2039-bded-4966-8bec-68c4994b931a}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {8e4f2039-bded-4966-8bec-68c4994b931a} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 2 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 1.0 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_MockRecord/SavedQueries/{b2f87496-0a45-41b3-ba94-d54dc90afe00}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {b2f87496-0a45-41b3-ba94-d54dc90afe00} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 1 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 1.0 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_MockRecord/SavedQueries/{fe19a300-f739-eb11-bf68-000d3a0b851b}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 1 6 | 0 7 | 0 8 | 1 9 | {fe19a300-f739-eb11-bf68-000d3a0b851b} 10 | 8192 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 1.0 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_SecondaryMockRecord/FormXml/quick/{80e3b3be-baa1-4abc-9ff3-1ed82ed2d5aa}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | {80e3b3be-baa1-4abc-9ff3-1ed82ed2d5aa} 5 | 1.0 6 | 1 7 | 1 8 |
9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 |
18 | 19 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 1 47 | 1 48 | 49 | 50 | 51 |
52 |
-------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_SecondaryMockRecord/FormXml/quick/{80e3b3be-baa1-4abc-9ff3-1ed82ed2d5aa}_managed.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | {80e3b3be-baa1-4abc-9ff3-1ed82ed2d5aa} 5 | 1.0 6 | 1 7 | 1 8 |
9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 |
18 | 19 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 1 47 | 1 48 | 49 | 50 | 51 |
52 |
-------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_SecondaryMockRecord/SavedQueries/{0f56be21-ac88-4347-b464-fd97bacfa84c}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {0f56be21-ac88-4347-b464-fd97bacfa84c} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 1.0 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_SecondaryMockRecord/SavedQueries/{22ea6a27-f739-eb11-bf68-000d3a0b851b}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 1 6 | 0 7 | 0 8 | 1 9 | {22ea6a27-f739-eb11-bf68-000d3a0b851b} 10 | 8192 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 1.0 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_SecondaryMockRecord/SavedQueries/{42b8b4df-d57c-45fd-ba06-5f19234f35e1}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {42b8b4df-d57c-45fd-ba06-5f19234f35e1} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 1 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 1.0 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_SecondaryMockRecord/SavedQueries/{be892914-e72f-446b-b351-8bff8684e42d}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 1 7 | 0 8 | 1 9 | {be892914-e72f-446b-b351-8bff8684e42d} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 4 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 1.0 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_SecondaryMockRecord/SavedQueries/{ca54c145-3bf1-4d00-99ae-3db3056f1372}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {ca54c145-3bf1-4d00-99ae-3db3056f1372} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 2 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 1.0 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_SecondaryMockRecord/SavedQueries/{cbf2061b-2054-4bc9-80ab-bffdade82ceb}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {cbf2061b-2054-4bc9-80ab-bffdade82ceb} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 0 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 1.0 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_SecondaryMockRecord/SavedQueries/{d81f44f7-26ec-484e-8435-dfd7649fac76}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 0 9 | {d81f44f7-26ec-484e-8435-dfd7649fac76} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 0 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 1.0 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_primarybusinessprocessflow/RibbonDiff.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_primarybusinessprocessflow/SavedQueries/{16ad7b3f-073a-eb11-a813-000d3a0b97ca}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 1 6 | 0 7 | 0 8 | 1 9 | {16ad7b3f-073a-eb11-a813-000d3a0b97ca} 10 | 8192 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 1.0 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_primarybusinessprocessflow/SavedQueries/{19e7392f-cff5-4320-86d4-496f32b80583}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 0 9 | {19e7392f-cff5-4320-86d4-496f32b80583} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 0 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 1.0 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_primarybusinessprocessflow/SavedQueries/{2bef1fb0-8cab-448d-aa81-0f72c09824ad}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {2bef1fb0-8cab-448d-aa81-0f72c09824ad} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 64 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 1.0 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_primarybusinessprocessflow/SavedQueries/{48c12097-b8f9-4532-951d-dbb3d900b48c}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {48c12097-b8f9-4532-951d-dbb3d900b48c} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 1.0 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_primarybusinessprocessflow/SavedQueries/{522b22fd-1dbc-4843-8c17-4f51514cde84}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {522b22fd-1dbc-4843-8c17-4f51514cde84} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 2 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 1.0 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_primarybusinessprocessflow/SavedQueries/{61aa85f2-5063-4530-8ec1-251a4326ef01}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 0 9 | {61aa85f2-5063-4530-8ec1-251a4326ef01} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 0 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 1.0 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_primarybusinessprocessflow/SavedQueries/{9cfb31fe-0842-48e3-b34e-3cd7b7425666}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 0 9 | {9cfb31fe-0842-48e3-b34e-3cd7b7425666} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 1.0 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_primarybusinessprocessflow/SavedQueries/{ba760875-c5cd-4fe0-8e2f-17d9a1842656}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 1 7 | 0 8 | 1 9 | {ba760875-c5cd-4fe0-8e2f-17d9a1842656} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 4 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 1.0 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_primarybusinessprocessflow/SavedQueries/{d8cdfb4a-b8f1-493b-94af-c01778a00efd}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {d8cdfb4a-b8f1-493b-94af-c01778a00efd} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 1 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 1.0 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_primarybusinessprocessflow/SavedQueries/{fa0636aa-9c5a-4f3c-ae17-2734cfdf336d}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 0 9 | {fa0636aa-9c5a-4f3c-ae17-2734cfdf336d} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 1.0 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_secondarybusinessprocessflow/FormXml/main/{c6b374b2-7d8f-44e1-b2a5-641d2765d7ef}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | {c6b374b2-7d8f-44e1-b2a5-641d2765d7ef} 5 | 1.0 6 | 1 7 | 1 8 |
9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 |
18 | 19 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | 1 39 | 1 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 |
-------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_secondarybusinessprocessflow/FormXml/main/{c6b374b2-7d8f-44e1-b2a5-641d2765d7ef}_managed.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | {c6b374b2-7d8f-44e1-b2a5-641d2765d7ef} 5 | 1.0 6 | 1 7 | 1 8 |
9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 |
18 | 19 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | 1 39 | 1 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 |
-------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_secondarybusinessprocessflow/RibbonDiff.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_secondarybusinessprocessflow/SavedQueries/{00f6e446-66c0-42b9-aa57-3eef587a8d35}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {00f6e446-66c0-42b9-aa57-3eef587a8d35} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 64 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 1.0 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_secondarybusinessprocessflow/SavedQueries/{1cf3fb71-073a-eb11-a813-000d3a0b97ca}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 1 6 | 0 7 | 0 8 | 1 9 | {1cf3fb71-073a-eb11-a813-000d3a0b97ca} 10 | 8192 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 1.0 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_secondarybusinessprocessflow/SavedQueries/{21d48f05-fad9-4fab-9de5-f31c95f88bcb}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 0 9 | {21d48f05-fad9-4fab-9de5-f31c95f88bcb} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 1.0 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_secondarybusinessprocessflow/SavedQueries/{282c6496-6c2a-4080-8235-701dee0cdeaf}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {282c6496-6c2a-4080-8235-701dee0cdeaf} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 1.0 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_secondarybusinessprocessflow/SavedQueries/{34c0c9da-73fc-4548-aef8-5782e1ec1484}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {34c0c9da-73fc-4548-aef8-5782e1ec1484} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 2 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 1.0 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_secondarybusinessprocessflow/SavedQueries/{64057e18-a232-4fd2-8a6f-2cf8c99cd7ae}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 1 7 | 0 8 | 1 9 | {64057e18-a232-4fd2-8a6f-2cf8c99cd7ae} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 4 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 1.0 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_secondarybusinessprocessflow/SavedQueries/{7a94bf0c-7f33-40cc-bc33-345a0653527e}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 1 9 | {7a94bf0c-7f33-40cc-bc33-345a0653527e} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 1 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 1.0 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_secondarybusinessprocessflow/SavedQueries/{7e8cb7b6-779f-4935-941b-11cc6c48e8e4}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 0 9 | {7e8cb7b6-779f-4935-941b-11cc6c48e8e4} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 0 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 1.0 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_secondarybusinessprocessflow/SavedQueries/{8acd2346-ea45-42ed-8cd5-be3e2b038b87}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 0 9 | {8acd2346-ea45-42ed-8cd5-be3e2b038b87} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 1.0 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Entities/sb_secondarybusinessprocessflow/SavedQueries/{e8ff9ab9-753d-4b5a-9a5c-2b8d567b9aef}.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1 5 | 0 6 | 0 7 | 0 8 | 0 9 | {e8ff9ab9-753d-4b5a-9a5c-2b8d567b9aef} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 0 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 1.0 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/OptionSets/sb_choice.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | picklist 4 | 1 5 | 1.0.0.0 6 | 1 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 24 | 29 | 30 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Other/Customizations.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 1033 20 | 21 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Other/Relationships.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Other/Relationships/BusinessUnit.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | OneToMany 5 | 1 6 | 1.0 7 | 0 8 | sb_MockRecord 9 | BusinessUnit 10 | NoCascade 11 | NoCascade 12 | NoCascade 13 | NoCascade 14 | NoCascade 15 | OwningBusinessUnit 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | OneToMany 24 | 1 25 | 1.0 26 | 0 27 | sb_SecondaryMockRecord 28 | BusinessUnit 29 | NoCascade 30 | NoCascade 31 | NoCascade 32 | NoCascade 33 | NoCascade 34 | OwningBusinessUnit 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Other/Relationships/Organization.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | OneToMany 5 | 1 6 | 1.0 7 | 0 8 | sb_primarybusinessprocessflow 9 | Organization 10 | NoCascade 11 | NoCascade 12 | NoCascade 13 | NoCascade 14 | NoCascade 15 | OrganizationId 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | OneToMany 24 | 1 25 | 1.0 26 | 0 27 | sb_secondarybusinessprocessflow 28 | Organization 29 | NoCascade 30 | NoCascade 31 | NoCascade 32 | NoCascade 33 | NoCascade 34 | OrganizationId 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Other/Relationships/Owner.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | OneToMany 5 | 1 6 | 1.0 7 | 0 8 | sb_MockRecord 9 | Owner 10 | NoCascade 11 | NoCascade 12 | NoCascade 13 | NoCascade 14 | NoCascade 15 | OwnerId 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | OneToMany 24 | 1 25 | 1.0 26 | 0 27 | sb_SecondaryMockRecord 28 | Owner 29 | NoCascade 30 | NoCascade 31 | NoCascade 32 | NoCascade 33 | NoCascade 34 | OwnerId 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Other/Relationships/ProcessStage.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | OneToMany 5 | 1 6 | 1.0 7 | 0 8 | sb_primarybusinessprocessflow 9 | ProcessStage 10 | NoCascade 11 | NoCascade 12 | NoCascade 13 | NoCascade 14 | NoCascade 15 | ActiveStageId 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | OneToMany 24 | 1 25 | 1.0 26 | 0 27 | sb_secondarybusinessprocessflow 28 | ProcessStage 29 | NoCascade 30 | NoCascade 31 | NoCascade 32 | NoCascade 33 | NoCascade 34 | ActiveStageId 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Other/Relationships/Team.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | OneToMany 5 | 1 6 | 1.0 7 | 0 8 | sb_MockRecord 9 | Team 10 | NoCascade 11 | NoCascade 12 | NoCascade 13 | NoCascade 14 | NoCascade 15 | OwningTeam 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | OneToMany 24 | 1 25 | 1.0 26 | 0 27 | sb_SecondaryMockRecord 28 | Team 29 | NoCascade 30 | NoCascade 31 | NoCascade 32 | NoCascade 33 | NoCascade 34 | OwningTeam 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Other/Relationships/TransactionCurrency.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | OneToMany 5 | 1 6 | 1.0.0.0 7 | 0 8 | sb_MockRecord 9 | TransactionCurrency 10 | NoCascade 11 | Restrict 12 | NoCascade 13 | NoCascade 14 | NoCascade 15 | TransactionCurrencyId 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | OneToMany 24 | 1 25 | 1.0.0.0 26 | 0 27 | sb_SecondaryMockRecord 28 | TransactionCurrency 29 | NoCascade 30 | Restrict 31 | NoCascade 32 | NoCascade 33 | NoCascade 34 | TransactionCurrencyId 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Other/Relationships/Workflow.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | OneToMany 5 | 1 6 | 1.0 7 | 0 8 | sb_primarybusinessprocessflow 9 | Workflow 10 | NoCascade 11 | NoCascade 12 | NoCascade 13 | NoCascade 14 | NoCascade 15 | ProcessId 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | OneToMany 24 | 1 25 | 1.0 26 | 0 27 | sb_secondarybusinessprocessflow 28 | Workflow 29 | NoCascade 30 | NoCascade 31 | NoCascade 32 | NoCascade 33 | NoCascade 34 | ProcessId 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/WebResources/sb_/js/sb_mockrecord.form.js: -------------------------------------------------------------------------------- 1 | function mockFormNotifications(context) { 2 | context.getFormContext().ui.setFormNotification("A mock info form notification", "INFO", "info_notification"); 3 | context.getFormContext().ui.setFormNotification("A mock warning form notification", "WARNING", "warning_notification"); 4 | context.getFormContext().ui.setFormNotification("A mock error form notification", "ERROR", "error_notification"); 5 | } -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/WebResources/sb_/js/sb_mockrecord.form.js.data.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | {6e19b941-083a-eb11-a813-000d3a0b97ca} 4 | sb_/js/sb_mockrecord.form.js 5 | sb_/js/sb_mockrecord.form.js 6 | 3 7 | 1.0.0.0 8 | 0 9 | 0 10 | <Dependencies><Dependency componentType="WebResource"/></Dependencies> 11 | 1 12 | 1 13 | 0 14 | /WebResources/sb_jssb_mockrecordformjs6E19B941-083A-EB11-A813-000D3A0B97CA 15 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/WebResources/sb_/js/sb_mockrecord.ribbon.js: -------------------------------------------------------------------------------- 1 | function mockLookupDialog(defaultEntityType, additionalEntityTypes, allowMultiSelect, defaultViewId, additionalViewIds) { 2 | var lookupOptions = 3 | { 4 | defaultEntityType, 5 | entityTypes: [defaultEntityType], 6 | allowMultiSelect, 7 | defaultViewId, 8 | viewIds: [defaultViewId], 9 | }; 10 | 11 | if (additionalEntityTypes) { 12 | lookupOptions.entityTypes.push(...additionalEntityTypes.split(",")) 13 | } 14 | if (additionalViewIds) { 15 | lookupOptions.viewIds.push(...additionalViewIds.split(",")); 16 | } 17 | 18 | Xrm.Utility.lookupObjects(lookupOptions); 19 | } 20 | 21 | function mockErrorDialog() { 22 | Xrm.Navigation.openErrorDialog({ message: 'A mock error message.' }) 23 | } 24 | 25 | function mockConfirmationDialog() { 26 | Xrm.Navigation.openConfirmDialog({ text: 'A mock confirmation message.' }); 27 | } -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/WebResources/sb_/js/sb_mockrecord.ribbon.js.data.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | {8c50cecc-0e3a-eb11-a813-000d3a0b97ca} 4 | sb_/js/sb_mockrecord.ribbon.js 5 | sb_/js/sb_mockrecord.ribbon.js 6 | 3 7 | 1.0.0.0 8 | 0 9 | 0 10 | <Dependencies><Dependency componentType="WebResource"/></Dependencies> 11 | 1 12 | 1 13 | 0 14 | /WebResources/sb_jssb_mockrecordribbonjs8C50CECC-0E3A-EB11-A813-000D3A0B97CA 15 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Workflows/MockBusinessProcessError-A35908FE-0FCD-4EB7-826C-405286DE4D9D.xaml.data.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | /Workflows/MockBusinessProcessError-A35908FE-0FCD-4EB7-826C-405286DE4D9D.xaml 4 | 1 5 | 0 6 | 0 7 | 1 8 | 4 9 | 0 10 | sb_triggerbusinessprocesserror 11 | 1 12 | 0 13 | 0 14 | 1 15 | 1 16 | 2 17 | 40 18 | 40 19 | 1 20 | 1 21 | 1.0.0.2 22 | 1 23 | 1 24 | sb_MockRecord 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | -------------------------------------------------------------------------------- /bindings/tests/sb_PowerAppsSpecFlowBindings_Mock/src/Workflows/SecondaryBusinessProcessFlow-D21A6986-EB11-429C-B4B1-BB747EEB0991.xaml.data.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | /Workflows/SecondaryBusinessProcessFlow-D21A6986-EB11-429C-B4B1-BB747EEB0991.xaml 4 | 1 5 | 0 6 | 4 7 | 0 8 | 4 9 | 0 10 | 1 11 | 0 12 | 0 13 | 0 14 | 1 15 | 2 16 | 100 17 | 1 18 | sb_secondarybusinessprocessflow 19 | 1 20 | 1.0.0.0 21 | 1 22 | 0 23 | 1 24 | 1 25 | sb_MockRecord 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 36 | 37 | -------------------------------------------------------------------------------- /driver/.eslintignore: -------------------------------------------------------------------------------- 1 | # don't ever lint node_modules 2 | node_modules 3 | # don't lint build output (make sure it's set to your correct build folder name) 4 | dist 5 | # don't lint nyc coverage output 6 | coverage 7 | # don't lint js 8 | **/*.js -------------------------------------------------------------------------------- /driver/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | parserOptions: { 4 | ecmaVersion: 2020, 5 | sourceType: "module", 6 | project: ['./tsconfig.json'], 7 | }, 8 | plugins: [ 9 | 'jasmine' 10 | ], 11 | extends: [ 12 | 'plugin:jasmine/recommended', 13 | 'airbnb-typescript/base', 14 | ], 15 | env: { 16 | jasmine: true, 17 | }, 18 | globals: { 19 | "Xrm": "readonly", 20 | "window": "readonly", 21 | "fetch": "readonly", 22 | }, 23 | rules: { 24 | }, 25 | }; -------------------------------------------------------------------------------- /driver/.gitattributes: -------------------------------------------------------------------------------- 1 | *.ts text eol=lf 2 | -------------------------------------------------------------------------------- /driver/.gitignore: -------------------------------------------------------------------------------- 1 | # Node modules 2 | node_modules/ 3 | 4 | # Build output 5 | /dist/ 6 | /tests/ 7 | 8 | # Test output 9 | test_results/ 10 | 11 | # IDE settings 12 | /.vscode/ 13 | 14 | -------------------------------------------------------------------------------- /driver/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | frameworks: ['jasmine', 'karma-typescript'], 4 | files: [ 5 | 'src/**/*.ts', 6 | 'test/**/*.ts' 7 | ], 8 | mime: { 9 | 'text/x-typescript': ['ts', 'tsx'] 10 | }, 11 | preprocessors: { 12 | '**/*.ts': 'karma-typescript' 13 | }, 14 | junitReporter: { 15 | outputDir: 'test_results/reports', 16 | suite: 'powerapps-specflow-bindings', 17 | useBrowserName: true, 18 | }, 19 | reporters: ['progress', 'karma-typescript', 'junit'], 20 | browsers: ['Chrome'], 21 | mime: { 22 | 'text/x-typescript': ['ts', 'tsx'] 23 | }, 24 | karmaTypescriptConfig: { 25 | reports: 26 | { 27 | html: { 28 | directory: 'test_results/coverage', 29 | subdirectory: 'html' 30 | }, 31 | lcovonly: { 32 | directory: 'test_results/coverage', 33 | subdirectory: 'lcov', 34 | filename: 'lcov.info', 35 | }, 36 | cobertura: { 37 | directory: 'test_results/coverage', 38 | subdirectory: 'cobertura', 39 | filename: 'cobertura.xml', 40 | } 41 | } 42 | } 43 | }); 44 | }; -------------------------------------------------------------------------------- /driver/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "clean": "if exist dist rd dist /s /q", 4 | "lint": "npx eslint . --fix -o test_results/analysis/eslint.json -f json", 5 | "build": "npm install && npm run lint && webpack --config webpack.config.js", 6 | "build:watch": "webpack --config webpack.config.js --watch", 7 | "test": "karma start karma.conf.js", 8 | "test:ci": "karma start karma.conf.js --single-run" 9 | }, 10 | "files": [ 11 | "dist" 12 | ], 13 | "devDependencies": { 14 | "@types/faker": "^5.1.3", 15 | "@types/jasmine": "^3.5.10", 16 | "@types/xrm": "^9.0.27", 17 | "@typescript-eslint/eslint-plugin": "^4.2.0", 18 | "@typescript-eslint/parser": "^4.4.0", 19 | "eslint": "^7.11.0", 20 | "eslint-config-airbnb-typescript": "^11.0.0", 21 | "eslint-config-prettier": "^6.12.0", 22 | "eslint-plugin-import": "^2.22.0", 23 | "eslint-plugin-jasmine": "^4.1.1", 24 | "eslint-plugin-jsdoc": "^30.6.4", 25 | "eslint-plugin-prefer-arrow": "^1.2.2", 26 | "fetch-mock": "^9.10.7", 27 | "jasmine": "^3.5.0", 28 | "karma": "^6.3.16", 29 | "karma-chrome-launcher": "^3.1.0", 30 | "karma-jasmine": "^3.3.1", 31 | "karma-junit-reporter": "^2.0.1", 32 | "karma-typescript": "^5.5.3", 33 | "ts-loader": "^8.0.5", 34 | "typescript": "^4.0.3", 35 | "webpack": "^5.91.0", 36 | "webpack-cli": "^4.0.0" 37 | }, 38 | "dependencies": { 39 | "faker": "^5.1.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /driver/src/data/createOptions.ts: -------------------------------------------------------------------------------- 1 | export interface CreateOptions { 2 | userToImpersonate: string 3 | } 4 | -------------------------------------------------------------------------------- /driver/src/data/deepInsertResponse.ts: -------------------------------------------------------------------------------- 1 | export interface DeepInsertResponse { 2 | record: { reference: Xrm.LookupValue, alias?: string }; 3 | associatedRecords: { reference: Xrm.LookupValue; alias?: string }[] 4 | } 5 | -------------------------------------------------------------------------------- /driver/src/data/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DataManager } from './dataManager'; 2 | export { default as DeepInsertService } from './deepInsertService'; 3 | export { DeepInsertResponse } from './deepInsertResponse'; 4 | export { default as Record } from './record'; 5 | export { TestRecord } from './testRecord'; 6 | export { default as Preprocessor } from './preprocessor'; 7 | export { default as FakerPreprocessor } from './fakerPreprocessor'; 8 | -------------------------------------------------------------------------------- /driver/src/data/preprocessor.ts: -------------------------------------------------------------------------------- 1 | import Record from './record'; 2 | 3 | export default abstract class Preprocessor { 4 | abstract preprocess(data: Record): Record; 5 | } 6 | -------------------------------------------------------------------------------- /driver/src/data/record.ts: -------------------------------------------------------------------------------- 1 | export default interface Record { 2 | [attribute: string]: number | string | unknown | unknown[]; 3 | } 4 | -------------------------------------------------------------------------------- /driver/src/data/testRecord.ts: -------------------------------------------------------------------------------- 1 | import Record from './record'; 2 | 3 | export interface TestRecord extends Record { 4 | '@alias': string; 5 | '@logicalName': string; 6 | } 7 | -------------------------------------------------------------------------------- /driver/src/driver.ts: -------------------------------------------------------------------------------- 1 | import { DataManager } from './data'; 2 | import { TestRecord } from './data/testRecord'; 3 | 4 | /** 5 | * Interacts with the Web API and Client API to assist test setup and teardown. 6 | * 7 | * @export 8 | * @class Driver 9 | */ 10 | export default class Driver { 11 | private readonly dataManager: DataManager; 12 | 13 | /** 14 | * Creates an instance of Driver. 15 | * @param {TestDataManager} [dataManager] A test data manager. 16 | * @memberof Driver 17 | */ 18 | constructor(dataManager: DataManager) { 19 | this.dataManager = dataManager; 20 | } 21 | 22 | /** 23 | * Loads test data into CDS from a JSON string. See Microsoft's docs on a 'Deep Insert'. 24 | * Should contain metadata to allow it to parse directly to an ITestRecord @see ITestRecord 25 | * 26 | * @param {string} json A JSON object. 27 | * @memberof Driver 28 | */ 29 | public async loadTestData(json: string): Promise { 30 | const testRecord = JSON.parse(json) as TestRecord; 31 | const logicalName = testRecord['@logicalName']; 32 | 33 | return this.dataManager.createData(logicalName, testRecord); 34 | } 35 | 36 | /** 37 | * 38 | * @param json a JSON object. 39 | * @param userToImpersonate The username of the user to impersonate. 40 | */ 41 | public async loadTestDataAsUser( 42 | json: string, 43 | userToImpersonate: string, 44 | ) { 45 | if (!userToImpersonate) { 46 | throw new Error('You have not provided the username of the user to impersonate.'); 47 | } 48 | 49 | const testRecord = JSON.parse(json) as TestRecord; 50 | const logicalName = testRecord['@logicalName']; 51 | 52 | return this.dataManager.createData(logicalName, testRecord, { userToImpersonate }); 53 | } 54 | 55 | /** 56 | * Deletes data that has been created as a result of any requests to load @see loadJsonData 57 | * @memberof Driver 58 | */ 59 | public deleteTestData(): Promise<(Xrm.LookupValue | void)[]> { 60 | return this.dataManager.cleanup(); 61 | } 62 | 63 | /** 64 | * Opens a test record. 65 | * 66 | * @param {string} alias The alias of the test record. 67 | * @returns {Xrm.Async.PromiseLike { 71 | if (this.dataManager.refsByAlias[alias] === undefined) { 72 | throw new Error(`Test record with alias '${alias}' does not exist`); 73 | } 74 | 75 | return Xrm.Navigation.openForm({ 76 | entityId: this.dataManager.refsByAlias[alias].id, 77 | entityName: this.dataManager.refsByAlias[alias].entityType, 78 | }); 79 | } 80 | 81 | /** 82 | * Gets a reference to a test record. 83 | * @param alias The alias of the test record. 84 | */ 85 | public getRecordReference(alias: string): Xrm.LookupValue { 86 | return this.dataManager.refsByAlias[alias]; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /driver/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Driver } from './driver'; 2 | export { DataManager, DeepInsertService, FakerPreprocessor } from './data'; 3 | export { CurrentUserRecordRepository, MetadataRepository, AuthenticatedRecordRepository } from './repositories'; 4 | -------------------------------------------------------------------------------- /driver/src/repositories/currentUserRecordRepository.ts: -------------------------------------------------------------------------------- 1 | import Record from '../data/record'; 2 | import AssociateRequest from '../requests/associateRequest'; 3 | import GenericRecordRepository from './genericRecordRepository'; 4 | 5 | /** 6 | * Repository to handle CRUD operations for entities using the logged in user. 7 | * 8 | * @export 9 | * @class RecordRepository 10 | * @extends {Repository} 11 | */ 12 | export default class CurrentUserRecordRepository extends GenericRecordRepository { 13 | private readonly webApi: Xrm.WebApiOnline; 14 | 15 | /** 16 | * Creates an instance of CurrentUserRecordRepository. 17 | * @param webApi The web API instance. 18 | */ 19 | constructor(webApi: Xrm.WebApiOnline) { 20 | super(); 21 | 22 | this.webApi = webApi; 23 | } 24 | 25 | /** @inheritdoc */ 26 | public async retrieveRecord(logicalName: string, id: string, query?: string): Promise { 27 | return this.webApi.retrieveRecord(logicalName, id, query); 28 | } 29 | 30 | /** @inheritdoc */ 31 | public async retrieveMultipleRecords( 32 | logicalName: string, 33 | query: string, 34 | ): Promise { 35 | return this.webApi.retrieveMultipleRecords(logicalName, query); 36 | } 37 | 38 | /** @inheritdoc */ 39 | public async createRecord(logicalName: string, record: Record): Promise { 40 | return this.webApi.createRecord(logicalName, GenericRecordRepository.sanitiseRecord(record)); 41 | } 42 | 43 | /** @inheritdoc */ 44 | public async upsertRecord(logicalName: string, record: Record): Promise { 45 | if (!record['@key']) { 46 | return this.webApi.createRecord( 47 | logicalName, CurrentUserRecordRepository.sanitiseRecord(record), 48 | ); 49 | } 50 | 51 | const retrieveResponse = await this.webApi.retrieveMultipleRecords( 52 | logicalName, 53 | `?$filter=${record['@key']} eq '${record[record['@key'] as string]}'&$select=${logicalName}id`, 54 | ); 55 | 56 | if (retrieveResponse.entities.length > 0) { 57 | const id = retrieveResponse.entities[0][`${logicalName}id`]; 58 | await this.webApi.updateRecord( 59 | logicalName, id, GenericRecordRepository.sanitiseRecord(record), 60 | ); 61 | 62 | return { entityType: logicalName, id }; 63 | } 64 | 65 | return this.webApi.createRecord( 66 | logicalName, GenericRecordRepository.sanitiseRecord(record), 67 | ); 68 | } 69 | 70 | /** @inheritdoc */ 71 | public async deleteRecord(ref: Xrm.LookupValue): Promise { 72 | return this.webApi.deleteRecord(ref.entityType, ref.id) as unknown as Xrm.LookupValue; 73 | } 74 | 75 | /** @inheritdoc */ 76 | public async associateManyToManyRecords( 77 | primaryRecord: Xrm.LookupValue, 78 | relatedRecords: Xrm.LookupValue[], 79 | relationship: string, 80 | ): Promise { 81 | this.webApi.execute(new AssociateRequest(primaryRecord, relatedRecords, relationship)); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /driver/src/repositories/index.ts: -------------------------------------------------------------------------------- 1 | export { default as MetadataRepository } from './metadataRepository'; 2 | export { default as RecordRepository } from './recordRepository'; 3 | export { default as CurrentUserRecordRepository } from './currentUserRecordRepository'; 4 | export { default as AuthenticatedRecordRepository } from './authenticatedRecordRepository'; 5 | -------------------------------------------------------------------------------- /driver/src/repositories/recordRepository.ts: -------------------------------------------------------------------------------- 1 | import Record from '../data/record'; 2 | 3 | export default interface RecordRepository { 4 | retrieveRecord(logicalName: string, id:string, query?: string): Promise; 5 | retrieveMultipleRecords(logicalName: string, query?: string): Promise; 6 | createRecord(logicalName: string, record: Record): Promise; 7 | upsertRecord(logicalName: string, record: Record): Promise; 8 | deleteRecord(ref: Xrm.LookupValue): Promise; 9 | associateManyToManyRecords( 10 | primaryRecord: Xrm.LookupValue, 11 | relatedRecords: Xrm.LookupValue[], 12 | relationship: string, 13 | ): Promise; 14 | } 15 | -------------------------------------------------------------------------------- /driver/src/requests/associateRequest.ts: -------------------------------------------------------------------------------- 1 | import { Request } from './request'; 2 | 3 | export default class AssociateRequest implements Request { 4 | public target: Xrm.LookupValue; 5 | 6 | public relatedEntities: Xrm.LookupValue[]; 7 | 8 | public relationship: string; 9 | 10 | constructor(target: Xrm.LookupValue, relatedEntities: Xrm.LookupValue[], relationship: string) { 11 | this.target = target; 12 | this.relatedEntities = relatedEntities; 13 | this.relationship = relationship; 14 | } 15 | 16 | public getMetadata(): Xrm.Metadata.ActionRequestMetadata { 17 | return { 18 | parameterTypes: { 19 | target: { 20 | typeName: `mscrm.${this.target.entityType}`, 21 | structuralProperty: 5, 22 | }, 23 | relatedEntities: { 24 | typeName: 'Collection(mscrm.crmbaseentity)', 25 | structuralProperty: 4, 26 | }, 27 | relationship: { 28 | typeName: 'Edm.String', 29 | structuralProperty: 1, 30 | }, 31 | }, 32 | operationType: 2, 33 | operationName: 'Associate', 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /driver/src/requests/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AssociateRequest } from './associateRequest'; 2 | export { Request } from './request'; 3 | -------------------------------------------------------------------------------- /driver/src/requests/request.ts: -------------------------------------------------------------------------------- 1 | export interface Request { 2 | getMetadata(): Xrm.Metadata.ActionRequestMetadata 3 | } 4 | -------------------------------------------------------------------------------- /driver/src/types/xrm.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | declare namespace Xrm { 3 | namespace Metadata { 4 | type RelationshipMetadata = 5 | Xrm.Metadata.NToNRelationshipMetadata | Xrm.Metadata.OneToNRelationshipMetadata; 6 | 7 | interface NToNRelationshipMetadata { 8 | Entity1LogicalName: string; 9 | Entity2LogicalName: string; 10 | IntersectEntityName: string; 11 | Entity1IntersectAttribute: string; 12 | Entity2IntersectAttribute: string; 13 | Entity1NavigationPropertyName: string; 14 | Entity2NavigationPropertyName: string; 15 | IsCustomRelationship: boolean; 16 | IsValidForAdvancedFind: boolean; 17 | SchemaName: string; 18 | SecurityTypes: string; 19 | IsManaged: boolean; 20 | RelationshipType: string; 21 | IntroducedVersion: string; 22 | MetadataId: string; 23 | } 24 | 25 | interface OneToNRelationshipMetadata { 26 | ReferencedAttribute: string; 27 | ReferencedEntity: string; 28 | ReferencingAttribute: string; 29 | ReferencingEntity: string; 30 | IsHierarchical: boolean; 31 | ReferencedEntityNavigationPropertyName: string; 32 | ReferencingEntityNavigationPropertyName: string; 33 | RelationshipBehavior: number; 34 | IsCustomRelationship: boolean; 35 | IsValidForAdvancedFind: boolean; 36 | SchemaName: string; 37 | SecurityTypes: string; 38 | IsManaged: boolean; 39 | RelationshipType: string; 40 | IntroducedVersion: string; 41 | MetadataId: string; 42 | } 43 | 44 | interface ActionRequestMetadata { 45 | parameterTypes: { [parameter: string]: ParameterMetadata }, 46 | operationType: number, 47 | operationName: string 48 | } 49 | 50 | interface ParameterMetadata { 51 | typeName: string, 52 | structuralProperty: number 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /driver/test/requests/associateRequest.spec.ts: -------------------------------------------------------------------------------- 1 | import AssociateRequest from '../../src/requests/associateRequest'; 2 | 3 | let associateRequest: AssociateRequest; 4 | 5 | describe('AssociateRequest', () => { 6 | const target: Xrm.LookupValue = { 7 | entityType: 'contact', 8 | id: '', 9 | }; 10 | const relatedEntities: Xrm.LookupValue[] = [ 11 | { 12 | entityType: 'account', 13 | id: '', 14 | }, 15 | { 16 | entityType: 'account', 17 | id: '', 18 | }, 19 | ]; 20 | const relationship = 'contact_account'; 21 | 22 | beforeEach(() => { 23 | associateRequest = new AssociateRequest(target, relatedEntities, relationship); 24 | }); 25 | 26 | describe('getMetadata', () => { 27 | it('replaces parameterTypes.target.typeName with target entity type', () => { 28 | expect(associateRequest.getMetadata().parameterTypes.target.typeName).toBe(`mscrm.${target.entityType}`); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /driver/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2015", 4 | "module": "ES2015", 5 | "moduleResolution": "node", 6 | "alwaysStrict": true, 7 | "strict": true, 8 | "sourceMap": true, 9 | "outDir": "dist", 10 | "allowSyntheticDefaultImports": true, 11 | }, 12 | "include": [ 13 | "src", 14 | "test", 15 | ], 16 | } -------------------------------------------------------------------------------- /driver/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | watch: false, 5 | entry: './src/index.ts', 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.tsx?$/, 12 | use: 'ts-loader', 13 | exclude: /node_modules/, 14 | }, 15 | ] 16 | }, 17 | resolve: { 18 | extensions: ['.ts', '.js'], 19 | }, 20 | output: { 21 | filename: 'driver.js', 22 | path: path.resolve(__dirname, 'dist'), 23 | libraryTarget: 'var', 24 | library: 'PowerAppsSpecFlowBindings' 25 | }, 26 | }; -------------------------------------------------------------------------------- /scripts/Remove-InvalidSymbolsPackageFiles.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter(Mandatory = $true)] 4 | [String] 5 | $SymbolPackageDirectory 6 | ) 7 | Add-Type -assembly System.IO.Compression.FileSystem 8 | 9 | Get-ChildItem "$SymbolPackageDirectory/*" -Include *.snupkg | ForEach-Object { 10 | Write-Host "Removing invalid files from $($_.Name)" 11 | $oldName = [IO.Path]::GetFileName($_.Name) 12 | $newName = [IO.Path]::GetFileName([IO.Path]::ChangeExtension($_.Name, "zip")) 13 | $zipFile = Rename-Item -Path $_.FullName -NewName $newName -PassThru 14 | $zip = [System.IO.Compression.ZipFile]::Open($zipFile.FullName, "Update") 15 | 16 | $allowedExtensions = @('.pdb', '.nuspec', '.xml', '.psmdcp', '.rels', '.p7s') 17 | ($zip.Entries | Where-Object { $allowedExtensions -notcontains [IO.Path]::GetExtension($_.Name) }) | ForEach-Object { 18 | Write-Host "Removing $($_.Name)" 19 | $_.Delete() 20 | } 21 | 22 | $zip.Dispose() 23 | 24 | Rename-Item -Path $zipFile.FullName -NewName $oldName 25 | } 26 | -------------------------------------------------------------------------------- /scripts/Set-AllUserLocalesToUk.ps1: -------------------------------------------------------------------------------- 1 | Install-Module -Name Microsoft.Xrm.Tooling.CrmConnector.PowerShell -Force -Scope CurrentUser -AllowClobber 2 | 3 | Write-Host "Updating user settings to English (United Kingdom)" 4 | 5 | # $connectionString = "AuthType=OAuth; Username=$env:username; Password=$env:password; Url=$env:environmentUrl; AppId=51f81489-12ee-4a9e-aaae-a2591f45987d; RedirectUri=app://58145B91-0C36-4500-8554-080854F2AC97; LoginPrompt=Auto"; 6 | $connectionString = "AuthType=ClientSecret; ClientId=$env:clientId; ClientSecret=`"$env:clientSecret`"; Url=$env:environmentUrl"; 7 | Write-Host $connectionString 8 | 9 | $conn = Get-CrmConnection -ConnectionString $connectionString; 10 | 11 | $condition = [Microsoft.Xrm.Sdk.Query.ConditionExpression]::new(); 12 | $condition.AttributeName = "localeid"; 13 | $condition.Operator = [Microsoft.Xrm.Sdk.Query.ConditionOperator]::NotEqual; 14 | $condition.Values.Add(2057); 15 | 16 | $filter = [Microsoft.Xrm.Sdk.Query.FilterExpression]::new(); 17 | $filter.Conditions.Add($condition); 18 | 19 | $query = [Microsoft.Xrm.Sdk.Query.QueryExpression]::new("usersettings"); 20 | $query.ColumnSet.AddColumns("localeid"); 21 | $query.Criteria.AddFilter($filter); 22 | 23 | $executeMultipleSettings = [Microsoft.Xrm.Sdk.ExecuteMultipleSettings]::new() 24 | $executeMultipleSettings.ContinueOnError = $true 25 | $executeMultipleSettings.ReturnResponses = $false 26 | 27 | $executeMultipleRequests = [Microsoft.Xrm.Sdk.OrganizationRequestCollection]::new() 28 | 29 | $usersettings = $conn.RetrieveMultiple($query).Entities 30 | $usersettings | ForEach-Object { 31 | $_.Attributes['localeid'] = 2057 32 | $updateRequest = [Microsoft.Xrm.Sdk.Messages.UpdateRequest]::new() 33 | $updateRequest.Target = $_ 34 | $executeMultipleRequests.Add($updateRequest) 35 | } 36 | 37 | $executeMultipleRequest = [Microsoft.Xrm.Sdk.Messages.ExecuteMultipleRequest]::new() 38 | $executeMultipleRequest.Settings = $executeMultipleSettings 39 | $executeMultipleRequest.Requests = $executeMultipleRequests 40 | $conn.Execute($executeMultipleRequest) -------------------------------------------------------------------------------- /scripts/Sync-DataverseUsers.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter(Mandatory)] 3 | [String] 4 | $EnvironmentName, 5 | [Parameter(Mandatory)] 6 | [String[]] 7 | $ObjectIds, 8 | [Parameter(Mandatory)] 9 | [String] 10 | $TenantId, 11 | [Parameter(Mandatory)] 12 | [String] 13 | $ClientId, 14 | [Parameter(Mandatory)] 15 | [String] 16 | $ClientSecret 17 | ) 18 | 19 | Install-Module -Name Microsoft.PowerApps.Administration.PowerShell -Force -Scope CurrentUser -AllowClobber 20 | 21 | Write-Host "Authenticating as $ClientId." 22 | Add-PowerAppsAccount -TenantID $TenantId -ApplicationId $ClientId -ClientSecret $ClientSecret 23 | 24 | $ObjectIds | ForEach-Object { 25 | Write-Host "Syncing $_." 26 | Add-AdminPowerAppsSyncUser -EnvironmentName $EnvironmentName -PrincipalObjectId $_ | Out-Null 27 | } -------------------------------------------------------------------------------- /templates/build-and-test-stages.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | 3 | - stage: BuildAndTest 4 | displayName: Build and Test 5 | jobs: 6 | - template: build-and-test-job.yml 7 | --------------------------------------------------------------------------------