diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..91e72f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,165 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +!build/ +/*/build +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +_NCrunch_* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e87a115 --- /dev/null +++ b/LICENSE @@ -0,0 +1,363 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..38f1684 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# Winium for Desktop +[![GitHub license](https://img.shields.io/badge/license-MPL 2.0-blue.svg?style=flat-square)](LICENSE) + +

+Winium.Desktop is Selenium Remote WebDriver implementation for automated testing of Windows application based on WinFroms and WPF platforms +

+ +Winium.Desktop is Selenium Remote WebDriver implementation for automated testing of Windows application based on WinFroms and WPF platforms. + +## Supported Platforms +- WinForms +- WPF + +For Windows Phone 8.1 test automation tool see [Windows Phone Driver](https://github.com/2gis/Winium.StoreApps). +For Windows Phone 8 Silverlight test automation tool see [Windows Phone Driver](https://github.com/2gis/winphonedriver). + +## Why Winium? +You have Selenium WebDriver for testing of web apps, Appium for testing of iOS and Android apps. And now you have Selenium-based tools for testing of Windows apps too. What are some of the benefits? As said by Appium: +> - You can write tests with your favorite dev tools using any WebDriver-compatible language such as Java, Objective-C, JavaScript with Node.js (in promise, callback or generator flavors), PHP, Python, Ruby, C#, Clojure, or Perl with the Selenium WebDriver API and language-specific client libraries. +> - You can use any testing framework. + +## Contributing + +Contributions are welcome! + +1. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. +2. Fork the repository to start making your changes to the master branch (or branch off of it). +3. We recommend to write a test which shows that the bug was fixed or that the feature works as expected. +4. Send a pull request and bug the maintainer until it gets merged and published. :smiley: + +## Contact + +Have some questions? Found a bug? Create [new issue](https://github.com/2gis/Winium.Desktop/issues/new) or contact us at g.golovin@2gis.ru + +## License + +Winium is released under the MPL 2.0 license. See [LICENSE](LICENSE) for details. diff --git a/src/.nuget/NuGet.Config b/src/.nuget/NuGet.Config new file mode 100644 index 0000000..67f8ea0 --- /dev/null +++ b/src/.nuget/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/.nuget/NuGet.targets b/src/.nuget/NuGet.targets new file mode 100644 index 0000000..55c01f2 --- /dev/null +++ b/src/.nuget/NuGet.targets @@ -0,0 +1,144 @@ + + + + $(MSBuildProjectDirectory)\..\ + + + false + + + false + + + true + + + true + + + + + + + + + + + $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) + + + + + $(SolutionDir).nuget + + + + $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config + $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config + + + + $(MSBuildProjectDirectory)\packages.config + $(PackagesProjectConfig) + + + + + $(NuGetToolsPath)\NuGet.exe + @(PackageSource) + + "$(NuGetExePath)" + mono --runtime=v4.0.30319 "$(NuGetExePath)" + + $(TargetDir.Trim('\\')) + + -RequireConsent + -NonInteractive + + "$(SolutionDir) " + "$(SolutionDir)" + + + $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) + $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols + + + + RestorePackages; + $(BuildDependsOn); + + + + + $(BuildDependsOn); + BuildPackage; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Settings.StyleCop b/src/Settings.StyleCop new file mode 100644 index 0000000..62855cc --- /dev/null +++ b/src/Settings.StyleCop @@ -0,0 +1,221 @@ + + + + + False + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + \ No newline at end of file diff --git a/src/Winium.Desktop.Driver/App.config b/src/Winium.Desktop.Driver/App.config new file mode 100644 index 0000000..d0feca6 --- /dev/null +++ b/src/Winium.Desktop.Driver/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Winium.Desktop.Driver/Automator/Automator.cs b/src/Winium.Desktop.Driver/Automator/Automator.cs new file mode 100644 index 0000000..fbbce3a --- /dev/null +++ b/src/Winium.Desktop.Driver/Automator/Automator.cs @@ -0,0 +1,77 @@ +namespace Winium.Desktop.Driver.Automator +{ + #region using + + using System.Collections.Generic; + + using Winium.Cruciatus; + + #endregion + + internal class Automator + { + #region Static Fields + + private static readonly object LockObject = new object(); + + private static volatile Automator instance; + + #endregion + + #region Constructors and Destructors + + public Automator(string session) + { + this.Session = session; + this.Elements = new ElementStorage(); + } + + #endregion + + #region Public Properties + + public Capabilities ActualCapabilities { get; set; } + + public Application Application { get; set; } + + public ElementStorage Elements { get; private set; } + + public string Session { get; private set; } + + #endregion + + #region Public Methods and Operators + + public static T GetValue(IReadOnlyDictionary parameters, string key) where T : class + { + object valueObject; + parameters.TryGetValue(key, out valueObject); + + return valueObject as T; + } + + public static Automator InstanceForSession(string sessionId) + { + if (instance == null) + { + lock (LockObject) + { + if (instance == null) + { + if (sessionId == null) + { + sessionId = "AwesomeSession"; + } + + // TODO: Add actual support for sessions. Temporary return single Automator for any season + instance = new Automator(sessionId); + } + } + } + + return instance; + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/Automator/Capabilities.cs b/src/Winium.Desktop.Driver/Automator/Capabilities.cs new file mode 100644 index 0000000..a81c07f --- /dev/null +++ b/src/Winium.Desktop.Driver/Automator/Capabilities.cs @@ -0,0 +1,65 @@ +namespace Winium.Desktop.Driver.Automator +{ + #region using + + using Newtonsoft.Json; + using Newtonsoft.Json.Serialization; + + #endregion + + internal class Capabilities + { + #region Constructors and Destructors + + internal Capabilities() + { + this.App = string.Empty; + this.LaunchDelay = 0; + this.DebugConnectToRunningApp = false; + this.InnerPort = 9998; + } + + #endregion + + #region Public Properties + + [JsonProperty("app")] + public string App { get; set; } + + [JsonProperty("debugConnectToRunningApp")] + public bool DebugConnectToRunningApp { get; set; } + + [JsonProperty("innerPort")] + public int InnerPort { get; set; } + + [JsonProperty("launchDelay")] + public int LaunchDelay { get; set; } + + #endregion + + #region Public Methods and Operators + + public static Capabilities CapabilitiesFromJsonString(string jsonString) + { + var capabilities = JsonConvert.DeserializeObject( + jsonString, + new JsonSerializerSettings + { + Error = + delegate(object sender, ErrorEventArgs args) + { + args.ErrorContext.Handled = true; + } + }); + + return capabilities; + } + + public string CapabilitiesToJsonString() + { + return JsonConvert.SerializeObject(this); + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/CommandExecutorDispatchTable.cs b/src/Winium.Desktop.Driver/CommandExecutorDispatchTable.cs new file mode 100644 index 0000000..76013a8 --- /dev/null +++ b/src/Winium.Desktop.Driver/CommandExecutorDispatchTable.cs @@ -0,0 +1,78 @@ +namespace Winium.Desktop.Driver +{ + #region using + + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + using Winium.Desktop.Driver.CommandExecutors; + using Winium.StoreApps.Common; + + #endregion + + internal class CommandExecutorDispatchTable + { + #region Fields + + private Dictionary commandExecutorsRepository; + + #endregion + + #region Constructors and Destructors + + public CommandExecutorDispatchTable() + { + this.ConstructDispatcherTable(); + } + + #endregion + + #region Public Methods and Operators + + public CommandExecutorBase GetExecutor(string commandName) + { + Type executorType; + if (this.commandExecutorsRepository.TryGetValue(commandName, out executorType)) + { + } + else + { + executorType = typeof(NotImplementedExecutor); + } + + return (CommandExecutorBase)Activator.CreateInstance(executorType); + } + + #endregion + + #region Methods + + private void ConstructDispatcherTable() + { + this.commandExecutorsRepository = new Dictionary(); + + // TODO: bad const + const string ExecutorsNamespace = "Winium.Desktop.Driver.CommandExecutors"; + + var q = + (from t in Assembly.GetExecutingAssembly().GetTypes() + where t.IsClass && t.Namespace == ExecutorsNamespace + select t).ToArray(); + + var fields = typeof(DriverCommand).GetFields(BindingFlags.Public | BindingFlags.Static); + foreach (var field in fields) + { + var localField = field; + var executor = q.FirstOrDefault(x => x.Name.Equals(localField.Name + "Executor")); + if (executor != null) + { + this.commandExecutorsRepository.Add(localField.GetValue(null).ToString(), executor); + } + } + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/CommandExecutors/ClickElementExecutor.cs b/src/Winium.Desktop.Driver/CommandExecutors/ClickElementExecutor.cs new file mode 100644 index 0000000..0f0175d --- /dev/null +++ b/src/Winium.Desktop.Driver/CommandExecutors/ClickElementExecutor.cs @@ -0,0 +1,17 @@ +namespace Winium.Desktop.Driver.CommandExecutors +{ + internal class ClickElementExecutor : CommandExecutorBase + { + #region Methods + + protected override string DoImpl() + { + var registeredKey = this.ExecutedCommand.Parameters["ID"].ToString(); + this.Automator.Elements.GetRegisteredElement(registeredKey).Click(); + + return this.JsonResponse(); + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/CommandExecutors/CloseExecutor.cs b/src/Winium.Desktop.Driver/CommandExecutors/CloseExecutor.cs new file mode 100644 index 0000000..1c471e9 --- /dev/null +++ b/src/Winium.Desktop.Driver/CommandExecutors/CloseExecutor.cs @@ -0,0 +1,27 @@ +namespace Winium.Desktop.Driver.CommandExecutors +{ + #region using + + using Winium.StoreApps.Common; + + #endregion + + internal class CloseExecutor : CommandExecutorBase + { + #region Methods + + protected override string DoImpl() + { + if (!this.Automator.Application.Close()) + { + this.Automator.Application.Kill(); + } + + this.Automator.Elements.Clear(); + + return this.JsonResponse(ResponseStatus.Success, null); + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/CommandExecutors/CommandExecutorBase.cs b/src/Winium.Desktop.Driver/CommandExecutors/CommandExecutorBase.cs new file mode 100644 index 0000000..42077e5 --- /dev/null +++ b/src/Winium.Desktop.Driver/CommandExecutors/CommandExecutorBase.cs @@ -0,0 +1,84 @@ +namespace Winium.Desktop.Driver.CommandExecutors +{ + #region using + + using System; + using System.Net; + + using Newtonsoft.Json; + + using Winium.Desktop.Driver.Automator; + using Winium.StoreApps.Common; + using Winium.StoreApps.Common.Exceptions; + + #endregion + + internal abstract class CommandExecutorBase + { + #region Public Properties + + public Command ExecutedCommand { get; set; } + + #endregion + + #region Properties + + protected Automator Automator { get; set; } + + #endregion + + #region Public Methods and Operators + + public CommandResponse Do() + { + if (this.ExecutedCommand == null) + { + throw new NullReferenceException("ExecutedCommand property must be set before calling Do"); + } + + try + { + var session = this.ExecutedCommand.SessionId; + this.Automator = Automator.InstanceForSession(session); + return CommandResponse.Create(HttpStatusCode.OK, this.DoImpl()); + } + catch (AutomationException ex) + { + return CommandResponse.Create(HttpStatusCode.OK, this.JsonResponse(ex.Status, ex.Message)); + } + catch (NotImplementedException exception) + { + return CommandResponse.Create( + HttpStatusCode.NotImplemented, + this.JsonResponse(ResponseStatus.UnknownCommand, exception.Message)); + } + catch (Exception exception) + { + return CommandResponse.Create( + HttpStatusCode.OK, + this.JsonResponse(ResponseStatus.UnknownError, "Unknown error: " + exception.Message)); + } + } + + #endregion + + #region Methods + + protected abstract string DoImpl(); + + /// + /// The JsonResponse with SUCCESS status and NULL value. + /// + protected string JsonResponse() + { + return this.JsonResponse(ResponseStatus.Success, null); + } + + protected string JsonResponse(ResponseStatus status, object value) + { + return JsonConvert.SerializeObject(new JsonResponse(this.Automator.Session, status, value)); + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/CommandExecutors/FindChildElementExecutor.cs b/src/Winium.Desktop.Driver/CommandExecutors/FindChildElementExecutor.cs new file mode 100644 index 0000000..4608df8 --- /dev/null +++ b/src/Winium.Desktop.Driver/CommandExecutors/FindChildElementExecutor.cs @@ -0,0 +1,26 @@ +namespace Winium.Desktop.Driver.CommandExecutors +{ + #region using + + using Winium.StoreApps.Common; + + #endregion + + internal class FindChildElementExecutor : CommandExecutorBase + { + #region Methods + + protected override string DoImpl() + { + var registeredKey = this.ExecutedCommand.Parameters["ID"].ToString(); + var searchValue = this.ExecutedCommand.Parameters["value"].ToString(); + var searchStrategy = this.ExecutedCommand.Parameters["using"].ToString(); + + var elementId = this.Automator.Elements.FindElement(registeredKey, searchStrategy, searchValue); + var webElement = new JsonWebElementContent(elementId); + return this.JsonResponse(ResponseStatus.Success, webElement); + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/CommandExecutors/FindElementExecutor.cs b/src/Winium.Desktop.Driver/CommandExecutors/FindElementExecutor.cs new file mode 100644 index 0000000..8e3e192 --- /dev/null +++ b/src/Winium.Desktop.Driver/CommandExecutors/FindElementExecutor.cs @@ -0,0 +1,26 @@ +namespace Winium.Desktop.Driver.CommandExecutors +{ + #region using + + using Winium.Cruciatus; + using Winium.StoreApps.Common; + + #endregion + + internal class FindElementExecutor : CommandExecutorBase + { + #region Methods + + protected override string DoImpl() + { + var searchValue = this.ExecutedCommand.Parameters["value"].ToString(); + var searchStrategy = this.ExecutedCommand.Parameters["using"].ToString(); + + var elementId = this.Automator.Elements.FindElement(CruciatusFactory.Root, searchStrategy, searchValue); + var webElement = new JsonWebElementContent(elementId); + return this.JsonResponse(ResponseStatus.Success, webElement); + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/CommandExecutors/NewSessionExecutor.cs b/src/Winium.Desktop.Driver/CommandExecutors/NewSessionExecutor.cs new file mode 100644 index 0000000..ac79ff8 --- /dev/null +++ b/src/Winium.Desktop.Driver/CommandExecutors/NewSessionExecutor.cs @@ -0,0 +1,47 @@ +namespace Winium.Desktop.Driver.CommandExecutors +{ + #region using + + using System.Threading; + + using Newtonsoft.Json; + + using Winium.Cruciatus; + using Winium.Desktop.Driver.Automator; + using Winium.StoreApps.Common; + + #endregion + + internal class NewSessionExecutor : CommandExecutorBase + { + #region Methods + + protected override string DoImpl() + { + // It is easier to reparse desired capabilities as JSON instead of re-mapping keys to attributes and calling type conversions, + // so we will take possible one time performance hit by serializing Dictionary and deserializing it as Capabilities object + var serializedCapability = + JsonConvert.SerializeObject(this.ExecutedCommand.Parameters["desiredCapabilities"]); + this.Automator.ActualCapabilities = Capabilities.CapabilitiesFromJsonString(serializedCapability); + + this.InitializeApplication(this.Automator.ActualCapabilities.DebugConnectToRunningApp); + + // Gives sometime to load visuals (needed only in case of slow emulation) + Thread.Sleep(this.Automator.ActualCapabilities.LaunchDelay); + + return this.JsonResponse(ResponseStatus.Success, this.Automator.ActualCapabilities); + } + + private void InitializeApplication(bool debugDoNotDeploy = false) + { + var appPath = this.Automator.ActualCapabilities.App; + this.Automator.Application = new Application(appPath); + if (!debugDoNotDeploy) + { + this.Automator.Application.Start(); + } + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/CommandExecutors/NotImplementedExecutor.cs b/src/Winium.Desktop.Driver/CommandExecutors/NotImplementedExecutor.cs new file mode 100644 index 0000000..7c14ee3 --- /dev/null +++ b/src/Winium.Desktop.Driver/CommandExecutors/NotImplementedExecutor.cs @@ -0,0 +1,21 @@ +namespace Winium.Desktop.Driver.CommandExecutors +{ + #region using + + using System; + + #endregion + + internal class NotImplementedExecutor : CommandExecutorBase + { + #region Methods + + protected override string DoImpl() + { + var msg = string.Format("'{0}' is not valid or implemented command.", this.ExecutedCommand.Name); + throw new NotImplementedException(msg); + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/CommandLineOptions.cs b/src/Winium.Desktop.Driver/CommandLineOptions.cs new file mode 100644 index 0000000..5f28695 --- /dev/null +++ b/src/Winium.Desktop.Driver/CommandLineOptions.cs @@ -0,0 +1,39 @@ +namespace Winium.Desktop.Driver +{ + #region using + + using CommandLine; + using CommandLine.Text; + + #endregion + + internal class CommandLineOptions + { + #region Public Properties + + [Option("log-path", Required = false, + HelpText = "write server log to file instead of stdout, increases log level to INFO")] + public string LogPath { get; set; } + + [Option("port", Required = false, HelpText = "port to listen on")] + public int? Port { get; set; } + + [Option("url-base", Required = false, HelpText = "base URL path prefix for commands, e.g. wd/url")] + public string UrlBase { get; set; } + + [Option("verbose", Required = false, HelpText = "log verbosely")] + public bool Verbose { get; set; } + + #endregion + + #region Public Methods and Operators + + [HelpOption] + public string GetUsage() + { + return HelpText.AutoBuild(this, current => HelpText.DefaultParsingErrorsHandler(this, current)); + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/ElementStorage.cs b/src/Winium.Desktop.Driver/ElementStorage.cs new file mode 100644 index 0000000..769b474 --- /dev/null +++ b/src/Winium.Desktop.Driver/ElementStorage.cs @@ -0,0 +1,103 @@ +namespace Winium.Desktop.Driver +{ + #region using + + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Threading; + + using Winium.Cruciatus.Elements; + using Winium.Desktop.Driver.Extensions; + using Winium.StoreApps.Common; + using Winium.StoreApps.Common.Exceptions; + + #endregion + + internal class ElementStorage + { + #region Static Fields + + private static int safeInstanceCount; + + #endregion + + #region Fields + + private readonly Dictionary registeredElements; + + #endregion + + #region Constructors and Destructors + + public ElementStorage() + { + this.registeredElements = new Dictionary(); + } + + #endregion + + #region Public Methods and Operators + + public void Clear() + { + this.registeredElements.Clear(); + } + + public string FindElement(CruciatusElement parent, string searchStrategy, string searchValue) + { + var strategy = ByHelper.GetStrategy(searchStrategy, searchValue); + var element = parent.FindElement(strategy); + if (element == null) + { + throw new AutomationException("Element cannot be found", ResponseStatus.NoSuchElement); + } + + return this.RegisterElement(element); + } + + public string FindElement(string parentRegisteredKey, string searchStrategy, string searchValue) + { + var parent = this.GetRegisteredElement(parentRegisteredKey); + return this.FindElement(parent, searchStrategy, searchValue); + } + + /// + /// Returns FrameworkElement registered with specified key if any exists. Throws if no element is found. + /// + /// + /// Registered element is not found or element has been garbage collected. + /// + public CruciatusElement GetRegisteredElement(string registeredKey) + { + CruciatusElement element; + if (this.registeredElements.TryGetValue(registeredKey, out element)) + { + if (element != null) + { + return element; + } + } + + throw new AutomationException("Stale element reference", ResponseStatus.StaleElementReference); + } + + public string RegisterElement(CruciatusElement element) + { + var registeredKey = this.registeredElements.FirstOrDefault(x => x.Value == element).Key; + + if (registeredKey == null) + { + Interlocked.Increment(ref safeInstanceCount); + + registeredKey = element.GetHashCode() + "-" + + safeInstanceCount.ToString(string.Empty, CultureInfo.InvariantCulture); + this.registeredElements.Add(registeredKey, element); + } + + return registeredKey; + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/Extensions/ByHelper.cs b/src/Winium.Desktop.Driver/Extensions/ByHelper.cs new file mode 100644 index 0000000..5de3750 --- /dev/null +++ b/src/Winium.Desktop.Driver/Extensions/ByHelper.cs @@ -0,0 +1,34 @@ +namespace Winium.Desktop.Driver.Extensions +{ + #region using + + using System; + using System.Windows.Automation; + + using Winium.Cruciatus.Core; + + #endregion + + public static class ByHelper + { + #region Public Methods and Operators + + public static By GetStrategy(string strategy, string value) + { + switch (strategy) + { + case "id": + return By.Uid(value); + case "name": + return By.Name(value); + case "class name": + return By.AutomationProperty(AutomationElementIdentifiers.ClassNameProperty, value); + default: + throw new NotImplementedException( + string.Format("'{0}' is not valid or implemented searching strategy.", strategy)); + } + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/HttpRequest.cs b/src/Winium.Desktop.Driver/HttpRequest.cs new file mode 100644 index 0000000..5c9aa2f --- /dev/null +++ b/src/Winium.Desktop.Driver/HttpRequest.cs @@ -0,0 +1,81 @@ +namespace Winium.Desktop.Driver +{ + #region using + + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + + #endregion + + public class HttpRequest + { + #region Public Properties + + public Dictionary Headers { get; set; } + + public string MessageBody { get; private set; } + + public string StartingLine { get; private set; } + + #endregion + + #region Public Methods and Operators + + public static HttpRequest ReadFromStreamWithoutClosing(Stream stream) + { + var request = new HttpRequest(); + var streamReader = new StreamReader(stream); + + request.StartingLine = streamReader.ReadLine(); + + request.Headers = ReadHeaders(streamReader); + + var contentLength = GetContentLength(request.Headers); + request.MessageBody = contentLength != 0 ? ReadContent(streamReader, contentLength) : string.Empty; + + return request; + } + + #endregion + + #region Methods + + private static int GetContentLength(IReadOnlyDictionary headers) + { + var contentLength = 0; + string contentLengthString; + if (headers.TryGetValue("Content-Length", out contentLengthString)) + { + contentLength = Convert.ToInt32(contentLengthString, CultureInfo.InvariantCulture); + } + + return contentLength; + } + + // reads the content of a request depending on its length + private static string ReadContent(TextReader textReader, int contentLength) + { + var readBuffer = new char[contentLength]; + textReader.Read(readBuffer, 0, readBuffer.Length); + return readBuffer.Aggregate(string.Empty, (current, ch) => current + ch); + } + + private static Dictionary ReadHeaders(TextReader textReader) + { + var headers = new Dictionary(); + string header; + while (!string.IsNullOrEmpty(header = textReader.ReadLine())) + { + var splitHeader = header.Split(':'); + headers.Add(splitHeader[0], splitHeader[1].Trim(' ')); + } + + return headers; + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/Listener.cs b/src/Winium.Desktop.Driver/Listener.cs new file mode 100644 index 0000000..d9884ac --- /dev/null +++ b/src/Winium.Desktop.Driver/Listener.cs @@ -0,0 +1,183 @@ +namespace Winium.Desktop.Driver +{ + #region using + + using System; + using System.Globalization; + using System.IO; + using System.Net; + using System.Net.Sockets; + + using Winium.StoreApps.Common; + + #endregion + + public class Listener + { + #region Static Fields + + private static string urnPrefix; + + #endregion + + #region Fields + + private UriDispatchTables dispatcher; + + private CommandExecutorDispatchTable executorDispatcher; + + private TcpListener listener; + + #endregion + + #region Constructors and Destructors + + public Listener(int listenerPort) + { + this.Port = listenerPort; + } + + #endregion + + #region Public Properties + + public static string UrnPrefix + { + get + { + return urnPrefix; + } + + set + { + if (!string.IsNullOrEmpty(value)) + { + // Normalize prefix + urnPrefix = "/" + value.Trim('/'); + } + } + } + + public int Port { get; private set; } + + public Uri Prefix { get; private set; } + + #endregion + + #region Public Methods and Operators + + public void StartListening() + { + try + { + this.listener = new TcpListener(IPAddress.Any, this.Port); + + this.Prefix = new Uri(string.Format(CultureInfo.InvariantCulture, "http://localhost:{0}", this.Port)); + this.dispatcher = new UriDispatchTables(new Uri(this.Prefix, UrnPrefix)); + this.executorDispatcher = new CommandExecutorDispatchTable(); + + // Start listening for client requests. + this.listener.Start(); + + // Enter the listening loop + while (true) + { + Logger.Debug("Waiting for a connection..."); + + // Perform a blocking call to accept requests. + var client = this.listener.AcceptTcpClient(); + + // Get a stream object for reading and writing + using (var stream = client.GetStream()) + { + var acceptedRequest = HttpRequest.ReadFromStreamWithoutClosing(stream); + Logger.Debug("ACCEPTED REQUEST {0}", acceptedRequest.StartingLine); + + var response = this.HandleRequest(acceptedRequest); + using (var writer = new StreamWriter(stream)) + { + try + { + writer.Write(response); + writer.Flush(); + } + catch (IOException ex) + { + Logger.Error("Error occured while writing response: {0}", ex); + } + } + + // Shutdown and end connection + } + + client.Close(); + + Logger.Debug("Client closed\n"); + } + } + catch (SocketException ex) + { + Logger.Error("SocketException occurred while trying to start listner: {0}", ex); + throw; + } + catch (ArgumentException ex) + { + Logger.Error("ArgumentException occurred while trying to start listner: {0}", ex); + throw; + } + finally + { + // Stop listening for new clients. + this.listener.Stop(); + } + } + + public void StopListening() + { + this.listener.Stop(); + } + + #endregion + + #region Methods + + private string HandleRequest(HttpRequest acceptedRequest) + { + var firstHeaderTokens = acceptedRequest.StartingLine.Split(' '); + var method = firstHeaderTokens[0]; + var resourcePath = firstHeaderTokens[1]; + + var uriToMatch = new Uri(this.Prefix, resourcePath); + var matched = this.dispatcher.Match(method, uriToMatch); + + if (matched == null) + { + Logger.Warn("Unknown command recived: {0}", uriToMatch); + return HttpResponseHelper.ResponseString(HttpStatusCode.NotFound, "Unknown command " + uriToMatch); + } + + var commandName = matched.Data.ToString(); + var commandToExecute = new Command(commandName, acceptedRequest.MessageBody); + foreach (string variableName in matched.BoundVariables.Keys) + { + commandToExecute.Parameters[variableName] = matched.BoundVariables[variableName]; + } + + var commandResponse = this.ProcessCommand(commandToExecute); + return HttpResponseHelper.ResponseString(commandResponse.HttpStatusCode, commandResponse.Content); + } + + private CommandResponse ProcessCommand(Command command) + { + Logger.Info("COMMAND {0}\r\n{1}", command.Name, command.Parameters.ToString()); + var executor = this.executorDispatcher.GetExecutor(command.Name); + executor.ExecutedCommand = command; + var respnose = executor.Do(); + Logger.Debug("RESPONSE:\r\n{0}", respnose); + + return respnose; + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/Logger.cs b/src/Winium.Desktop.Driver/Logger.cs new file mode 100644 index 0000000..f20b855 --- /dev/null +++ b/src/Winium.Desktop.Driver/Logger.cs @@ -0,0 +1,76 @@ +namespace Winium.Desktop.Driver +{ + #region using + + using System.ComponentModel; + + using NLog; + using NLog.Targets; + + #endregion + + internal static class Logger + { + #region Constants + + private const string LayoutFormat = "${date:format=HH\\:MM\\:ss} [${level:uppercase=true}] ${message}"; + + #endregion + + #region Static Fields + + private static readonly NLog.Logger Log = LogManager.GetLogger("outerdriver"); + + #endregion + + #region Public Methods and Operators + + public static void Debug([Localizable(false)] string message, params object[] args) + { + Log.Debug(message, args); + } + + public static void Error([Localizable(false)] string message, params object[] args) + { + Log.Error(message, args); + } + + public static void Fatal([Localizable(false)] string message, params object[] args) + { + Log.Fatal(message, args); + } + + public static void Info([Localizable(false)] string message, params object[] args) + { + Log.Info(message, args); + } + + public static void TargetConsole(bool verbose) + { + var target = new ConsoleTarget { Layout = LayoutFormat }; + + NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, verbose ? LogLevel.Debug : LogLevel.Fatal); + LogManager.ReconfigExistingLoggers(); + } + + public static void TargetFile(string fileName, bool verbose) + { + var target = new FileTarget { Layout = LayoutFormat, FileName = fileName }; + + NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, verbose ? LogLevel.Debug : LogLevel.Info); + LogManager.ReconfigExistingLoggers(); + } + + public static void Trace([Localizable(false)] string message, params object[] args) + { + Log.Trace(message, args); + } + + public static void Warn([Localizable(false)] string message, params object[] args) + { + Log.Warn(message, args); + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/Program.cs b/src/Winium.Desktop.Driver/Program.cs new file mode 100644 index 0000000..07b1faa --- /dev/null +++ b/src/Winium.Desktop.Driver/Program.cs @@ -0,0 +1,54 @@ +namespace Winium.Desktop.Driver +{ + #region using + + using System; + + #endregion + + internal class Program + { + #region Methods + + [STAThread] + private static void Main(string[] args) + { + var listeningPort = 9999; + + var options = new CommandLineOptions(); + if (CommandLine.Parser.Default.ParseArguments(args, options)) + { + if (options.Port.HasValue) + { + listeningPort = options.Port.Value; + } + } + + if (options.LogPath != null) + { + Logger.TargetFile(options.LogPath, options.Verbose); + } + else + { + Logger.TargetConsole(options.Verbose); + } + + try + { + var listener = new Listener(listeningPort); + Listener.UrnPrefix = options.UrlBase; + + Console.WriteLine("Starting Windows Desktop Driver on port {0}\n", listeningPort); + + listener.StartListening(); + } + catch (Exception ex) + { + Logger.Fatal("Failed to start driver: {0}", ex); + throw; + } + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/Properties/AssemblyInfo.cs b/src/Winium.Desktop.Driver/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8c6227a --- /dev/null +++ b/src/Winium.Desktop.Driver/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +#region using + +using System.Reflection; +using System.Runtime.InteropServices; + +#endregion + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Winium.Desktop.Driver")] +[assembly: AssemblyDescription("Selenium Remote WebDriver implementation for test automation of Windows application based on WinFroms and WPF platforms.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Winium.Desktop.Driver")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2878a69b-16cd-455a-8f46-6323e9109635")] + +// Version information for an assembly consists of the following four values: +// Major Version +// Minor Version +// Build Number +// Revision +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Winium.Desktop.Driver/Requester.cs b/src/Winium.Desktop.Driver/Requester.cs new file mode 100644 index 0000000..890b0a4 --- /dev/null +++ b/src/Winium.Desktop.Driver/Requester.cs @@ -0,0 +1,147 @@ +namespace Winium.Desktop.Driver +{ + #region using + + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Net; + + using Newtonsoft.Json; + + using Winium.StoreApps.Common; + using Winium.StoreApps.Common.Exceptions; + + #endregion + + internal class Requester + { + #region Fields + + private readonly string ip; + + private readonly int port; + + #endregion + + #region Constructors and Destructors + + public Requester(string ip, int port) + { + this.ip = ip; + this.port = port; + } + + #endregion + + #region Public Methods and Operators + + public string ForwardCommand(Command commandToForward, bool verbose = true, int timeout = 0) + { + var serializedCommand = JsonConvert.SerializeObject(commandToForward); + + var response = this.SendRequest(serializedCommand, verbose, timeout); + if (response.Key == HttpStatusCode.OK) + { + return response.Value; + } + + throw new InnerDriverRequestException(response.Value, response.Key); + } + + public KeyValuePair SendRequest(string requestContent, bool verbose, int timeout) + { + var result = string.Empty; + StreamReader reader = null; + HttpWebResponse response = null; + var status = HttpStatusCode.OK; + try + { + // create the request + var uri = string.Format(CultureInfo.InvariantCulture, "http://{0}:{1}", this.ip, this.port); + var request = CreateWebRequest(uri, requestContent); + if (timeout != 0) + { + request.Timeout = timeout; + } + + if (verbose) + { + Logger.Debug("Sending request to inner driver: {0}", uri); + } + + // send the request and get the response + try + { + response = request.GetResponse() as HttpWebResponse; + } + catch (WebException ex) + { + response = ex.Response as HttpWebResponse; + } + + if (response != null) + { + status = response.StatusCode; + var stream = response.GetResponseStream(); + if (stream == null) + { + throw new NullReferenceException("No response stream."); + } + + // read and return the response + reader = new StreamReader(stream); + result = reader.ReadToEnd(); + } + } + catch (Exception ex) + { + if (verbose) + { + // No need to log exceptions raised when sending service commands like ping. + Logger.Error("Error occurred while trying to send request to inner driver: {0}", ex); + } + } + finally + { + if (response != null) + { + response.Close(); + } + + if (reader != null) + { + reader.Close(); + } + } + + return new KeyValuePair(status, result); + } + + #endregion + + #region Methods + + private static HttpWebRequest CreateWebRequest(string uri, string content) + { + // create request + var request = (HttpWebRequest)WebRequest.Create(uri); + request.ContentType = "application/json"; + request.Method = "POST"; + request.KeepAlive = false; + + // write request body + if (!string.IsNullOrEmpty(content)) + { + var writer = new StreamWriter(request.GetRequestStream()); + writer.Write(content); + writer.Close(); + } + + return request; + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/UriDispatchTables.cs b/src/Winium.Desktop.Driver/UriDispatchTables.cs new file mode 100644 index 0000000..5cdaaed --- /dev/null +++ b/src/Winium.Desktop.Driver/UriDispatchTables.cs @@ -0,0 +1,293 @@ +namespace Winium.Desktop.Driver +{ + #region using + + using System; + using System.Collections.Generic; + using System.Reflection; + + using Winium.StoreApps.Common; + + #endregion + + internal class UriDispatchTables + { + #region Fields + + private readonly Dictionary commandDictionary = new Dictionary(); + + private UriTemplateTable deleteDispatcherTable; + + private UriTemplateTable getDispatcherTable; + + private UriTemplateTable postDispatcherTable; + + #endregion + + #region Constructors and Destructors + + public UriDispatchTables(Uri prefix) + { + this.InitializeCommandDictionary(); + this.ConstructDispatcherTables(prefix); + } + + #endregion + + #region Public Methods and Operators + + public UriTemplateMatch Match(string httpMethod, Uri uriToMatch) + { + var table = this.FindDispatcherTable(httpMethod); + return table != null ? table.MatchSingle(uriToMatch) : null; + } + + #endregion + + #region Methods + + internal UriTemplateTable FindDispatcherTable(string httpMethod) + { + UriTemplateTable tableToReturn = null; + switch (httpMethod) + { + case CommandInfo.GetCommand: + tableToReturn = this.getDispatcherTable; + break; + + case CommandInfo.PostCommand: + tableToReturn = this.postDispatcherTable; + break; + + case CommandInfo.DeleteCommand: + tableToReturn = this.deleteDispatcherTable; + break; + } + + return tableToReturn; + } + + private void ConstructDispatcherTables(Uri prefix) + { + this.getDispatcherTable = new UriTemplateTable(prefix); + this.postDispatcherTable = new UriTemplateTable(prefix); + this.deleteDispatcherTable = new UriTemplateTable(prefix); + + var fields = typeof(DriverCommand).GetFields(BindingFlags.Public | BindingFlags.Static); + foreach (var field in fields) + { + var commandName = field.GetValue(null).ToString(); + var commandInformation = this.commandDictionary[commandName]; + var commandUriTemplate = new UriTemplate(commandInformation.ResourcePath); + var templateTable = this.FindDispatcherTable(commandInformation.Method); + templateTable.KeyValuePairs.Add(new KeyValuePair(commandUriTemplate, commandName)); + } + + this.getDispatcherTable.MakeReadOnly(false); + this.postDispatcherTable.MakeReadOnly(false); + this.deleteDispatcherTable.MakeReadOnly(false); + } + + private void InitializeCommandDictionary() + { + this.commandDictionary.Add(DriverCommand.DefineDriverMapping, new CommandInfo("POST", "/config/drivers")); + this.commandDictionary.Add(DriverCommand.Status, new CommandInfo("GET", "/status")); + this.commandDictionary.Add(DriverCommand.NewSession, new CommandInfo("POST", "/session")); + this.commandDictionary.Add(DriverCommand.GetSessionList, new CommandInfo("GET", "/sessions")); + this.commandDictionary.Add( + DriverCommand.GetSessionCapabilities, + new CommandInfo("GET", "/session/{sessionId}")); + this.commandDictionary.Add(DriverCommand.Quit, new CommandInfo("DELETE", "/session/{sessionId}")); + this.commandDictionary.Add( + DriverCommand.GetCurrentWindowHandle, + new CommandInfo("GET", "/session/{sessionId}/window_handle")); + this.commandDictionary.Add( + DriverCommand.GetWindowHandles, + new CommandInfo("GET", "/session/{sessionId}/window_handles")); + this.commandDictionary.Add(DriverCommand.GetCurrentUrl, new CommandInfo("GET", "/session/{sessionId}/url")); + this.commandDictionary.Add(DriverCommand.Get, new CommandInfo("POST", "/session/{sessionId}/url")); + this.commandDictionary.Add(DriverCommand.GoForward, new CommandInfo("POST", "/session/{sessionId}/forward")); + this.commandDictionary.Add(DriverCommand.GoBack, new CommandInfo("POST", "/session/{sessionId}/back")); + this.commandDictionary.Add(DriverCommand.Refresh, new CommandInfo("POST", "/session/{sessionId}/refresh")); + this.commandDictionary.Add( + DriverCommand.ExecuteScript, + new CommandInfo("POST", "/session/{sessionId}/execute")); + this.commandDictionary.Add( + DriverCommand.ExecuteAsyncScript, + new CommandInfo("POST", "/session/{sessionId}/execute_async")); + this.commandDictionary.Add( + DriverCommand.Screenshot, + new CommandInfo("GET", "/session/{sessionId}/screenshot")); + this.commandDictionary.Add( + DriverCommand.SwitchToFrame, + new CommandInfo("POST", "/session/{sessionId}/frame")); + this.commandDictionary.Add( + DriverCommand.SwitchToParentFrame, + new CommandInfo("POST", "/session/{sessionId}/frame/parent")); + this.commandDictionary.Add( + DriverCommand.SwitchToWindow, + new CommandInfo("POST", "/session/{sessionId}/window")); + this.commandDictionary.Add( + DriverCommand.GetAllCookies, + new CommandInfo("GET", "/session/{sessionId}/cookie")); + this.commandDictionary.Add(DriverCommand.AddCookie, new CommandInfo("POST", "/session/{sessionId}/cookie")); + this.commandDictionary.Add( + DriverCommand.DeleteAllCookies, + new CommandInfo("DELETE", "/session/{sessionId}/cookie")); + this.commandDictionary.Add( + DriverCommand.DeleteCookie, + new CommandInfo("DELETE", "/session/{sessionId}/cookie/{name}")); + this.commandDictionary.Add( + DriverCommand.GetPageSource, + new CommandInfo("GET", "/session/{sessionId}/source")); + this.commandDictionary.Add(DriverCommand.GetTitle, new CommandInfo("GET", "/session/{sessionId}/title")); + this.commandDictionary.Add( + DriverCommand.FindElement, + new CommandInfo("POST", "/session/{sessionId}/element")); + this.commandDictionary.Add( + DriverCommand.FindElements, + new CommandInfo("POST", "/session/{sessionId}/elements")); + this.commandDictionary.Add( + DriverCommand.GetActiveElement, + new CommandInfo("POST", "/session/{sessionId}/element/active")); + this.commandDictionary.Add( + DriverCommand.FindChildElement, + new CommandInfo("POST", "/session/{sessionId}/element/{id}/element")); + this.commandDictionary.Add( + DriverCommand.FindChildElements, + new CommandInfo("POST", "/session/{sessionId}/element/{id}/elements")); + this.commandDictionary.Add( + DriverCommand.DescribeElement, + new CommandInfo("GET", "/session/{sessionId}/element/{id}")); + this.commandDictionary.Add( + DriverCommand.ClickElement, + new CommandInfo("POST", "/session/{sessionId}/element/{id}/click")); + this.commandDictionary.Add( + DriverCommand.GetElementText, + new CommandInfo("GET", "/session/{sessionId}/element/{id}/text")); + this.commandDictionary.Add( + DriverCommand.SubmitElement, + new CommandInfo("POST", "/session/{sessionId}/element/{id}/submit")); + this.commandDictionary.Add( + DriverCommand.SendKeysToElement, + new CommandInfo("POST", "/session/{sessionId}/element/{id}/value")); + this.commandDictionary.Add( + DriverCommand.GetElementTagName, + new CommandInfo("GET", "/session/{sessionId}/element/{id}/name")); + this.commandDictionary.Add( + DriverCommand.ClearElement, + new CommandInfo("POST", "/session/{sessionId}/element/{id}/clear")); + this.commandDictionary.Add( + DriverCommand.IsElementSelected, + new CommandInfo("GET", "/session/{sessionId}/element/{id}/selected")); + this.commandDictionary.Add( + DriverCommand.IsElementEnabled, + new CommandInfo("GET", "/session/{sessionId}/element/{id}/enabled")); + this.commandDictionary.Add( + DriverCommand.IsElementDisplayed, + new CommandInfo("GET", "/session/{sessionId}/element/{id}/displayed")); + this.commandDictionary.Add( + DriverCommand.GetElementLocation, + new CommandInfo("GET", "/session/{sessionId}/element/{id}/location")); + this.commandDictionary.Add( + DriverCommand.GetElementLocationOnceScrolledIntoView, + new CommandInfo("GET", "/session/{sessionId}/element/{id}/location_in_view")); + this.commandDictionary.Add( + DriverCommand.GetElementSize, + new CommandInfo("GET", "/session/{sessionId}/element/{id}/size")); + this.commandDictionary.Add( + DriverCommand.GetElementValueOfCssProperty, + new CommandInfo("GET", "/session/{sessionId}/element/{id}/css/{propertyName}")); + this.commandDictionary.Add( + DriverCommand.GetElementAttribute, + new CommandInfo("GET", "/session/{sessionId}/element/{id}/attribute/{name}")); + this.commandDictionary.Add( + DriverCommand.ElementEquals, + new CommandInfo("GET", "/session/{sessionId}/element/{id}/equals/{other}")); + this.commandDictionary.Add(DriverCommand.Close, new CommandInfo("DELETE", "/session/{sessionId}/window")); + this.commandDictionary.Add( + DriverCommand.GetWindowSize, + new CommandInfo("GET", "/session/{sessionId}/window/{windowHandle}/size")); + this.commandDictionary.Add( + DriverCommand.SetWindowSize, + new CommandInfo("POST", "/session/{sessionId}/window/{windowHandle}/size")); + this.commandDictionary.Add( + DriverCommand.GetWindowPosition, + new CommandInfo("GET", "/session/{sessionId}/window/{windowHandle}/position")); + this.commandDictionary.Add( + DriverCommand.SetWindowPosition, + new CommandInfo("POST", "/session/{sessionId}/window/{windowHandle}/position")); + this.commandDictionary.Add( + DriverCommand.MaximizeWindow, + new CommandInfo("POST", "/session/{sessionId}/window/{windowHandle}/maximize")); + this.commandDictionary.Add( + DriverCommand.GetOrientation, + new CommandInfo("GET", "/session/{sessionId}/orientation")); + this.commandDictionary.Add( + DriverCommand.SetOrientation, + new CommandInfo("POST", "/session/{sessionId}/orientation")); + this.commandDictionary.Add( + DriverCommand.DismissAlert, + new CommandInfo("POST", "/session/{sessionId}/dismiss_alert")); + this.commandDictionary.Add( + DriverCommand.AcceptAlert, + new CommandInfo("POST", "/session/{sessionId}/accept_alert")); + this.commandDictionary.Add( + DriverCommand.GetAlertText, + new CommandInfo("GET", "/session/{sessionId}/alert_text")); + this.commandDictionary.Add( + DriverCommand.SetAlertValue, + new CommandInfo("POST", "/session/{sessionId}/alert_text")); + this.commandDictionary.Add( + DriverCommand.SetTimeout, + new CommandInfo("POST", "/session/{sessionId}/timeouts")); + this.commandDictionary.Add( + DriverCommand.ImplicitlyWait, + new CommandInfo("POST", "/session/{sessionId}/timeouts/implicit_wait")); + this.commandDictionary.Add( + DriverCommand.SetAsyncScriptTimeout, + new CommandInfo("POST", "/session/{sessionId}/timeouts/async_script")); + this.commandDictionary.Add(DriverCommand.MouseClick, new CommandInfo("POST", "/session/{sessionId}/click")); + this.commandDictionary.Add( + DriverCommand.MouseDoubleClick, + new CommandInfo("POST", "/session/{sessionId}/doubleclick")); + this.commandDictionary.Add( + DriverCommand.MouseDown, + new CommandInfo("POST", "/session/{sessionId}/buttondown")); + this.commandDictionary.Add(DriverCommand.MouseUp, new CommandInfo("POST", "/session/{sessionId}/buttonup")); + this.commandDictionary.Add( + DriverCommand.MouseMoveTo, + new CommandInfo("POST", "/session/{sessionId}/moveto")); + this.commandDictionary.Add( + DriverCommand.SendKeysToActiveElement, + new CommandInfo("POST", "/session/{sessionId}/keys")); + this.commandDictionary.Add( + DriverCommand.TouchSingleTap, + new CommandInfo("POST", "/session/{sessionId}/touch/click")); + this.commandDictionary.Add( + DriverCommand.TouchPress, + new CommandInfo("POST", "/session/{sessionId}/touch/down")); + this.commandDictionary.Add( + DriverCommand.TouchRelease, + new CommandInfo("POST", "/session/{sessionId}/touch/up")); + this.commandDictionary.Add( + DriverCommand.TouchMove, + new CommandInfo("POST", "/session/{sessionId}/touch/move")); + this.commandDictionary.Add( + DriverCommand.TouchScroll, + new CommandInfo("POST", "/session/{sessionId}/touch/scroll")); + this.commandDictionary.Add( + DriverCommand.TouchDoubleTap, + new CommandInfo("POST", "/session/{sessionId}/touch/doubleclick")); + this.commandDictionary.Add( + DriverCommand.TouchLongPress, + new CommandInfo("POST", "/session/{sessionId}/touch/longclick")); + this.commandDictionary.Add( + DriverCommand.TouchFlick, + new CommandInfo("POST", "/session/{sessionId}/touch/flick")); + this.commandDictionary.Add(DriverCommand.UploadFile, new CommandInfo("POST", "/session/{sessionId}/file")); + } + + #endregion + } +} diff --git a/src/Winium.Desktop.Driver/Winium.Desktop.Driver.csproj b/src/Winium.Desktop.Driver/Winium.Desktop.Driver.csproj new file mode 100644 index 0000000..9f523bb --- /dev/null +++ b/src/Winium.Desktop.Driver/Winium.Desktop.Driver.csproj @@ -0,0 +1,107 @@ + + + + + Debug + AnyCPU + {B214C2BA-43FA-486F-AD0B-D4890C1748C0} + Exe + Properties + Winium.Desktop.Driver + Winium.Desktop.Driver + v4.5.1 + 512 + ..\ + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\CommandLineParser.1.9.71\lib\net45\CommandLine.dll + + + ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + + + False + ..\packages\NLog.3.1.0.0\lib\net45\NLog.dll + + + + + + + ..\packages\InputSimulator.1.0.4.0\lib\net20\WindowsInput.dll + + + False + ..\packages\Winium.Cruciatus.2.0.0\lib\net45\Winium.Cruciatus.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {3c8d0b9c-576b-4778-97b1-6839aa944aee} + Winium.StoreApps.Common + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/Winium.Desktop.Driver/packages.config b/src/Winium.Desktop.Driver/packages.config new file mode 100644 index 0000000..e74c3f3 --- /dev/null +++ b/src/Winium.Desktop.Driver/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Winium.StoreApps.Common/Command.cs b/src/Winium.StoreApps.Common/Command.cs new file mode 100644 index 0000000..82333a2 --- /dev/null +++ b/src/Winium.StoreApps.Common/Command.cs @@ -0,0 +1,80 @@ +namespace Winium.StoreApps.Common +{ + #region + + using System.Collections.Generic; + + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + #endregion + + public class Command + { + #region Fields + + private IDictionary commandParameters = new JObject(); + + #endregion + + #region Constructors and Destructors + + public Command(string name, IDictionary parameters) + { + this.Name = name; + if (parameters != null) + { + this.Parameters = parameters; + } + } + + public Command(string name, string jsonParameters) + : this(name, string.IsNullOrEmpty(jsonParameters) ? null : JObject.Parse(jsonParameters)) + { + } + + public Command(string name) + { + this.Name = name; + } + + public Command() + { + } + + #endregion + + #region Public Properties + + /// + /// Gets the command name + /// + [JsonProperty("name")] + public string Name { get; set; } + + /// + /// Gets the parameters of the command + /// + [JsonProperty("parameters")] + public IDictionary Parameters + { + get + { + return this.commandParameters; + } + + set + { + this.commandParameters = value; + } + } + + /// + /// Gets the SessionID of the command + /// + [JsonProperty("sessionId")] + public string SessionId { get; set; } + + #endregion + } +} diff --git a/src/Winium.StoreApps.Common/CommandInfo.cs b/src/Winium.StoreApps.Common/CommandInfo.cs new file mode 100644 index 0000000..e85b3a3 --- /dev/null +++ b/src/Winium.StoreApps.Common/CommandInfo.cs @@ -0,0 +1,33 @@ +namespace Winium.StoreApps.Common +{ + public class CommandInfo + { + #region Constants + + public const string DeleteCommand = "DELETE"; + + public const string GetCommand = "GET"; + + public const string PostCommand = "POST"; + + #endregion + + #region Constructors and Destructors + + public CommandInfo(string method, string resourcePath) + { + this.ResourcePath = resourcePath; + this.Method = method; + } + + #endregion + + #region Public Properties + + public string Method { get; set; } + + public string ResourcePath { get; set; } + + #endregion + } +} diff --git a/src/Winium.StoreApps.Common/CommandResponse.cs b/src/Winium.StoreApps.Common/CommandResponse.cs new file mode 100644 index 0000000..a2b6e7d --- /dev/null +++ b/src/Winium.StoreApps.Common/CommandResponse.cs @@ -0,0 +1,33 @@ +namespace Winium.StoreApps.Common +{ + #region + + using System.Net; + + #endregion + + public class CommandResponse + { + #region Public Properties + + public string Content { get; set; } + + public HttpStatusCode HttpStatusCode { get; set; } + + #endregion + + #region Public Methods and Operators + + public static CommandResponse Create(HttpStatusCode code, string content) + { + return new CommandResponse { HttpStatusCode = code, Content = content }; + } + + public override string ToString() + { + return string.Format("{0}: {1}", this.HttpStatusCode, this.Content); + } + + #endregion + } +} diff --git a/src/Winium.StoreApps.Common/DriverCommand.cs b/src/Winium.StoreApps.Common/DriverCommand.cs new file mode 100644 index 0000000..40391f6 --- /dev/null +++ b/src/Winium.StoreApps.Common/DriverCommand.cs @@ -0,0 +1,470 @@ +// Copied from OpenQA +namespace Winium.StoreApps.Common +{ + /// + /// Values describing the list of commands understood by a remote server using the JSON wire protocol. + /// + /// + public static class DriverCommand + { + #region Static Fields + + /// + /// Represents the AcceptAlert command + /// + /// + public static readonly string AcceptAlert = "acceptAlert"; + + /// + /// Represents adding a cookie command + /// + /// + public static readonly string AddCookie = "addCookie"; + + /// + /// Represents ClearElement command + /// + /// + public static readonly string ClearElement = "clearElement"; + + /// + /// Represents ClickElement command + /// + /// + public static readonly string ClickElement = "clickElement"; + + /// + /// Represents a Browser close command + /// + /// + public static readonly string Close = "close"; + + /// + /// Represents the Define Driver Mapping command + /// + /// + public static readonly string DefineDriverMapping = "defineDriverMapping"; + + /// + /// Represents Deleting all cookies command + /// + /// + public static readonly string DeleteAllCookies = "deleteAllCookies"; + + /// + /// Represents deleting a cookie command + /// + /// + public static readonly string DeleteCookie = "deleteCookie"; + + /// + /// Describes an element + /// + /// + public static readonly string DescribeElement = "describeElement"; + + /// + /// Represents the DismissAlert command + /// + /// + public static readonly string DismissAlert = "dismissAlert"; + + /// + /// Represents ElementEquals command + /// + /// + public static readonly string ElementEquals = "elementEquals"; + + /// + /// Represents ExecuteAsyncScript command + /// + /// + public static readonly string ExecuteAsyncScript = "executeAsyncScript"; + + /// + /// Represents ExecuteScript command + /// + /// + public static readonly string ExecuteScript = "executeScript"; + + /// + /// Represents FindChildElement command + /// + /// + public static readonly string FindChildElement = "findChildElement"; + + /// + /// Represents FindChildElements command + /// + /// + public static readonly string FindChildElements = "findChildElements"; + + /// + /// Represents FindElement command + /// + /// + public static readonly string FindElement = "findElement"; + + /// + /// Represents FindElements command + /// + /// + public static readonly string FindElements = "findElements"; + + /// + /// Represents a GET command + /// + /// + public static readonly string Get = "get"; + + /// + /// Represents GetActiveElement command + /// + /// + public static readonly string GetActiveElement = "getActiveElement"; + + /// + /// Represents the GetAlertText command + /// + /// + public static readonly string GetAlertText = "getAlertText"; + + /// + /// Represents getting all cookies command + /// + /// + public static readonly string GetAllCookies = "getCookies"; + + /// + /// Represents GetCurrentUrl command + /// + /// + public static readonly string GetCurrentUrl = "getCurrentUrl"; + + /// + /// Represents GetCurrentWindowHandle command + /// + /// + public static readonly string GetCurrentWindowHandle = "getCurrentWindowHandle"; + + /// + /// Represents GetElementAttribute command + /// + /// + public static readonly string GetElementAttribute = "getElementAttribute"; + + /// + /// Represents GetElementLocation command + /// + /// + public static readonly string GetElementLocation = "getElementLocation"; + + /// + /// Represents GetElementLocationOnceScrolledIntoView command + /// + /// + public static readonly string GetElementLocationOnceScrolledIntoView = "getElementLocationOnceScrolledIntoView"; + + /// + /// Represents GetElementSize command + /// + /// + public static readonly string GetElementSize = "getElementSize"; + + /// + /// Represents GetElementTagName command + /// + /// + public static readonly string GetElementTagName = "getElementTagName"; + + /// + /// Represents GetElementText command + /// + /// + public static readonly string GetElementText = "getElementText"; + + /// + /// Represents GetElementValueOfCSSProperty command + /// + /// + public static readonly string GetElementValueOfCssProperty = "getElementValueOfCssProperty"; + + /// + /// Represents GetOrientation command + /// + /// + public static readonly string GetOrientation = "getOrientation"; + + /// + /// Represents GetPageSource command + /// + /// + public static readonly string GetPageSource = "getPageSource"; + + /// + /// Represents the Get Session Capabilities command + /// + /// + public static readonly string GetSessionCapabilities = "getSessionCapabilities"; + + /// + /// Represents the Get Session List command + /// + /// + public static readonly string GetSessionList = "getSessionList"; + + /// + /// Represents GetTitle command + /// + /// + public static readonly string GetTitle = "getTitle"; + + /// + /// Represents GetWindowHandles command + /// + /// + public static readonly string GetWindowHandles = "getWindowHandles"; + + /// + /// Represents GetWindowPosition command + /// + /// + public static readonly string GetWindowPosition = "getWindowPosition"; + + /// + /// Represents GetWindowSize command + /// + /// + public static readonly string GetWindowSize = "getWindowSize"; + + /// + /// Represents a Browser going back command + /// + /// + public static readonly string GoBack = "goBack"; + + /// + /// Represents a Browser going forward command + /// + /// + public static readonly string GoForward = "goForward"; + + /// + /// Represents the ImplicitlyWait command + /// + /// + public static readonly string ImplicitlyWait = "implicitlyWait"; + + /// + /// Represents IsElementDisplayed command + /// + /// + public static readonly string IsElementDisplayed = "isElementDisplayed"; + + /// + /// Represents IsElementEnabled command + /// + /// + public static readonly string IsElementEnabled = "isElementEnabled"; + + /// + /// Represents IsElementSelected command + /// + /// + public static readonly string IsElementSelected = "isElementSelected"; + + /// + /// Represents MaximizeWindow command + /// + /// + public static readonly string MaximizeWindow = "maximizeWindow"; + + /// + /// Represents the MouseClick command. + /// + /// + public static readonly string MouseClick = "mouseClick"; + + /// + /// Represents the MouseDoubleClick command. + /// + /// + public static readonly string MouseDoubleClick = "mouseDoubleClick"; + + /// + /// Represents the MouseDown command. + /// + /// + public static readonly string MouseDown = "mouseDown"; + + /// + /// Represents the MouseMoveTo command. + /// + /// + public static readonly string MouseMoveTo = "mouseMoveTo"; + + /// + /// Represents the MouseUp command. + /// + /// + public static readonly string MouseUp = "mouseUp"; + + /// + /// Represents a New Session command + /// + /// + public static readonly string NewSession = "newSession"; + + /// + /// Represents a browser quit command + /// + /// + public static readonly string Quit = "quit"; + + /// + /// Represents a Browser refreshing command + /// + /// + public static readonly string Refresh = "refresh"; + + /// + /// Represents Screenshot command + /// + /// + public static readonly string Screenshot = "screenshot"; + + /// + /// Represents the SendKeysToActiveElement command. + /// + /// + public static readonly string SendKeysToActiveElement = "sendKeysToActiveElement"; + + /// + /// Represents SendKeysToElements command + /// + /// + public static readonly string SendKeysToElement = "sendKeysToElement"; + + /// + /// Represents the SetAlertValue command + /// + /// + public static readonly string SetAlertValue = "setAlertValue"; + + /// + /// Represents the SetAsyncScriptTimeout command + /// + /// + public static readonly string SetAsyncScriptTimeout = "setScriptTimeout"; + + /// + /// Represents SetOrientation command + /// + /// + public static readonly string SetOrientation = "setOrientation"; + + /// + /// Represents the SetTimeout command + /// + /// + public static readonly string SetTimeout = "setTimeout"; + + /// + /// Represents SetWindowPosition command + /// + /// + public static readonly string SetWindowPosition = "setWindowPosition"; + + /// + /// Represents SetWindowSize command + /// + /// + public static readonly string SetWindowSize = "setWindowSize"; + + /// + /// Represents the Status command. + /// + /// + public static readonly string Status = "status"; + + /// + /// Represents SubmitElement command + /// + /// + public static readonly string SubmitElement = "submitElement"; + + /// + /// Represents SwitchToFrame command + /// + /// + public static readonly string SwitchToFrame = "switchToFrame"; + + /// + /// Represents SwitchToParentFrame command + /// + /// + public static readonly string SwitchToParentFrame = "switchToParentFrame"; + + /// + /// Represents SwitchToWindow command + /// + /// + public static readonly string SwitchToWindow = "switchToWindow"; + + /// + /// Represents the TouchDoubleTap command. + /// + /// + public static readonly string TouchDoubleTap = "touchDoubleTap"; + + /// + /// Represents the TouchFlick command. + /// + /// + public static readonly string TouchFlick = "touchFlick"; + + /// + /// Represents the TouchLongPress command. + /// + /// + public static readonly string TouchLongPress = "touchLongPress"; + + /// + /// Represents the TouchMove command. + /// + /// + public static readonly string TouchMove = "touchMove"; + + /// + /// Represents the TouchPress command. + /// + /// + public static readonly string TouchPress = "touchDown"; + + /// + /// Represents the TouchRelease command. + /// + /// + public static readonly string TouchRelease = "touchUp"; + + /// + /// Represents the TouchScroll command. + /// + /// + public static readonly string TouchScroll = "touchScroll"; + + /// + /// Represents the TouchSingleTap command. + /// + /// + public static readonly string TouchSingleTap = "touchSingleTap"; + + /// + /// Represents the UploadFile command. + /// + /// + public static readonly string UploadFile = "uploadFile"; + + #endregion + } +} diff --git a/src/Winium.StoreApps.Common/Exceptions/AutomationException.cs b/src/Winium.StoreApps.Common/Exceptions/AutomationException.cs new file mode 100644 index 0000000..58909c2 --- /dev/null +++ b/src/Winium.StoreApps.Common/Exceptions/AutomationException.cs @@ -0,0 +1,58 @@ +namespace Winium.StoreApps.Common.Exceptions +{ + #region + + using System; + + #endregion + + public class AutomationException : Exception + { + #region Fields + + private ResponseStatus responseStatus = ResponseStatus.UnknownError; + + #endregion + + #region Constructors and Destructors + + public AutomationException() + { + } + + public AutomationException(string message, ResponseStatus status) + : base(message) + { + this.Status = status; + } + + public AutomationException(string message, params object[] args) + : base(string.Format(message, args)) + { + } + + public AutomationException(string message, Exception innerException) + : base(message, innerException) + { + } + + #endregion + + #region Public Properties + + public ResponseStatus Status + { + get + { + return this.responseStatus; + } + + set + { + this.responseStatus = value; + } + } + + #endregion + } +} diff --git a/src/Winium.StoreApps.Common/Exceptions/InnerDriverRequestException.cs b/src/Winium.StoreApps.Common/Exceptions/InnerDriverRequestException.cs new file mode 100644 index 0000000..dca7762 --- /dev/null +++ b/src/Winium.StoreApps.Common/Exceptions/InnerDriverRequestException.cs @@ -0,0 +1,42 @@ +namespace Winium.StoreApps.Common.Exceptions +{ + #region + + using System; + using System.Net; + + #endregion + + public class InnerDriverRequestException : Exception + { + #region Constructors and Destructors + + public InnerDriverRequestException() + { + } + + public InnerDriverRequestException(string message, HttpStatusCode statusCode) + : base(message) + { + this.StatusCode = statusCode; + } + + public InnerDriverRequestException(string message, params object[] args) + : base(string.Format(message, args)) + { + } + + public InnerDriverRequestException(string message, Exception innerException) + : base(message, innerException) + { + } + + #endregion + + #region Public Properties + + public HttpStatusCode StatusCode { get; set; } + + #endregion + } +} diff --git a/src/Winium.StoreApps.Common/HttpResponseHelper.cs b/src/Winium.StoreApps.Common/HttpResponseHelper.cs new file mode 100644 index 0000000..4496168 --- /dev/null +++ b/src/Winium.StoreApps.Common/HttpResponseHelper.cs @@ -0,0 +1,73 @@ +namespace Winium.StoreApps.Common +{ + #region + + using System.Collections.Generic; + using System.Net; + using System.Text; + + #endregion + + public static class HttpResponseHelper + { + #region Constants + + private const string JsonContentType = "application/json;charset=UTF-8"; + + private const string PlainTextContentType = "text/plain"; + + #endregion + + #region Static Fields + + private static Dictionary statusCodeDescriptors; + + #endregion + + #region Public Properties + + public static Dictionary StatusCodeDescriptors + { + get + { + return statusCodeDescriptors + ?? (statusCodeDescriptors = + new Dictionary + { + { HttpStatusCode.OK, "OK" }, + { HttpStatusCode.BadRequest, "Bad Request" }, + { HttpStatusCode.NotFound, "Not Found" }, + { HttpStatusCode.NotImplemented, "Not Implemented" } + }); + } + } + + #endregion + + #region Public Methods and Operators + + public static bool IsClientError(int code) + { + return code >= 400 && code < 500; + } + + public static string ResponseString(HttpStatusCode statusCode, string content) + { + var contentType = IsClientError((int)statusCode) ? PlainTextContentType : JsonContentType; + + string statusDescription; + StatusCodeDescriptors.TryGetValue(statusCode, out statusDescription); + + var responseString = new StringBuilder(); + responseString.AppendLine(string.Format("HTTP/1.1 {0} {1}", (int)statusCode, statusDescription)); + responseString.AppendLine(string.Format("Content-Type: {0}", contentType)); + responseString.AppendLine("Connection: close"); + responseString.AppendLine(string.Empty); + responseString.AppendLine(content); + + return responseString.ToString(); + } + + #endregion + } +} diff --git a/src/Winium.StoreApps.Common/JsonWireClasses.cs b/src/Winium.StoreApps.Common/JsonWireClasses.cs new file mode 100644 index 0000000..49e3ab0 --- /dev/null +++ b/src/Winium.StoreApps.Common/JsonWireClasses.cs @@ -0,0 +1,55 @@ +// +namespace Winium.StoreApps.Common +{ + #region + + using Newtonsoft.Json; + + #endregion + + public class JsonWebElementContent + { + #region Constructors and Destructors + + public JsonWebElementContent(string element) + { + this.Element = element; + } + + #endregion + + #region Public Properties + + [JsonProperty("ELEMENT")] + public string Element { get; set; } + + #endregion + } + + public class JsonResponse + { + #region Constructors and Destructors + + public JsonResponse(string sessionId, ResponseStatus responseCode, object value) + { + this.SessionId = sessionId; + this.Status = responseCode; + this.Value = value; + } + + #endregion + + #region Public Properties + + [JsonProperty("sessionId")] + public string SessionId { get; set; } + + [JsonProperty("status")] + public ResponseStatus Status { get; set; } + + [JsonProperty("value")] + public object Value { get; set; } + + #endregion + } +} diff --git a/src/Winium.StoreApps.Common/Properties/AssemblyInfo.cs b/src/Winium.StoreApps.Common/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b075b41 --- /dev/null +++ b/src/Winium.StoreApps.Common/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +#region + +using System.Reflection; +using System.Resources; + +#endregion + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Winium.StoreApps.Common")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Winium.StoreApps.Common")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// Major Version +// Minor Version +// Build Number +// Revision +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.*")] diff --git a/src/Winium.StoreApps.Common/ResponseStatus.cs b/src/Winium.StoreApps.Common/ResponseStatus.cs new file mode 100644 index 0000000..ab8cb47 --- /dev/null +++ b/src/Winium.StoreApps.Common/ResponseStatus.cs @@ -0,0 +1,55 @@ +namespace Winium.StoreApps.Common +{ + public enum ResponseStatus + { + Success = 0, + + NoSuchDriver = 6, + + NoSuchElement = 7, + + NoSuchFrame = 8, + + UnknownCommand = 9, + + StaleElementReference = 10, + + ElementNotVisible = 11, + + InvalidElementState = 12, + + UnknownError = 13, + + ElementIsNotSelectable = 15, + + JavaScriptError = 17, + + XPathLookupError = 19, + + Timeout = 21, + + NoSuchWindow = 23, + + InvalidCookieDomain = 24, + + UnableToSetCookie = 25, + + UnexpectedAlertOpen = 26, + + NoAlertOpenError = 27, + + ScriptTimeout = 28, + + InvalidElementCoordinates = 29, + + ImeNotAvailable = 30, + + ImeEngineActivationFailed = 31, + + InvalidSelector = 32, + + SessionNotCreatedException = 33, + + MoveTargetOutOfBounds = 34 + } +} diff --git a/src/Winium.StoreApps.Common/Winium.StoreApps.Common.csproj b/src/Winium.StoreApps.Common/Winium.StoreApps.Common.csproj new file mode 100644 index 0000000..1144dd8 --- /dev/null +++ b/src/Winium.StoreApps.Common/Winium.StoreApps.Common.csproj @@ -0,0 +1,73 @@ + + + + + 12.0 + Debug + AnyCPU + {3C8D0B9C-576B-4778-97B1-6839AA944AEE} + Library + Properties + Winium.StoreApps.Common + Winium.StoreApps.Common + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile151 + v4.6 + ..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + ..\packages\Newtonsoft.Json.6.0.8\lib\portable-net40+sl5+wp80+win8+wpa81\Newtonsoft.Json.dll + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/Winium.StoreApps.Common/packages.config b/src/Winium.StoreApps.Common/packages.config new file mode 100644 index 0000000..fa6b7f1 --- /dev/null +++ b/src/Winium.StoreApps.Common/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Winium.sln b/src/Winium.sln new file mode 100644 index 0000000..acddaa3 --- /dev/null +++ b/src/Winium.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Winium.Desktop.Driver", "Winium.Desktop.Driver\Winium.Desktop.Driver.csproj", "{B214C2BA-43FA-486F-AD0B-D4890C1748C0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{56A74D50-0B52-4130-BF77-C5BD2B3441A6}" + ProjectSection(SolutionItems) = preProject + Settings.StyleCop = Settings.StyleCop + Winium.sln.DotSettings = Winium.sln.DotSettings + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Winium.StoreApps.Common", "Winium.StoreApps.Common\Winium.StoreApps.Common.csproj", "{3C8D0B9C-576B-4778-97B1-6839AA944AEE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{7F1DBF6F-6390-429F-BAD0-08D85DE19E0C}" + ProjectSection(SolutionItems) = preProject + .nuget\NuGet.Config = .nuget\NuGet.Config + .nuget\NuGet.targets = .nuget\NuGet.targets + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B214C2BA-43FA-486F-AD0B-D4890C1748C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B214C2BA-43FA-486F-AD0B-D4890C1748C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B214C2BA-43FA-486F-AD0B-D4890C1748C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B214C2BA-43FA-486F-AD0B-D4890C1748C0}.Release|Any CPU.Build.0 = Release|Any CPU + {3C8D0B9C-576B-4778-97B1-6839AA944AEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C8D0B9C-576B-4778-97B1-6839AA944AEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C8D0B9C-576B-4778-97B1-6839AA944AEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C8D0B9C-576B-4778-97B1-6839AA944AEE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/Winium.sln.DotSettings b/src/Winium.sln.DotSettings new file mode 100644 index 0000000..7308043 --- /dev/null +++ b/src/Winium.sln.DotSettings @@ -0,0 +1,444 @@ + + <?xml version="1.0" encoding="utf-16"?><Profile name="StyleCop"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>True</EmbraceInRegion><RegionName>using</RegionName></CSOptimizeUsings><CSReformatCode>True</CSReformatCode><CSReorderTypeMembers>True</CSReorderTypeMembers><StyleCop.Documentation><SA1600ElementsMustBeDocumented>False</SA1600ElementsMustBeDocumented><SA1604ElementDocumentationMustHaveSummary>False</SA1604ElementDocumentationMustHaveSummary><SA1609PropertyDocumentationMustHaveValueDocumented>False</SA1609PropertyDocumentationMustHaveValueDocumented><SA1611ElementParametersMustBeDocumented>False</SA1611ElementParametersMustBeDocumented><SA1615ElementReturnValueMustBeDocumented>False</SA1615ElementReturnValueMustBeDocumented><SA1617VoidReturnValueMustNotBeDocumented>False</SA1617VoidReturnValueMustNotBeDocumented><SA1618GenericTypeParametersMustBeDocumented>False</SA1618GenericTypeParametersMustBeDocumented><SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes>False</SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes><SA1628DocumentationTextMustBeginWithACapitalLetter>False</SA1628DocumentationTextMustBeginWithACapitalLetter><SA1629DocumentationTextMustEndWithAPeriod>False</SA1629DocumentationTextMustEndWithAPeriod><SA1633SA1641UpdateFileHeader>Ignore</SA1633SA1641UpdateFileHeader><SA1639FileHeaderMustHaveSummary>False</SA1639FileHeaderMustHaveSummary><SA1642ConstructorSummaryDocumentationMustBeginWithStandardText>False</SA1642ConstructorSummaryDocumentationMustBeginWithStandardText><SA1643DestructorSummaryDocumentationMustBeginWithStandardText>False</SA1643DestructorSummaryDocumentationMustBeginWithStandardText><SA1644DocumentationHeadersMustNotContainBlankLines>False</SA1644DocumentationHeadersMustNotContainBlankLines></StyleCop.Documentation><StyleCop.Layout><SA1500CurlyBracketsForMultiLineStatementsMustNotShareLine>True</SA1500CurlyBracketsForMultiLineStatementsMustNotShareLine><SA1509OpeningCurlyBracketsMustNotBePrecededByBlankLine>True</SA1509OpeningCurlyBracketsMustNotBePrecededByBlankLine><SA1510ChainedStatementBlocksMustNotBePrecededByBlankLine>True</SA1510ChainedStatementBlocksMustNotBePrecededByBlankLine><SA1511WhileDoFooterMustNotBePrecededByBlankLine>True</SA1511WhileDoFooterMustNotBePrecededByBlankLine><SA1512SingleLineCommentsMustNotBeFollowedByBlankLine>True</SA1512SingleLineCommentsMustNotBeFollowedByBlankLine><SA1513ClosingCurlyBracketMustBeFollowedByBlankLine>True</SA1513ClosingCurlyBracketMustBeFollowedByBlankLine><SA1514ElementDocumentationHeaderMustBePrecededByBlankLine>True</SA1514ElementDocumentationHeaderMustBePrecededByBlankLine><SA1515SingleLineCommentMustBeProceededByBlankLine>True</SA1515SingleLineCommentMustBeProceededByBlankLine></StyleCop.Layout><StyleCop.Maintainability><SA1119StatementMustNotUseUnnecessaryParenthesis>True</SA1119StatementMustNotUseUnnecessaryParenthesis></StyleCop.Maintainability><StyleCop.Ordering><AlphabeticalUsingDirectives>Alphabetical</AlphabeticalUsingDirectives><ExpandUsingDirectives>FullyQualify</ExpandUsingDirectives><SA1212PropertyAccessorsMustFollowOrder>True</SA1212PropertyAccessorsMustFollowOrder><SA1213EventAccessorsMustFollowOrder>True</SA1213EventAccessorsMustFollowOrder></StyleCop.Ordering><StyleCop.Readability><SA1100DoNotPrefixCallsWithBaseUnlessLocalImplementationExists>True</SA1100DoNotPrefixCallsWithBaseUnlessLocalImplementationExists><SA1106CodeMustNotContainEmptyStatements>True</SA1106CodeMustNotContainEmptyStatements><SA1108BlockStatementsMustNotContainEmbeddedComments>True</SA1108BlockStatementsMustNotContainEmbeddedComments><SA1109BlockStatementsMustNotContainEmbeddedRegions>True</SA1109BlockStatementsMustNotContainEmbeddedRegions><SA1120CommentsMustContainText>True</SA1120CommentsMustContainText><SA1121UseBuiltInTypeAlias>True</SA1121UseBuiltInTypeAlias><SA1122UseStringEmptyForEmptyStrings>True</SA1122UseStringEmptyForEmptyStrings><SA1123DoNotPlaceRegionsWithinElements>True</SA1123DoNotPlaceRegionsWithinElements><SA1124CodeMustNotContainEmptyRegions>True</SA1124CodeMustNotContainEmptyRegions></StyleCop.Readability><StyleCop.Spacing><SA1001CommasMustBeSpacedCorrectly>True</SA1001CommasMustBeSpacedCorrectly><SA1005SingleLineCommentsMustBeginWithSingleSpace>True</SA1005SingleLineCommentsMustBeginWithSingleSpace><SA1006PreprocessorKeywordsMustNotBePrecededBySpace>True</SA1006PreprocessorKeywordsMustNotBePrecededBySpace><SA1021NegativeSignsMustBeSpacedCorrectly>True</SA1021NegativeSignsMustBeSpacedCorrectly><SA1022PositiveSignsMustBeSpacedCorrectly>True</SA1022PositiveSignsMustBeSpacedCorrectly><SA1025CodeMustNotContainMultipleWhitespaceInARow>True</SA1025CodeMustNotContainMultipleWhitespaceInARow></StyleCop.Spacing></Profile> + StyleCop + StyleCop + True + True + True + True + True + True + True + True + True + NEXT_LINE_SHIFTED_2 + 1 + 1 + 1 + 1 + 1 + NEXT_LINE_SHIFTED_2 + SEPARATE + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + NEXT_LINE_SHIFTED_2 + 1 + 1 + False + True + public protected internal private static new abstract virtual override sealed readonly extern unsafe volatile async + False + False + False + False + True + ALWAYS_USE + ON_SINGLE_LINE + False + True + False + False + True + False + True + True + CHOP_IF_LONG + True + True + CHOP_IF_LONG + CHOP_IF_LONG + <?xml version="1.0" encoding="utf-8"?> +<!-- Last updated 15.05.2012 --> +<Patterns xmlns="urn:shemas-jetbrains-com:member-reordering-patterns"> + + <!-- Do not reorder COM interfaces --> + <Pattern> + <Match> + <And Weight="2000"> + <Kind Is="interface"/> + <Or> + <HasAttribute CLRName="System.Runtime.InteropServices.InterfaceTypeAttribute"/> + <HasAttribute CLRName="System.Runtime.InteropServices.ComImport"/> + </Or> + </And> + </Match> + </Pattern> + + <!-- Do not reorder P/Invoke structs --> + <Pattern> + <Match> + <And Weight="2000"> + <Or> + <Kind Is="struct"/> + <Kind Is="class"/> + </Or> + <HasAttribute CLRName="System.Runtime.InteropServices.StructLayoutAttribute"/> + </And> + </Match> + </Pattern> + + <!-- Do not reorder P/Invoke classes (called xxxNativeMethods) --> + <Pattern> + <Match> + <And Weight="2000"> + <Kind Is="class"/> + <Name Is=".*NativeMethods" /> + </And> + </Match> + </Pattern> + + <!-- StyleCop pattern --> + <Pattern RemoveAllRegions="true"> + <Match> + <Or Weight="1000" > + <Kind Is="class" /> + <Kind Is="struct" /> + <Kind Is="interface"/> + </Or> + </Match> + + <!-- Constants --> + <Entry> + <Match> + <Kind Is="constant"/> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private"/> + <Name/> + </Sort> + <Group Region="Constants"/> + </Entry> + + <!-- Static fields --> + <Entry> + <Match> + <And> + <Kind Is="field"/> + <Static /> + </And> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private"/> + <Readonly/> + <Name/> + </Sort> + <Group Region="Static Fields"/> + </Entry> + + <!-- Fields --> + <Entry> + <Match> + <Kind Is="field"/> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private"/> + <Readonly/> + <Name/> + </Sort> + <Group Region="Fields"/> + </Entry> + + <!-- constructors and destructors --> + <Entry> + <Match> + <Or Weight="200"> + <Kind Is="constructor"/> + <Kind Is="destructor"/> + </Or> + </Match> + <Sort> + <Static/> + <Kind Order="constructor destructor"/> + <Access Order="public internal protected-internal protected private"/> + </Sort> + <Group Region="Constructors and Destructors"/> + </Entry> + + <!-- delegates --> + <Entry> + <Match> + <Kind Is="delegate"/> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private" /> + <Static /> + <Name/> + </Sort> + <Group Region="Delegates"/> + </Entry> + + <!-- public events --> + <Entry> + <Match> + <And> + <Kind Is="event"/> + <Access Is="public"/> + </And> + </Match> + <Sort> + <Access Order="public" /> + <Static /> + <Name/> + </Sort> + <Group Region="Public Events"/> + </Entry> + + <!-- interface events --> + <Entry> + <Match> + <And> + <Kind Is="event"/> + <ImplementsInterface/> + </And> + </Match> + <Sort> + <ImplementsInterface Immediate="true"/> + <Name/> + </Sort> + <Group Region="Explicit Interface Events" /> + </Entry> + + <!-- other events --> + <Entry> + <Match> + <Kind Is="event"/> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private" /> + <Static /> + <Name/> + </Sort> + <Group Region="Events"/> + </Entry> + + <!-- enum --> + <Entry> + <Match> + <Kind Is="enum"/> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private" /> + <Name/> + </Sort> + <Group Region="Enums"/> + </Entry> + + <!-- interfaces --> + <Entry> + <Match> + <Kind Is="interface" /> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private" /> + <Name/> + </Sort> + <Group Region="Interfaces"/> + </Entry> + + <!-- public properties --> + <Entry> + <Match> + <And> + <Kind Is="property"/> + <Access Is="public"/> + </And> + </Match> + <Sort> + <Access Order="public"/> + <Static/> + <Name/> + </Sort> + <Group Region="Public Properties"/> + </Entry> + + <!-- interface properties --> + <Entry> + <Match> + <And> + <Kind Is="property"/> + <ImplementsInterface/> + </And> + </Match> + <Sort> + <ImplementsInterface Immediate="true"/> + <Name/> + </Sort> + <Group Region="Explicit Interface Properties" /> + </Entry> + + <!-- other properties --> + <Entry> + <Match> + <Kind Is="property"/> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private"/> + <Static/> + <Name/> + </Sort> + <Group Region="Properties"/> + </Entry> + + <!-- public indexers --> + <Entry> + <Match> + <And> + <Kind Is="indexer" Weight="1000" /> + <Access Is="public"/> + </And> + </Match> + <Sort> + <Access Order="public" /> + <Static/> + <Name/> + </Sort> + <Group Region="Public Indexers"/> + </Entry> + + <!-- interface indexers --> + <Entry> + <Match> + <And> + <Kind Is="indexer" Weight="1000"/> + <ImplementsInterface/> + </And> + </Match> + <Sort> + <ImplementsInterface Immediate="true"/> + <Name/> + </Sort> + <Group Region="Explicit Interface Indexers" /> + </Entry> + + <!-- other indexers --> + <Entry> + <Match> + <Kind Is="indexer" Weight="1000" /> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private" /> + <Static/> + <Name/> + </Sort> + <Group Region="Indexers"/> + </Entry> + + <!-- public methods (includes operators) --> + <Entry> + <Match> + <And> + <Or> + <Kind Is="method"/> + <Kind Is="operator"/> + </Or> + <Access Is="public"/> + </And> + </Match> + <Sort> + <Access Order="public"/> + <Static/> + <Name/> + </Sort> + <Group Region="Public Methods and Operators"/> + </Entry> + + <!-- interface methods --> + <Entry> + <Match> + <And> + <Kind Is="method"/> + <ImplementsInterface/> + </And> + </Match> + <Sort> + <ImplementsInterface Immediate="true"/> + <Name/> + </Sort> + <Group Region="Explicit Interface Methods" /> + </Entry> + + <!-- other methods --> + <Entry> + <Match> + <Kind Is="method"/> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private"/> + <Static/> + <Name/> + </Sort> + <Group Region="Methods"/> + </Entry> + + <!-- operators --> + <Entry> + <Match> + <Kind Is="operator"/> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private" /> + <Static/> + <Name/> + </Sort> + <Group Region="Operators"/> + </Entry> + + <!-- Nested structs --> + <Entry> + <Match> + <Kind Is="struct" + Weight="600" /> + </Match> + <Sort> + <Static /> + <Access Order="public internal protected-internal protected private" /> + <Name/> + </Sort> + </Entry> + + <!-- Nested classes --> + <Entry> + <Match> + <Kind Is="class" + Weight="700" /> + </Match> + <Sort> + <Static /> + <Access Order="public internal protected-internal protected private" /> + <Name/> + </Sort> + </Entry> + + <!-- all other members --> + <Entry/> + + </Pattern> +</Patterns> + CustomLayout + True + True + $object$_On$event$ + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + CSharpOtherPage + OverriddenFalse + OverriddenFalse \ No newline at end of file