├── doxygen_run.cmd ├── webdrivers └── readme.txt ├── images ├── doxy.png ├── framework_01.png ├── framework_02.png ├── page-recorder.png └── knockout-demo01.png ├── src ├── Demo.Tutorial │ ├── Ideas.txt │ ├── DemoPages │ │ ├── Ch_00_Introduction │ │ │ └── simple.html │ │ └── Ch_02_WorkingWithForms │ │ │ ├── bootstrap3demo.html │ │ │ └── bootstrap.min.css │ ├── packages.config │ ├── DemoPages.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Demo.Tutorial.csproj │ └── Ch_00_Introduction.cs ├── MainPage.txt ├── Docs.DoxygenPages │ ├── md_page.md │ └── mainpage.md ├── SWD.Core │ ├── packages.config │ ├── Pages │ │ ├── Invokable.cs │ │ ├── ICheckExpectedWebElements.cs │ │ ├── CorePage.cs │ │ └── SelfTestingCorePage.cs │ ├── WebDriver │ │ ├── JavaScriptErrorOnThePageException.cs │ │ ├── Wait.cs │ │ ├── WebElementExtensions.cs │ │ ├── WebDriverRunner.cs │ │ └── SwdBrowser.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Configuration │ │ └── Config.cs │ └── Swd.Core.csproj ├── Demo.TestModel │ ├── packages.config │ ├── PageDeclarations │ │ ├── EmptyPage.cs │ │ ├── DummyExamplePage.cs │ │ └── CreateNewAccountPage.cs │ ├── MyPageBase.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── MyPages.cs │ └── Demo.TestModel.csproj ├── Demo.TestProject │ ├── packages.config │ ├── Smoke │ │ └── Smoke_test_for_each_pageobject.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── TestBase.cs │ └── Demo.TestProject.csproj ├── App.config ├── Config.config └── SWD.StarterKit.sln ├── tools └── doxygen.exe ├── notes ├── demo-applications.md └── tasks.md ├── .gitignore ├── LICENSE ├── README.md └── methodology_all_in_one_rus.md /doxygen_run.cmd: -------------------------------------------------------------------------------- 1 | .\tools\doxygen -------------------------------------------------------------------------------- /webdrivers/readme.txt: -------------------------------------------------------------------------------- 1 | Put notes on how to download webdrivers here -------------------------------------------------------------------------------- /images/doxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzharii/SWD.Starter/HEAD/images/doxy.png -------------------------------------------------------------------------------- /src/Demo.Tutorial/Ideas.txt: -------------------------------------------------------------------------------- 1 | Page Objects: 2 | 3 | 1. Aggregation/Composition vs Inheritance -------------------------------------------------------------------------------- /tools/doxygen.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzharii/SWD.Starter/HEAD/tools/doxygen.exe -------------------------------------------------------------------------------- /images/framework_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzharii/SWD.Starter/HEAD/images/framework_01.png -------------------------------------------------------------------------------- /images/framework_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzharii/SWD.Starter/HEAD/images/framework_02.png -------------------------------------------------------------------------------- /images/page-recorder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzharii/SWD.Starter/HEAD/images/page-recorder.png -------------------------------------------------------------------------------- /images/knockout-demo01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzharii/SWD.Starter/HEAD/images/knockout-demo01.png -------------------------------------------------------------------------------- /src/MainPage.txt: -------------------------------------------------------------------------------- 1 | SWD Starter MainPage {#mainpage} 2 | ============ 3 | 4 | Documentation that will appear on the main page -------------------------------------------------------------------------------- /src/Docs.DoxygenPages/md_page.md: -------------------------------------------------------------------------------- 1 | Md Page 1 2 | ---------- 3 | 4 | Inside md_page. 5 | 6 | 7 | ~~~~~~~~~~~~~{.py} 8 | # A class 9 | class Dummy: 10 | pass 11 | ~~~~~~~~~~~~~ -------------------------------------------------------------------------------- /src/Demo.Tutorial/DemoPages/Ch_00_Introduction/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | A simple title 4 | 5 | 6 |

This is a simple HTML page

7 | 8 | -------------------------------------------------------------------------------- /src/Docs.DoxygenPages/mainpage.md: -------------------------------------------------------------------------------- 1 | mainpage {#mainpage} 2 | --------- 3 | 4 | Inside Main Page! 5 | 6 | 7 | First Header | Second Header 8 | ------------- | ------------- 9 | Content Cell | Content Cell 10 | Content Cell | Content Cell 11 | -------------------------------------------------------------------------------- /src/SWD.Core/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Demo.TestModel/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Demo.Tutorial/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Demo.TestProject/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /notes/demo-applications.md: -------------------------------------------------------------------------------- 1 | #Demo Applications 2 | 3 | - [jkneb / ember-crud](https://github.com/jkneb/ember-crud) 4 | Simple CRUD for Ember.js 1.1.2 with Ember Data 1.0.0beta3 + Touch gestures mobile (responsive) version + Grunt templates precompiling 5 | http://jkneb.github.io/ember-crud -------------------------------------------------------------------------------- /src/SWD.Core/Pages/Invokable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Swd.Core.Pages 7 | { 8 | public interface Invokable 9 | { 10 | void Invoke(); 11 | bool IsDisplayed(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SWD.Core/Pages/ICheckExpectedWebElements.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Swd.Core.Pages 7 | { 8 | public interface ICheckExpectedWebElements 9 | { 10 | void VerifyExpectedElementsAreDisplayed(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/SWD.Core/WebDriver/JavaScriptErrorOnThePageException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Swd.Core.WebDriver 7 | { 8 | public class JavaScriptErrorOnThePageException : Exception 9 | { 10 | 11 | public JavaScriptErrorOnThePageException(string errorMessage) : base(errorMessage) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SWD.Core/Pages/CorePage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | using OpenQA.Selenium; 7 | using OpenQA.Selenium.Support.PageObjects; 8 | using Swd.Core.WebDriver; 9 | 10 | namespace Swd.Core.Pages 11 | { 12 | public abstract class CorePage 13 | { 14 | public IWebDriver Driver { get { return SwdBrowser.Driver; } } 15 | 16 | public CorePage() 17 | { 18 | PageFactory.InitElements(Driver, this); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /notes/tasks.md: -------------------------------------------------------------------------------- 1 | Some tasks: 2 | 3 | - [x] Add handling for unexpected JavaScript errors 4 | - [ ] Prepare for MSTest run from commandline. 5 | Edit config files from commandline 6 | - [ ] Document how to start ember-crud from scratch: cinst, ruby, grunt etc. 7 | - [ ] Document how to start scrumboard: cinst, python etc. 8 | - [ ] Add NUnit Sample 9 | - [ ] Add FluentAssertions samples 10 | - [ ] Configuration: add MyConfig for custom projects and DemoProject 11 | 12 | 13 | Other starter kits: 14 | 15 | - https://github.com/davidb583/white-angularjs-app 16 | 17 | -------------------------------------------------------------------------------- /src/SWD.Core/Pages/SelfTestingCorePage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | using OpenQA.Selenium; 7 | using OpenQA.Selenium.Support.PageObjects; 8 | using Swd.Core.WebDriver; 9 | 10 | namespace Swd.Core.Pages 11 | { 12 | public abstract class SelfTestingCorePage : CorePage, Invokable, ICheckExpectedWebElements 13 | { 14 | public abstract void Invoke(); 15 | 16 | public abstract bool IsDisplayed(); 17 | 18 | public abstract void VerifyExpectedElementsAreDisplayed(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Demo.TestModel/PageDeclarations/EmptyPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Swd.Core; 6 | using Swd.Core.Pages; 7 | using OpenQA.Selenium; 8 | 9 | namespace Demo.TestModel.PageDeclarations 10 | { 11 | // This is an empty page... 12 | // I was too lazy to implement anything... 13 | public class EmptyPage: MyPageBase 14 | { 15 | public override void Invoke() 16 | { 17 | // Empty 18 | } 19 | 20 | public override bool IsDisplayed() 21 | { 22 | return true; 23 | } 24 | 25 | public override void VerifyExpectedElementsAreDisplayed() 26 | { 27 | // Empty 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Demo.TestModel/PageDeclarations/DummyExamplePage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Swd.Core; 6 | using Swd.Core.Pages; 7 | using OpenQA.Selenium; 8 | 9 | namespace Demo.TestModel.PageDeclarations 10 | { 11 | public class DummyExamplePage : MyPageBase 12 | { 13 | public override void Invoke() 14 | { 15 | throw new NotImplementedException(); 16 | } 17 | 18 | public override bool IsDisplayed() 19 | { 20 | throw new NotImplementedException(); 21 | } 22 | 23 | public override void VerifyExpectedElementsAreDisplayed() 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !bin/readme.txt 2 | bin/* 3 | 4 | !webdrivers/readme.txt 5 | webdrivers/* 6 | 7 | !packages/repositories.config 8 | 9 | docs/api/* 10 | 11 | #OS junk files 12 | [Tt]humbs.db 13 | *.DS_Store 14 | 15 | #Visual Studio files 16 | *.[Oo]bj 17 | *.user 18 | *.aps 19 | *.pch 20 | *.vspscc 21 | *.vssscc 22 | *_i.c 23 | *_p.c 24 | *.ncb 25 | *.suo 26 | *.tlb 27 | *.tlh 28 | *.bak 29 | *.[Cc]ache 30 | *.ilk 31 | *.log 32 | *.lib 33 | *.sbr 34 | *.sdf 35 | *.opensdf 36 | *.unsuccessfulbuild 37 | ipch/ 38 | obj/ 39 | [Bb]in 40 | [Dd]ebug*/ 41 | [Rr]elease*/ 42 | Ankh.NoLoad 43 | 44 | #MonoDevelop 45 | *.pidb 46 | *.userprefs 47 | 48 | #Tooling 49 | _ReSharper*/ 50 | *.resharper 51 | [Tt]est[Rr]esult* 52 | *.sass-cache 53 | 54 | #Project files 55 | [Bb]uild/ 56 | 57 | #Subversion files 58 | .svn 59 | 60 | # Office Temp Files 61 | ~$* 62 | 63 | #NuGet 64 | packages/ 65 | 66 | #ncrunch 67 | *ncrunch* 68 | *crunch*.local.xml 69 | 70 | # visual studio database projects 71 | *.dbmdl 72 | 73 | #Test files 74 | *.testsettings -------------------------------------------------------------------------------- /src/Demo.TestModel/MyPageBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Swd.Core; 6 | using Swd.Core.Pages; 7 | using OpenQA.Selenium; 8 | 9 | namespace Demo.TestModel 10 | { 11 | public abstract class MyPageBase : SelfTestingCorePage, IDisposable 12 | { 13 | 14 | 15 | // Verifies the expected WebElement to be Visible 16 | public virtual void VerifyElementVisible(string elementName, IWebElement webElement) 17 | { 18 | if (!webElement.Displayed) 19 | { 20 | string message = "Error: WebElement with name <" + elementName + ">\n" 21 | + "was expected to be visible," 22 | + "but the element was not found on the page."; 23 | 24 | throw new Exception(); 25 | } 26 | } 27 | 28 | // Override and implement this method, 29 | // in case you want the pages to clean up 30 | public virtual void Dispose() 31 | { 32 | // Does nothing at the moment 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Config.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /src/Demo.Tutorial/DemoPages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Remote; 6 | 7 | using System.IO; 8 | using System.Reflection; 9 | using System.Collections.Generic; 10 | using System.Collections; 11 | 12 | using Swd.Core.WebDriver; 13 | 14 | namespace Demo.Tutorial 15 | { 16 | public static class DemoPages 17 | { 18 | // http://stackoverflow.com/a/283917/1126595 19 | static public string AssemblyDirectory() 20 | { 21 | string codeBase = Assembly.GetExecutingAssembly().CodeBase; 22 | UriBuilder uri = new UriBuilder(codeBase); 23 | string path = Uri.UnescapeDataString(uri.Path); 24 | return Path.GetDirectoryName(path); 25 | } 26 | 27 | public static void OpenLocalHtmlFile(string pageRelativePath) 28 | { 29 | string fullPath = Path.Combine(AssemblyDirectory(), "DemoPages", pageRelativePath); 30 | Uri uri = new Uri(fullPath); 31 | 32 | string uriPath = uri.AbsoluteUri; 33 | 34 | SwdBrowser.Driver.Url = uriPath; 35 | } 36 | 37 | public static void Ch00_OpenHtmlFile(string relativeFilePath) 38 | { 39 | OpenLocalHtmlFile(Path.Combine("Ch_00_Introduction", relativeFilePath)); 40 | } 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Demo.TestProject/Smoke/Smoke_test_for_each_pageobject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | using Demo.TestModel; 5 | using Swd.Core.Pages; 6 | using Swd.Core.WebDriver; 7 | 8 | namespace Demo.TestProject.Smoke 9 | { 10 | 11 | [TestClass] 12 | public class Smoke_test_for_each_pageobject : TestBase 13 | { 14 | 15 | public void PageTest(MyPageBase page) 16 | { 17 | // Implement Dispose inside page object in order to do cleanup 18 | using (page) 19 | { 20 | // SwdBrowser.HandleJavaScriptErrors(); 21 | 22 | page.Invoke(); 23 | 24 | // SwdBrowser.HandleJavaScriptErrors(); 25 | 26 | page.VerifyExpectedElementsAreDisplayed(); 27 | 28 | // SwdBrowser.HandleJavaScriptErrors(); 29 | } 30 | } 31 | 32 | 33 | // Add testMethods for your new pages here: 34 | // *PageName*_VerifyExpectedElements() 35 | 36 | [TestMethod] 37 | public void EmptyPage_VerifyExpectedElements() 38 | { 39 | PageTest(MyPages.EmptyPage); 40 | } 41 | 42 | [TestMethod] 43 | public void CreateNewAccountPage_VerifyExpectedElements() 44 | { 45 | PageTest(MyPages.CreateNewAccountPage); 46 | } 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/SWD.Core/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Swd.Core")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Swd.Core")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("abd28e62-d6ac-49b9-a99c-45bddba9b7c6")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Demo.Tutorial/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Demo.Tutorial")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Demo.Tutorial")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("c3c7ae07-03e9-426d-86ac-af33dd8dc978")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Demo.TestModel/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Demo.TestModel")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Demo.TestModel")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("8ee37168-9cc9-45d4-896c-02eb994b220a")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Demo.TestProject/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Demo.TestProject")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Demo.TestProject")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("d2734f76-3d33-495d-b995-d24876c4c3cd")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Demo.TestProject/TestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | using Swd.Core.WebDriver; 9 | 10 | using System.Drawing.Imaging; 11 | using System.IO; 12 | 13 | namespace Demo.TestProject 14 | { 15 | public abstract class TestBase 16 | { 17 | protected TestContext testContext; 18 | public TestContext TestContext 19 | { 20 | get { return this.testContext; } 21 | set { this.testContext = value; } 22 | } 23 | 24 | 25 | [TestCleanup] 26 | public virtual void TestCleanUp() 27 | { 28 | var testResult = TestContext.CurrentTestOutcome; 29 | 30 | bool isTestFailed = !(testResult == UnitTestOutcome.Passed || testResult == UnitTestOutcome.Inconclusive); 31 | 32 | 33 | if (isTestFailed) 34 | { 35 | try 36 | { 37 | var myUniqueFileName = string.Format(@"screenshot_{0}.png", Guid.NewGuid()); 38 | var fullPath = Path.Combine(Path.GetTempPath(), myUniqueFileName); 39 | 40 | var screenshot = SwdBrowser.TakeScreenshot(); 41 | screenshot.SaveAsFile(fullPath, ImageFormat.Png); 42 | 43 | // Attach image to the log file 44 | TestContext.AddResultFile(fullPath); 45 | } 46 | catch(Exception e) 47 | { 48 | Console.WriteLine("Unable to take screenshot:" + e.Message); 49 | } 50 | } 51 | 52 | bool shouldRestartBrowser = isTestFailed; 53 | if (shouldRestartBrowser) 54 | { 55 | SwdBrowser.CloseDriver(); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Demo.TestModel/MyPages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Demo.TestModel.PageDeclarations; 6 | 7 | namespace Demo.TestModel 8 | { 9 | /** 10 | The class `MyPages` is an entry point for your PageObjects. \n 11 | This is a Factory class, which is accessible from any place of the code. \n 12 | Allows to call PegeObject actions from tests as well as from another PaeObjects \n 13 | */ 14 | public static class MyPages 15 | { 16 | 17 | /** 18 | This method can be extended to support additional logic for all pages, 19 | for instance, switch to the frame or window when it would be required or cache 20 | the created pageobject instance 21 | */ 22 | public static T GetPage() where T : MyPageBase, new() 23 | { 24 | T page = new T(); 25 | return page; 26 | } 27 | 28 | /** 29 | Adding new pages 30 | ---------------- 31 | For instance, you have defined a new PageObject with name `UserProfilePage` \n 32 | In order to have a convenient access to this page from any place of the test automation code, like this one: 33 | 34 | > MyPages.UserProfilePage.DoSomeAction(“param1”); 35 | 36 | Put the line in the following format to the list below: 37 | 38 | > public static UserProfilePage UserProfilePage { get { return GetPage(); } } 39 | 40 | Please, don't be confused because the property name and returning type are same. It is allowed in C#, but, anyway, you can give any name for the property. 41 | */ 42 | 43 | // Example: 44 | public static DummyExamplePage DummyExamplePage { get { return GetPage(); } } 45 | public static EmptyPage EmptyPage { get { return GetPage(); } } 46 | 47 | // Put your new pages here: 48 | //======================================================================================= 49 | 50 | public static CreateNewAccountPage CreateNewAccountPage { get { return GetPage(); } } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Demo.TestModel/PageDeclarations/CreateNewAccountPage.cs: -------------------------------------------------------------------------------- 1 | 2 | #region Usings - System 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | #endregion 8 | #region Usings - SWD 9 | using Swd.Core; 10 | using Swd.Core.Pages; 11 | using Swd.Core.WebDriver; 12 | #endregion 13 | #region Usings - WebDriver 14 | using OpenQA.Selenium.Support.PageObjects; 15 | using OpenQA.Selenium; 16 | #endregion 17 | namespace Demo.TestModel.PageDeclarations 18 | { 19 | public class CreateNewAccountPage : MyPageBase 20 | { 21 | #region WebElements 22 | 23 | [FindsBy(How = How.XPath, Using = @"id(""wpName2"")")] 24 | protected IWebElement txtUserName { get; set; } 25 | 26 | 27 | [FindsBy(How = How.XPath, Using = @"id(""wpPassword2"")")] 28 | protected IWebElement txtPassword { get; set; } 29 | 30 | 31 | [FindsBy(How = How.XPath, Using = @"id(""wpRetype"")")] 32 | protected IWebElement txtConfirmPassword { get; set; } 33 | 34 | 35 | [FindsBy(How = How.XPath, Using = @"id(""wpEmail"")")] 36 | protected IWebElement txtEmailAddress { get; set; } 37 | 38 | 39 | [FindsBy(How = How.XPath, Using = @"id(""wpCaptchaWord"")")] 40 | protected IWebElement txtCaptcha { get; set; } 41 | 42 | 43 | [FindsBy(How = How.XPath, Using = @"id(""wpCreateaccount"")")] 44 | protected IWebElement btnCreateYourAccount { get; set; } 45 | 46 | #endregion 47 | 48 | #region Invoke and Exists 49 | public override void Invoke() 50 | { 51 | Driver.Url = @"https://en.wikipedia.org/w/index.php?title=Special:UserLogin&returnto=Main+Page&type=signup"; 52 | } 53 | 54 | public override bool IsDisplayed() 55 | { 56 | return txtCaptcha.Displayed; 57 | } 58 | #endregion 59 | 60 | public override void VerifyExpectedElementsAreDisplayed() 61 | { 62 | VerifyElementVisible("txtUserName", txtUserName); 63 | VerifyElementVisible("txtPassword", txtPassword); 64 | VerifyElementVisible("txtConfirmPassword", txtConfirmPassword); 65 | VerifyElementVisible("txtEmailAddress", txtEmailAddress); 66 | VerifyElementVisible("txtCaptcha", txtCaptcha); 67 | VerifyElementVisible("btnCreateYourAccount", btnCreateYourAccount); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/SWD.Core/WebDriver/Wait.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using OpenQA.Selenium.Support; 6 | using OpenQA.Selenium.Support.UI; 7 | using OpenQA.Selenium; 8 | using System.Diagnostics; 9 | 10 | namespace Swd.Core.WebDriver 11 | { 12 | public static class Wait 13 | { 14 | 15 | public static IWebElement UntilVisible(IWebElement element, TimeSpan timeOut) 16 | { 17 | Stopwatch sw = new Stopwatch(); 18 | sw.Start(); 19 | 20 | while (true) 21 | { 22 | Exception lastException = null; 23 | try 24 | { 25 | if (element.Displayed) 26 | { 27 | return element; 28 | } 29 | System.Threading.Thread.Sleep(100); 30 | } 31 | catch (Exception e) { lastException = e; } 32 | 33 | if (sw.Elapsed > timeOut) 34 | { 35 | string exceptionMessage = lastException == null ? "" : lastException.Message; 36 | string errorMessage = string.Format("Wait.UntilVisible: Element was not displayed after {0} Milliseconds" + 37 | "\r\n Error Message:\r\n{1}", timeOut.TotalMilliseconds, exceptionMessage); 38 | throw new TimeoutException(errorMessage); 39 | } 40 | } 41 | } 42 | 43 | public static IWebElement UntilVisible(IWebElement element, int timeOutMilliseconds) 44 | { 45 | return UntilVisible(element, TimeSpan.FromMilliseconds(timeOutMilliseconds)); 46 | } 47 | 48 | 49 | public static IWebElement UntilVisible(By by, IWebDriver driver, TimeSpan timeOut) 50 | { 51 | WebDriverWait wdWait = new WebDriverWait(driver, timeOut); 52 | wdWait.IgnoreExceptionTypes 53 | ( 54 | typeof(ElementNotVisibleException), 55 | typeof(NoSuchElementException), 56 | typeof(StaleElementReferenceException) 57 | ); 58 | 59 | return wdWait.Until(ExpectedConditions.ElementIsVisible(by)); 60 | } 61 | 62 | public static IWebElement UntilVisible(By by, IWebDriver driver, int timeOutMilliseconds) 63 | { 64 | return UntilVisible(by, driver, TimeSpan.FromMilliseconds(timeOutMilliseconds)); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/SWD.StarterKit.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Express 2013 for Windows Desktop 4 | VisualStudioVersion = 12.0.21005.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Swd.Core", "SWD.Core\Swd.Core.csproj", "{C05CBE09-D707-4E42-886B-2778511D2BBF}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Configuration", "Configuration", "{731CC8D7-D57E-4C48-A28B-898A45220FA2}" 9 | ProjectSection(SolutionItems) = preProject 10 | App.config = App.config 11 | Config.config = Config.config 12 | EndProjectSection 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo.TestModel", "Demo.TestModel\Demo.TestModel.csproj", "{4FBA00DA-A9FB-4076-A8C7-8BD0F466289D}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DemoProject (MsTest)", "DemoProject (MsTest)", "{E65F55D5-0279-4F6E-83EB-23921296D266}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Framework", "Framework", "{F1CE735C-DED6-4E9C-8AE7-E16C7C6D7A66}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo.Tutorial", "Demo.Tutorial\Demo.Tutorial.csproj", "{01A502D4-26AC-4A41-8EAC-5C83BC38A299}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo.TestProject", "Demo.TestProject\Demo.TestProject.csproj", "{BC12253A-D1DF-482C-A6C1-D42DD5EAF0E4}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {C05CBE09-D707-4E42-886B-2778511D2BBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {C05CBE09-D707-4E42-886B-2778511D2BBF}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {C05CBE09-D707-4E42-886B-2778511D2BBF}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {C05CBE09-D707-4E42-886B-2778511D2BBF}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {4FBA00DA-A9FB-4076-A8C7-8BD0F466289D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {4FBA00DA-A9FB-4076-A8C7-8BD0F466289D}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {4FBA00DA-A9FB-4076-A8C7-8BD0F466289D}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {4FBA00DA-A9FB-4076-A8C7-8BD0F466289D}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {01A502D4-26AC-4A41-8EAC-5C83BC38A299}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {01A502D4-26AC-4A41-8EAC-5C83BC38A299}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {01A502D4-26AC-4A41-8EAC-5C83BC38A299}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {01A502D4-26AC-4A41-8EAC-5C83BC38A299}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {BC12253A-D1DF-482C-A6C1-D42DD5EAF0E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {BC12253A-D1DF-482C-A6C1-D42DD5EAF0E4}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {BC12253A-D1DF-482C-A6C1-D42DD5EAF0E4}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {BC12253A-D1DF-482C-A6C1-D42DD5EAF0E4}.Release|Any CPU.Build.0 = Release|Any CPU 46 | EndGlobalSection 47 | GlobalSection(SolutionProperties) = preSolution 48 | HideSolutionNode = FALSE 49 | EndGlobalSection 50 | GlobalSection(NestedProjects) = preSolution 51 | {C05CBE09-D707-4E42-886B-2778511D2BBF} = {F1CE735C-DED6-4E9C-8AE7-E16C7C6D7A66} 52 | {4FBA00DA-A9FB-4076-A8C7-8BD0F466289D} = {E65F55D5-0279-4F6E-83EB-23921296D266} 53 | {01A502D4-26AC-4A41-8EAC-5C83BC38A299} = {E65F55D5-0279-4F6E-83EB-23921296D266} 54 | {BC12253A-D1DF-482C-A6C1-D42DD5EAF0E4} = {E65F55D5-0279-4F6E-83EB-23921296D266} 55 | EndGlobalSection 56 | EndGlobal 57 | -------------------------------------------------------------------------------- /src/SWD.Core/WebDriver/WebElementExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using OpenQA.Selenium.Support; 6 | using OpenQA.Selenium; 7 | using OpenQA.Selenium.Support.UI; 8 | using System.Diagnostics; 9 | 10 | namespace Swd.Core.WebDriver 11 | { 12 | /// 13 | /// WebElementExtensions defines extension methods for IWebElement objects. 14 | /// Extend and simplify the functionality provided by WebDriver Core/Support libraries 15 | /// 16 | public static class WebElementExtensions 17 | { 18 | /// 19 | /// Default timeout for methods 20 | /// 21 | public static int DefaultTimeOutMilliseconds = 1000; 22 | 23 | /// 24 | /// Waits until element is visible. Internally, uses element.Displayed with ignored WebDriver exceptions 25 | /// 26 | /// 27 | public static IWebElement WaitUntilVisible(this IWebElement element, TimeSpan timeOut) 28 | { 29 | return Wait.UntilVisible(element, timeOut); 30 | } 31 | 32 | /// 33 | /// Waits until element is visible. Internally, uses element.Displayed with ignored WebDriver exceptions 34 | /// 35 | /// 36 | public static IWebElement WaitUntilVisible(this IWebElement element, int timeOutMilliseconds) 37 | { 38 | return Wait.UntilVisible(element, TimeSpan.FromMilliseconds(timeOutMilliseconds)); 39 | } 40 | 41 | /// 42 | /// Waits until element is visible. Internally, uses element.Displayed with ignored WebDriver exceptions 43 | /// 44 | /// 45 | 46 | public static IWebElement WaitUntilVisible(this IWebElement element) 47 | { 48 | return Wait.UntilVisible(element, TimeSpan.FromMilliseconds(DefaultTimeOutMilliseconds)); 49 | } 50 | 51 | /// 52 | /// Replaces WebDriver’s element.Text property. Gets value from 53 | /// *input* and *select* tags rather than returning text inside those elements. 54 | /// 55 | public static string GetElementText(this IWebElement element) 56 | { 57 | string result = ""; 58 | string tag = element.TagName.ToLower(); 59 | 60 | switch (tag) 61 | { 62 | case "input": 63 | result = element.GetAttribute("value"); 64 | break; 65 | case "select": 66 | result = new SelectElement(element).SelectedOption.Text; 67 | break; 68 | default: 69 | result = element.Text; 70 | break; 71 | } 72 | return result; 73 | } 74 | 75 | /// 76 | /// Gets a value indicating whether or not this element is displayed. 77 | /// This method suppresses any WebDriver exceptions 78 | /// 79 | /// 80 | /// 81 | public static bool IsDisplayedSafe(this IWebElement element) 82 | { 83 | bool result = false; 84 | try 85 | { 86 | result = element.Displayed; 87 | } 88 | catch (Exception e) 89 | { 90 | 91 | // Empty; Ignored 92 | } 93 | return result; 94 | 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Demo.TestModel/Demo.TestModel.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {4FBA00DA-A9FB-4076-A8C7-8BD0F466289D} 9 | Library 10 | Properties 11 | Demo.TestModel 12 | Demo.TestModel 13 | v4.0 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | ..\..\bin\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | ..\..\bin\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | False 44 | ..\packages\Selenium.WebDriver.2.46.0\lib\net40\WebDriver.dll 45 | 46 | 47 | False 48 | ..\packages\Selenium.Support.2.46.0\lib\net40\WebDriver.Support.dll 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | {C05CBE09-D707-4E42-886B-2778511D2BBF} 62 | Swd.Core 63 | 64 | 65 | 66 | 67 | App.config 68 | 69 | 70 | Config.config 71 | 72 | 73 | 74 | 75 | 76 | 83 | -------------------------------------------------------------------------------- /src/SWD.Core/Configuration/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Configuration; 6 | 7 | namespace Swd.Core.Configuration 8 | { 9 | /// 10 | /// Config -- Core Configuration Class 11 | /// 12 | 13 | #region Documentation 14 | /// 15 | /// Framework Configuration Files 16 | /// ----------------------------- 17 | /// The configuration class in the Swd.Starter allows to change some values without project recompilation, such as: 18 | /// 19 | /// * Selecting WebBrowser 20 | /// * Setting the main application URLs and others; 21 | /// 22 | /// The configuration sub-module is based on standard .NET Framework XML configuration files with several tweaks. 23 | /// 24 | /// In the project Src folder, you can find the following files: 25 | /// * App.config – contains a technical configuration and includes the file Config.config. (See line ) 26 | /// * Config.config – contains user-defined settings. 27 | /// 28 | /// The class `Config` read the configuration values from the class `Config.config` \n 29 | /// 30 | /// 31 | /// Those two configuration files are shared between all the projects. 32 | /// 33 | /// How to properly add configuration files into the new project 34 | /// ----------------------------------------------------------------------------------------- 35 | /// For instance, you have created new testing projects `MyApp.TestModel` and MyApp.Tests. 36 | /// 37 | /// In order to add Global Configuration Files support, please follow the instructions below: 38 | /// 39 | /// 1. Open Right-click context menu for project `MyApp.TestModel` in the Solution Explorer 40 | /// 2. Pick item Add → Existing Item... 41 | /// 3. In the Open File fialog, find the file `App.config` in the folder `SWD.Starter\src` 42 | /// 4. Select the file, but do not press button “Add” yet 43 | /// 5. Clich on the right side of the Add button in order to Invoke Add popup menu from 44 | /// 6. Pick “Add as a Link” 45 | /// 7. Recompile the project 46 | /// 47 | /// Repeat the steps for `MyApp.Tests` 48 | /// 49 | 50 | #endregion 51 | 52 | public class Config 53 | { 54 | public static bool wdIsRemote 55 | { 56 | get 57 | { 58 | return ConfigurationManager.AppSettings["swdIsRemote"] == "true"; 59 | } 60 | } 61 | public static string wdRemoteUrl 62 | { 63 | get 64 | { 65 | return ConfigurationManager.AppSettings["swdRemoteUrl"]; 66 | } 67 | } 68 | 69 | public static string swdBrowserType 70 | { 71 | get 72 | { 73 | return ConfigurationManager.AppSettings["swdBrowserType"]; 74 | } 75 | } 76 | 77 | /// 78 | /// Add your own configuration parameters 79 | /// ------------------------------------- 80 | /// Quick instructions: \n 81 | /// 82 | /// 1. Open the file Config.config 83 | /// 2. Add the following xml line: 84 | /// > 85 | /// 3. Create a property with name “yourParameterName” which reads configuration file key yourParameterName 86 | /// 87 | /// 88 | /// Put your configuration settings below: 89 | 90 | public static string yourParameterName 91 | { 92 | get 93 | { 94 | return ConfigurationManager.AppSettings["yourParameterName"]; 95 | } 96 | } 97 | 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/SWD.Core/Swd.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {C05CBE09-D707-4E42-886B-2778511D2BBF} 9 | Library 10 | Properties 11 | Swd.Core 12 | Swd.Core 13 | v4.0 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | ..\..\bin\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | 27 | 28 | pdbonly 29 | true 30 | ..\..\bin\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | False 47 | ..\packages\Selenium.WebDriver.2.46.0\lib\net40\WebDriver.dll 48 | 49 | 50 | False 51 | ..\packages\Selenium.Support.2.46.0\lib\net40\WebDriver.Support.dll 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | App.config 70 | 71 | 72 | Config.config 73 | Always 74 | 75 | 76 | 77 | 78 | 79 | chromedriver.exe 80 | PreserveNewest 81 | 82 | 83 | IEDriverServer.exe 84 | PreserveNewest 85 | 86 | 87 | 88 | 95 | -------------------------------------------------------------------------------- /src/SWD.Core/WebDriver/WebDriverRunner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | 7 | using OpenQA.Selenium; 8 | using OpenQA.Selenium.Remote; 9 | using OpenQA.Selenium.Chrome; 10 | using OpenQA.Selenium.Firefox; 11 | using OpenQA.Selenium.IE; 12 | using OpenQA.Selenium.Safari; 13 | using OpenQA.Selenium.PhantomJS; 14 | 15 | namespace Swd.Core.WebDriver 16 | { 17 | public class WebDriverRunner 18 | { 19 | public const string browser_Firefox = "Firefox"; 20 | public const string browser_Chrome = "Chrome"; 21 | public const string browser_InternetExplorer = "InternetExplorer"; 22 | public const string browser_PhantomJS = "PhantomJS"; 23 | public const string browser_HtmlUnit = "HtmlUnit"; 24 | public const string browser_HtmlUnitWithJavaScript = "HtmlUnitWithJavaScript"; 25 | public const string browser_Opera = "Opera"; 26 | public const string browser_Safari = "Safari"; 27 | public const string browser_IPhone = "IPhone"; 28 | public const string browser_IPad = "IPad"; 29 | public const string browser_Android = "Android"; 30 | 31 | 32 | public static IWebDriver Run(string browserName, bool isRemote, string remoteUrl) 33 | { 34 | IWebDriver driver = null; 35 | if (isRemote) 36 | { 37 | driver = ConnetctToRemoteWebDriver(browserName, remoteUrl); 38 | } 39 | else 40 | { 41 | driver = StartEmbededWebDriver(browserName); 42 | } 43 | return driver; 44 | } 45 | 46 | private static IWebDriver ConnetctToRemoteWebDriver(string browserName, string remoteUrl) 47 | { 48 | DesiredCapabilities caps = null; 49 | Uri hubUri = new Uri(remoteUrl); 50 | 51 | switch (browserName) 52 | { 53 | 54 | case browser_Firefox: 55 | caps = DesiredCapabilities.Firefox(); 56 | break; 57 | case browser_Chrome: 58 | caps = DesiredCapabilities.Chrome(); 59 | break; 60 | case browser_InternetExplorer: 61 | caps = DesiredCapabilities.InternetExplorer(); 62 | break; 63 | case browser_PhantomJS: 64 | caps = DesiredCapabilities.PhantomJS(); 65 | break; 66 | case browser_HtmlUnit: 67 | caps = DesiredCapabilities.HtmlUnit(); 68 | break; 69 | case browser_HtmlUnitWithJavaScript: 70 | caps = DesiredCapabilities.HtmlUnitWithJavaScript(); 71 | break; 72 | case browser_Opera: 73 | caps = DesiredCapabilities.Opera(); 74 | break; 75 | case browser_Safari: 76 | caps = DesiredCapabilities.Safari(); 77 | break; 78 | case browser_IPhone: 79 | caps = DesiredCapabilities.IPhone(); 80 | break; 81 | case browser_IPad: 82 | caps = DesiredCapabilities.IPad(); 83 | break; 84 | case browser_Android: 85 | caps = DesiredCapabilities.Android(); 86 | break; 87 | default: 88 | throw new ArgumentException(String.Format(@"<{0}> was not recognized as supported browser. This parameter is case sensitive", browserName), 89 | "WebDriverOptions.BrowserName"); 90 | } 91 | RemoteWebDriver newDriver = new RemoteWebDriver(hubUri, caps); 92 | return newDriver; 93 | } 94 | 95 | private static IWebDriver StartEmbededWebDriver(string browserName) 96 | { 97 | switch (browserName) 98 | { 99 | 100 | case browser_Firefox: 101 | return new FirefoxDriver(); 102 | case browser_Chrome: 103 | return new ChromeDriver(); 104 | case browser_InternetExplorer: 105 | return new InternetExplorerDriver(); 106 | case browser_PhantomJS: 107 | return new PhantomJSDriver(); 108 | case browser_Safari: 109 | return new SafariDriver(); 110 | default: 111 | throw new ArgumentException(String.Format(@"<{0}> was not recognized as supported browser. This parameter is case sensitive", browserName), 112 | "WebDriverOptions.BrowserName"); 113 | } 114 | } 115 | 116 | 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/SWD.Core/WebDriver/SwdBrowser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | using OpenQA.Selenium; 7 | using OpenQA.Selenium.Remote; 8 | using OpenQA.Selenium.Chrome; 9 | using OpenQA.Selenium.Firefox; 10 | using OpenQA.Selenium.IE; 11 | using OpenQA.Selenium.Safari; 12 | using OpenQA.Selenium.PhantomJS; 13 | 14 | using Swd.Core.Configuration; 15 | 16 | using OpenQA.Selenium.Support.Extensions; 17 | 18 | namespace Swd.Core.WebDriver 19 | { 20 | 21 | public static class SwdBrowser 22 | { 23 | 24 | private static IWebDriver _driver = null; 25 | 26 | /// 27 | /// Returns current WebDriver instance. 28 | /// 29 | /// * When the Driver was already created and the browser was opened – the 30 | /// property returns a reference to current browser. 31 | /// * If the Driver was not initialized yet – it will create a new browser 32 | /// (WebDriver) instance automatically, according to the configuration file. 33 | /// 34 | public static IWebDriver Driver 35 | { 36 | get 37 | { 38 | if (_driver == null) 39 | { 40 | _driver = WebDriverRunner.Run(Config.swdBrowserType, 41 | Config.wdIsRemote, 42 | Config.wdRemoteUrl); 43 | } 44 | return _driver; 45 | } 46 | } 47 | 48 | 49 | /// 50 | /// Closes the current WebDriver instance (and a web-browser window) 51 | /// 52 | public static void CloseDriver() 53 | { 54 | if (_driver != null) 55 | { 56 | _driver.Dispose(); 57 | _driver = null; 58 | } 59 | } 60 | 61 | /// 62 | /// Executes JavaScript in the context of the currently selected frame or window. 63 | /// 64 | /// JavaScript code block 65 | /// The value returned by the script 66 | public static object ExecuteScript(string jsCode) 67 | { 68 | return (Driver as IJavaScriptExecutor).ExecuteScript(jsCode); 69 | } 70 | 71 | 72 | /// 73 | /// *Executes JavaScript code block inside opened Web-Browser* 74 | /// 75 | /// Collects JavaScript errors on the page and throws JavaScriptErrorOnThePageException 76 | /// in case unhandled JavaScript errors had occurred on the WebPage 77 | /// During the first call on the specific web page, this method injects a script 78 | /// for error collection into the web page. The next calls will check if there 79 | /// are errors collected. 80 | /// If any JavaScript errors are captured – the method will throw JavaScriptErrorOnThePageException 81 | /// 82 | public static void HandleJavaScriptErrors() 83 | { 84 | string jsCode = 85 | #region JavaScript Error Handler code 86 | @" 87 | if (typeof window.jsErrors === 'undefined') 88 | { 89 | window.jsErrors = ''; 90 | window.onerror = function (errorMessage, url, lineNumber) 91 | { 92 | var message = 'Error: [' + errorMessage + '], url: [' + url + '], line: [' + lineNumber + ']'; 93 | message = message + ""\n""; 94 | window.jsErrors += message; 95 | return false; 96 | }; 97 | } 98 | 99 | var errors = window.jsErrors; 100 | window.jsErrors = ''; 101 | return errors;"; 102 | #endregion 103 | 104 | string errors = ""; 105 | errors = (string)ExecuteScript(jsCode); 106 | 107 | if (!string.IsNullOrEmpty(errors)) 108 | { 109 | throw new JavaScriptErrorOnThePageException(errors); 110 | } 111 | } 112 | 113 | public static Screenshot TakeScreenshot() 114 | { 115 | return Driver.TakeScreenshot(); 116 | } 117 | 118 | /// 119 | /// Driver Finalizer: automatically closes WebDriver when the test suite run in completed 120 | /// 121 | static readonly Finalizer finalizer = new Finalizer(); 122 | sealed class Finalizer 123 | { 124 | ~Finalizer() 125 | { 126 | CloseDriver(); 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SWD.Starter 2 | =========== 3 | 4 | #### ← [SWD Starter Java](https://github.com/dzharii/Swd.StarterJ) -= SWD Starter C# =- [SWD Page Recorder](https://github.com/dzharii/swd-recorder) → 5 | 6 | ## :apple: Installation Prerequisites 7 | In order to download and start using the project you have to install: 8 | 9 | * **[Git]( http://git-scm.com/)** 10 | * [Visual Studio Community 2013](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) (RECOMENDED) 11 | * _ _ _ OR [Visual Studio 2013 Express Desktop]( http://www.microsoft.com/en-US/download/details.aspx?id=40787) (if you don’t have a professional one) 12 | 13 | I personally recommend to use [Chocolatey]( https://chocolatey.org/) in order to perform such boring operations – automatically. 14 | 15 | ## :small_red_triangle_down: Download SWD Page Recorder 16 | 17 | [![Logo](https://github.com/dzharii/SWD.Starter/raw/master/images/page-recorder.png)](https://github.com/dzharii/swd-recorder/releases) 18 | 19 | 20 | ## Inaccurate instructions 21 | 22 | 1. Run Page Recorder 23 | 2. Open SWD.Starter / src / SWD.StarterKit.sln 24 | 2.1 Copy 25 | * chromedriver.exe 26 | * IEDriverServer.exe 27 | into folder webdrivers 28 | 29 | 2.2 Build the project 30 | 31 | 3. Record your PageObject with Page Recorder 32 | 33 | 4. Generate code with template "[CSharp] SWD Starter PageObjects" 34 | 5. Follow the instructions inside generated code 35 | 5.1 Implement Invoke() and IsDisplayed(); 36 | 37 | 6. Run the tests (set browser in the file "Config.config") 38 | swdBrowserType 39 | default is Firefox 40 | 41 | 42 | ## Doxygen documentation generator 43 | 44 | Just don’t forget to run `SWD.Starter\doxygen_run.cmd` and enjoy your framework API documentation! 45 | 46 | See generated `SWD.Starter\docs\api\html\index.html` 47 | 48 | ![Logo](https://github.com/dzharii/SWD.Starter/raw/master/images/doxy.png) 49 | 50 | ## Core concepts: 51 | 52 | 1. Share the opened browser instance across different test cases – because that is how the most users do, e.g. they do not close the browser after each action. 53 | 1. If you really want parallel test execution, just run parallel processes… Multithreaded tests implementation inside a single process would add complexity to the code and kill your workstation performance. 54 | 1. Start using PageObjects – or your code will turn to real crap soon. 55 | 1. Are your 3000 UI tests took too much time? – You are doing something wrong when you want to test everything through UI. Consider 20 high level (UI) to 80 low level (HTTP Requests / Database / Unit) tests ratio. 56 | 1. Decouple your code: avoid copy-paste approach as long as it is reasonable: 57 | * When the duplicated code in unique only for the given suite – create a separate method inside the suite 58 | * When the duplicated code can be used globally across the project – move it to a special BusinessSteps class or to the specific page object class; 59 | * when you code just extends WebDriver functionality and has no connection to your test project logic – move it to the framework core assembly. 60 | 61 | 1. When you work with PageObject, it is OK to create a lot of small methods with business logic oriented names. E.g., if you have the code, which opens a new project form: 62 | ``` 63 | btnNewProject.Click(); 64 | WaitForAjax(); 65 | ``` 66 | Then just move it to a separate PageObject method: 67 | ``` 68 | var newProjectForm = projectsPage.OpenNewProjectForm(); 69 | ``` 70 | 1. Be proud of your code 71 | 72 | 73 | ## :books: Useful Materials 74 | 75 | ### :blue_book: Test Automation Framework methodology 76 | 77 | 78 | :warning: read this only if you understand Russian... Otherwise... you will not understand anything. LOL: **[Заметки по архитектуре и методологии фреймворка](https://github.com/dzharii/SWD.Starter/blob/master/methodology_all_in_one_rus.md)** 79 | 80 | 81 | ### :anchor: External Articles in English: 82 | 83 | * [SWD Page Recorder – records WebElements and generates PageObject classes (Announcement)](https://groups.google.com/d/msg/selenium-users/epneoHaOymk/MjjhyoBcUf4J) 84 | * [PageObject Generator Utility for Selenium WebDriver](http://unmesh.me/2013/08/29/pageobject-generator-utility-for-selenium-webdriver/) 85 | 86 | ### :anchor: External Articles in Russian: 87 | 88 | * [Материалы моего доклада (SWD Page Recorder) на #SeleniumCamp 2014 и ещё несколько фактов]( http://blog.zhariy.com/2014/02/swd-page-recorder-seleniumcamp-2014.html) 89 | * [SWD Page Recorder: Записывает PageObject-классы для Selenium WebDriver]( http://habrahabr.ru/post/191802/) 90 | * [SWD.Starter: Быстрый старт автоматизации тестирования UI на C# + Selenium WebDriver + PageObjects]( http://habrahabr.ru/post/208822/) 91 | 92 | 93 | ### :video_camera: Videos 94 | 95 | * [(English)SWD Page Recorder: Working with Frames and JavaScript popups]( https://www.youtube.com/watch?v=C4jnX0PF_mc) 96 | * [(Rus) SWD Page Recorder – record your Page Objects fast like a ninja! (Dmytro Zharii)](https://www.youtube.com/watch?v=7B1RGt-MTuU) 97 | * [(Rus) SWD Page Recorder BETA1 -- записывает PageObject'ы на C#, Java, Ruby, Perl, Python!]( https://www.youtube.com/watch?v=4Md_kC4Fdpg) 98 | * [За пределами PageObject](http://blog.zhariy.com/2013/02/atdays-pageobject.html) 99 | 100 | ### :octocat: Original Github open-source Projects 101 | 102 | * [dzharii / swd-recorder](https://github.com/dzharii/swd-recorder) 103 | * [dzharii / SWD.Starter](https://github.com/dzharii/SWD.Starter) 104 | 105 | ### :mag_right: About Dmytro ;) 106 | 107 | * Blog (Rus): http://blog.zhariy.com/ 108 | * LinkedIn: https://www.linkedin.com/in/dmytrozharii 109 | * Personal Email: dmytro.zharii !@[@]@! gmail.com 110 | * :bird: Twitter: [@dzhariy](https://twitter.com/dzhariy) 111 | 112 | -------------------------------------------------------------------------------- /src/Demo.TestProject/Demo.TestProject.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {BC12253A-D1DF-482C-A6C1-D42DD5EAF0E4} 7 | Library 8 | Properties 9 | Demo.TestProject 10 | Demo.TestProject 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | False 42 | ..\packages\Selenium.WebDriver.2.46.0\lib\net40\WebDriver.dll 43 | 44 | 45 | False 46 | ..\packages\Selenium.Support.2.46.0\lib\net40\WebDriver.Support.dll 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | App.config 69 | 70 | 71 | Config.config 72 | 73 | 74 | 75 | 76 | 77 | {4fba00da-a9fb-4076-a8c7-8bd0f466289d} 78 | Demo.TestModel 79 | 80 | 81 | {c05cbe09-d707-4e42-886b-2778511d2bbf} 82 | Swd.Core 83 | 84 | 85 | 86 | 87 | 88 | 89 | False 90 | 91 | 92 | False 93 | 94 | 95 | False 96 | 97 | 98 | False 99 | 100 | 101 | 102 | 103 | 104 | 105 | 112 | -------------------------------------------------------------------------------- /src/Demo.Tutorial/Demo.Tutorial.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {01A502D4-26AC-4A41-8EAC-5C83BC38A299} 7 | Library 8 | Properties 9 | Demo.Tutorial 10 | Demo.Tutorial 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | ..\..\bin\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | ..\..\bin\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | False 42 | ..\packages\Selenium.WebDriver.2.46.0\lib\net40\WebDriver.dll 43 | 44 | 45 | False 46 | ..\packages\Selenium.Support.2.46.0\lib\net40\WebDriver.Support.dll 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | App.config 69 | 70 | 71 | Config.config 72 | 73 | 74 | 75 | 76 | 77 | {c05cbe09-d707-4e42-886b-2778511d2bbf} 78 | Swd.Core 79 | 80 | 81 | 82 | 83 | PreserveNewest 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | False 93 | 94 | 95 | False 96 | 97 | 98 | False 99 | 100 | 101 | False 102 | 103 | 104 | 105 | 106 | 107 | 108 | 115 | -------------------------------------------------------------------------------- /src/Demo.Tutorial/Ch_00_Introduction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | using Swd.Core.WebDriver; 5 | 6 | using OpenQA.Selenium; 7 | using OpenQA.Selenium.Support.UI; 8 | 9 | //using op 10 | 11 | namespace Demo.Tutorial 12 | { 13 | /// 14 | /// Dear Reader! 15 | /// ============== 16 | /// 17 | /// What a great moment! You’ve decided to start crafting Selenium WebDriver tests! 18 | /// So, let’s start our journey. 19 | /// 20 | /// 21 | [TestClass] 22 | public class Ch_00_Introduction 23 | { 24 | /// 25 | /// ### First step: Run the WebDriver 26 | /// 27 | /// Let’s start from a very simple, but *very* important action. Let’s open a page in the Web Browser. 28 | /// Please, make sure you have installed Firefox on your computer. Otherwise – the code will fail. 29 | /// So, please find the test with name S01_First_Step_Run_WebDriver_with_Firefox below 30 | /// and RIGHT-click on the line: `SwdBrowser.Driver.Url ...`. 31 | /// 32 | /// In the context menu, select `Run Tests`. 33 | /// 34 | /// ... 35 | /// 36 | /// Have you seen the browser appeared? O_O 37 | /// 38 | [TestMethod] 39 | public void S01_First_Step_Run_WebDriver_with_Firefox() 40 | { 41 | 42 | SwdBrowser.Driver.Url = "http://swd-tools.com"; 43 | 44 | } 45 | 46 | /// 47 | /// I hope the test code went smoothly… Otherwise, please check the error provided in the test log. 48 | /// WebDriver often prints and informative messages there. 49 | /// 50 | /// Let me explain what had happened. 51 | /// The class `SwdBrowser` is defined in the namespace Swd.Core.WebDriver. 52 | /// This class controls the WebDriver lifetime. 53 | /// 54 | /// When you call `SwdBrowser.Driver`, it creates a new WebDriver instance. 55 | /// When you call `SwdBrowser.Driver` for the second time – it returns the WebDriver instance 56 | /// which was already created. That technique is called “lazy initialization”. 57 | /// 58 | /// SwdBrowser is an global object. So you can request the “current” WebDriver instance 59 | /// from any line of your code. 60 | /// 61 | /// 62 | /// 63 | /// ### Framework configuration file 64 | /// 65 | /// Now, the time had come to discuss the configuration files. In the Solution Explorer, please find and expand the folder “Configuration”. 66 | /// This folder contains the files “App.config” and “Config.config”. 67 | /// I know, “Config.config” is kind of a silly name… anyway, this is our main configuration file. 68 | /// Please, open the file. 69 | /// The section we need to change is `Browser Name`, which defines the default browser. 70 | /// 71 | /// This section should look like the following: 72 | /// 73 | /// 74 | /// 75 | /// 76 | /// 77 | /// 78 | /// Now, comment the line with “Firefox” and uncomment "Chrome". 79 | /// Note: In the xml files (and Config.config is an xml file), to comment the line you need to put it inside: 80 | /// 81 | /// 82 | /// 83 | /// In Visual Studio, there are a convenient hotkeys to comment/uncomment lines. 84 | /// To comment the line (in the C# code or in the XML/HTML file), put caret on the line you want to comment; hold `Ctrl` and press K, C. (`Ctr+K,C`) 85 | /// To uncomment line – press `Ctrl+K,U`. 86 | /// 87 | /// After this task is done, the section should look like the following: 88 | /// 89 | /// 90 | /// 91 | /// 92 | /// 93 | /// 94 | /// And one more important step! 95 | /// SWD.Starter does not include the driver executable. You need to download "chromedriver.exe" and "IEDriverServer.exe" separately. 96 | /// Please, visit Selenium Download page: http://www.seleniumhq.org/download/ 97 | /// And download the driver executable for Internet Explorer and Chrome. 98 | /// 99 | /// Extract and put those *.exe files into the folder SWD.Starter\webdrivers\ 100 | /// Swd.Starter 101 | /// +---bin 102 | /// +---docs 103 | /// +---src 104 | /// +---tools 105 | /// \---webdrivers 106 | /// chromedriver.exe 107 | /// IEDriverServer.exe 108 | /// readme.txt 109 | /// 110 | /// 111 | /// 112 | /// After you’ll be done downloading the files, please, run the test below. 113 | /// The test should open a local HTML file in the Chrome browser. 114 | /// 115 | /// Reminder: Inside the TestMethod code, right-click to invoke Context Menu, 116 | /// and choose "Run Tests" 117 | /// 118 | /// 119 | /// 120 | /// 121 | [TestMethod] 122 | public void S10_Change_the_configuration_and_open_local_page_in_Chrome() 123 | { 124 | // Btw, the method Ch00_OpenHtmlFile, uses SwdBrowser.Driver inside 125 | DemoPages.Ch00_OpenHtmlFile("simple.html"); 126 | 127 | Console.Write(SwdBrowser.Driver.Title); 128 | Console.Write("==========================="); 129 | Console.Write(SwdBrowser.Driver.PageSource); 130 | 131 | System.Threading.Thread.Sleep(5 * 1000); // Sleep for 5 seconsds 132 | } 133 | 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Demo.Tutorial/DemoPages/Ch_02_WorkingWithForms/bootstrap3demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | 14 | Bootstrap 3 From 15 | 16 | 17 |
18 | 19 |
20 | 21 | Please enter your first name 22 |
23 |
24 | 25 | 26 |
27 | 28 |
29 | 30 | Please enter your last name 31 |
32 |
33 | 34 | 35 |
36 | 37 |
38 | 39 | Please enter your email address 40 |
41 |
42 | 43 | 44 |
45 | 46 |
47 | 48 | Please enter your email one more time 49 |
50 |
51 | 52 | 53 |
54 | 55 |
56 |
57 | 61 |
62 |
63 | 67 |
68 |
69 |
70 | 71 | 72 |
73 | 74 |
75 | 79 |
80 |
81 | 82 | 83 |
84 | 85 |
86 | 87 | Please enter your password 88 |
89 |
90 | 91 | 92 |
93 | 94 |
95 | 96 | Please confirm your password 97 |
98 |
99 | 100 | 101 |
102 | 103 |
104 | 105 |
106 |
107 | 108 | 109 |
110 | 111 |
112 | 113 | Please enter your country 114 |
115 |
116 | 117 | 118 |
119 | 120 |
121 | 122 | You city 123 |
124 |
125 | 126 | 127 |
128 | 129 |
130 |
131 | 135 |
136 |
137 | 141 |
142 |
143 |
144 | 145 | 146 |
147 | 148 |
149 | 153 | 157 | 161 | 165 | 169 |
170 |
171 | 172 | 173 |
174 | 175 |
176 |
177 | 181 |
182 |
183 | 187 |
188 |
189 |
190 | 191 | 192 |
193 | 194 |
195 | 199 | 203 | 207 | 211 |
212 |
213 | 214 | 215 |
216 | 217 |
218 | 219 |
220 |
221 | 222 | 223 |
224 | 225 |
226 |
227 | 231 |
232 |
233 | 237 |
238 |
239 |
240 | 241 | 242 |
243 | 244 |
245 | 246 |
247 |
248 | 249 | 250 |
251 | 252 |
253 | 254 | 255 |
256 |
257 | 258 |
259 |
260 | 261 | -------------------------------------------------------------------------------- /methodology_all_in_one_rus.md: -------------------------------------------------------------------------------- 1 | ## Авто-тесты в Visual Studio 2013 Express (Desktop) 2 | [Оригинал: automated-testing.info](http://automated-testing.info/t/avto-testy-v-visual-studio-2013-express-desktop/3703) 3 | 4 | Visual Studio Express -- это линейка “бесплатной” Visual Studio с ограниченой функциональностью. Основные ограничения были в том, что: 5 | 6 | 1. Не подержалась установка плагинов для VS, ладно там Resharper нельзя было установить, но ведь и пакетный менеджер Nuget был тоже недоступен, и приходилось подключать новые сборки по-старинке, через поиск файла на диске. 7 | 2. Не поддерживался Ms-Test и тестовые проекты. Можно было использовать NUnit. Это работало, но не особо удобно. Особенно, когда нужно было что-то продебажить. 8 | 3. Были ограничены возможности рефакторинга. Сейчас не скажу точно, но вроде-бы нельзя было переименовать метод средствами IDE, и делать это приходилось поиском и заменой по тексту. 9 | 10 | До версии VS 2013, я устанавливал VS Express… нервно смеялся… и сразу же сносил. 11 | 12 | Возможно наличие альтернатив, таких как [Xamarin Studio](http://xamarin.com/studio) и [Sharp Develop](http://www.icsharpcode.net), а может быть еще что, все таки заставили Майкрософт расщедрится на добавление фич в VS 2013, которых так не хватало: 13 | 14 | 1. Плагины до сих пор установить нельзя, но появилась поддержка Nuget. Теперь сборки WebDriver и другие проекты можно устанавливать и обновлять посредством пакетного менеджера. 15 | 2. Добавили поддержку тестовых проектов. Можно запускать и отлаживать тесты внутри IDE. Поддерживается только родной “Ms-Test” 16 | 3. Мне вполне хватает возможности “вынести код в отдельный метод”, создать новый метод и переименовать переменную/метод 17 | 18 | В целом -- очень доволен. 19 | Почему написал этот пост? -- Нет, не ради денег :blush: 20 | 21 | Раньше разговаривал с людьми, которые шли путем мучений, используя VS Express 2010/2012 + NUnit. Так вот, мучениям пришел конец! (Только осталось переписать тесты под MsTest :smiley: ) 22 | 23 | [Microsoft Visual Studio Express 2013 для Windows Desktop] (http://www.microsoft.com/ru-ru/download/details.aspx?id=40787) 24 | 25 |
26 | 27 | ## Автоматическое создание Браузера и инициализация PageObject 28 | [Оригинал: automated-testing.info](http://automated-testing.info/t/zametka-avtomaticheskoe-sozdanie-brauzera-i-iniczializacziya-pageobject/3522) 29 | 30 | ### Проблема: 31 | 32 | Многих людей, хлебом не корми – дай только пописать лишний код, да и передать лишний вебдрайвер каждому ПейджОбжекту в самый конструктор… 33 | 34 | В примере ниже, я покажу, как избежать лишних явных созданий экземпляра вебдрайвера и лишних инициализаций ```PageFactory.InitElements``` 35 | 36 | Я понимаю, что многие начали работу с PageObject [по этому примеру с PageFactory]( https://code.google.com/p/selenium/wiki/PageFactory#A_Simple_Example), но ведь это совсем не значит, что этот пример самый оптимальный. Это – просто пример. 37 | 38 | Я не хочу каждый раз инициализировать вебдрайвер. Я не хочу, каждый раз инициализировать страницу при помощи PageFactory. Я просто, хочу писать код… 39 | 40 | 41 | ### Решение 42 | 43 | Для начала, разберемся с надоедливым созданием нового экземпляра WebDriver в каждом тесте. Пусть Вебдрайвер – сам себя создает, когда мне это нужно. 44 | 45 | Вызов Browser.Driver(), вернет либо уже созданный Вебдрайвер, либо создаст его при первом обращении. 46 | 47 | ``` csharp 48 | // Вызови Browser.Driver(), и драйвер – твой! 49 | public static class Browser 50 | { 51 | private static IWebDriver driver; 52 | 53 | // Автоматически создает новый WebDriver при 54 | // первом обращении, либо возвращает уже созданный 55 | public static IWebDriver Driver() 56 | { 57 | // Если driver равен null (??), то создать новый FirefoxDriver 58 | driver = driver ?? new FirefoxDriver(); 59 | return driver; 60 | } 61 | 62 | // Driver капут 63 | public static void CloseDriver() 64 | { 65 | if (driver != null) driver.Quit(); 66 | } 67 | 68 | 69 | // Эммм… у статических классов – нет деструктора. 70 | // Тут создается объект обычного класса Finalizer, 71 | // который будет безжалостно уничтожен .NET фреймворком 72 | // по завершению теста. А за собой он потянет закрытие 73 | // вебдрайвера. 74 | static readonly Finalizer finalizer = new Finalizer(); 75 | sealed class Finalizer 76 | { 77 | ~Finalizer() 78 | { 79 | CloseDriver(); 80 | } 81 | } 82 | } 83 | ``` 84 | 85 | Идем дальше. 86 | Я хочу, чтобы каждая страница *сама себя инициализировала* при помощи PageFactory.InitElements. Тем самым, при создании нового экземпляра страницы, будет возвращаться готовая страница, с которой уже можно работать, а не какой-то полуфабрикат, который еще и на фабрику отправлять нужно. 87 | Для этого, необходимо создать специальный базовый класс. 88 | 89 | В .NET есть такая особенность: конструкторы без параметров дочерних классов, будут автоматически вызывать, в первую очередь, конструктор базового класса. 90 | Вы спрашиваете, будет ли это работать и в Java? – Не знаю, попробуйте. 91 | 92 | ``` csharp 93 | // Это базовый класс для всех страниц 94 | public abstract class BasePage 95 | { 96 | // Полезное свойство. Позволяет писать меньше точек. 97 | public IWebDriver Driver { get { return Browser.Driver(); } } 98 | 99 | // Конструктор без параметров дочернего класса, 100 | // автоматически вызывает конструктор без параметров базового. 101 | // Тут очень важно, что это работает только для конструкторов 102 | // ** без параметров **. 103 | // На этом свойстве и сыграем. 104 | public BasePage() 105 | { 106 | // На самом деле, this – это будет объект дочернего класса. 107 | // PageFactory умеет с ним работать со столь запутанной 108 | // схемой. 109 | // Убийца – садовник. Извините. 110 | 111 | PageFactory.InitElements(Driver, this); 112 | } 113 | } 114 | ``` 115 | Пришел черед создать PageObject, который декларирует страницу. В нем есть два действия (метода): 116 | 117 | * Invoke() – прото открывает страницу 118 | * Calcualte() – выполняет действия по вычислению логарифма 119 | 120 | ``` csharp 121 | public class LogCalculatorPage : BasePage 122 | { 123 | // =================== Элементы страницы ======================= 124 | // 125 | [FindsBy(How = How.XPath, Using = @"//input[@name='b']")] 126 | protected IWebElement txtLogBase { get; set; } 127 | 128 | [FindsBy(How = How.XPath, Using = @"//input[@name='x']")] 129 | protected IWebElement txtLog { get; set; } 130 | 131 | // Товарищи, эти локаторы были записаны на скорую руку при помощи 132 | // SWD Page Recorder 133 | // И мне лень было их оптимизировать. Но, это не значит, что это 134 | // правильный путь. 135 | [FindsBy(How = How.XPath, Using = @"//form[@name='calcform1']/table[1]/tbody[1]/tr[5]/td[2]/input[1]")] 136 | protected IWebElement btnCalculate { get; set; } 137 | 138 | [FindsBy(How = How.XPath, Using = @"//input[@name='y']")] 139 | protected IWebElement txtResult { get; set; } 140 | // =================== ~~~~~~~~~~~~~~~~~ ======================= 141 | 142 | 143 | // Тыщ-тыщ, «вызывает» страницу 144 | public void Invoke() 145 | { 146 | // Я еще раз напомню, что Driver – унаследован из базового 147 | // класса, 148 | // А на самом деле, при каждом таком обращении, вызывается 149 | // Browser.Driver(). 150 | // Но, удобно же, правда? 151 | Driver.Navigate().GoToUrl(@"http://www.rapidtables.com/calc/math/Log_Calculator.htm"); 152 | } 153 | 154 | 155 | // Калькъ! 156 | public double Calcualte(string logBase, double logValue) 157 | { 158 | txtLogBase.SendKeys(logBase); 159 | 160 | txtLog.SendKeys(logValue.ToString()); 161 | 162 | btnCalculate.Click(); 163 | 164 | var rawResult = txtResult.GetAttribute("value"); 165 | 166 | return Convert.ToDouble(rawResult); 167 | } 168 | } 169 | ``` 170 | 171 | А вот так выглядит тест: 172 | 173 | ``` csharp 174 | static void Main(string[] args) 175 | { 176 | // Магия базового класса! При создании объекта, 177 | // он уже будет проинициализирован PageFactory 178 | var calcPage = new LogCalculatorPage(); 179 | 180 | calcPage.Invoke(); 181 | 182 | var result = calcPage.Calcualte("10", 100); 183 | 184 | Console.WriteLine("Вот такой суровый логарифм: " + result); 185 | } 186 | ``` 187 | 188 | Видео работы теста, чтобы вы не говорили, что я обманываю :) 189 | 190 | 191 | http://www.youtube.com/watch?v=rdJ1k9wahuo&feature=youtu.be 192 | 193 | [Весь исходный код – тут](https://gist.github.com/dzhariy/6984074) 194 | 195 |
196 | 197 | ## Все зависит от проекта… или как выбрать локатор для тестов на WebDriver? 198 | [Оригинал: automated-testing.info](http://automated-testing.info/t/vse-zavisit-ot-proekta-ili-kak-vybrat-lokator-dlya-testov-na-webdriver/3681) 199 | 200 | Зачастую меня немного подтролливают, когда я произношу фразу “все зависит от проекта”. Ну действительно же, зависит. И не только от проекта, но от фреймвоков, на котором реализуется проект. 201 | 202 | Когда мы выбираем локатор для идентификации элемента страницы, мы же ведь хотим, чтобы он был стабильным, ведь так? А стабильность... это дело относительное... по крайней мере, относительно между различными фреймворками. 203 | 204 | Начнем с клиент-сайда 205 | 206 | ### Голый HTML или Bootstrap + JQuery 207 | 208 | В этом случае, обычно очень хорошо работает идентификация по HTML id и CSS селекторам. 209 | Ведь программисты сами обращаются к элементам по id, и учитывая то, что переименование id элемента грозит поиском и заменой по всем файлам... программисты не будут менять его часто, и будут следить, чтобы этот ID был уникальным на странице, как завещает нам великий стандарт HTML. 210 | Тоже относится и к именам CSS классов. Они, конечно же, могут менятся со временем, но... это болезненно для разработчика, следовательно -- метятся будут не часто. 211 | Зачастую, для таких страниц, хорошо работает поиск FindElementById или CSS. 212 | 213 | Вы еще не уснули? 214 | 215 | ### Фреймворк Knockout 216 | B вот вам сразу [пример приложения](http://knockoutjs.com/examples/contactsEditor.html). 217 | Если исследовать типичный элемент накаута -- текстовое поле, то что же мы увидим? 218 | ![](https://github.com/dzharii/SWD.Starter/raw/master/images/knockout-demo01.png) 219 | 220 | ``` 221 | 222 | ``` 223 | 224 | Невероятно... согласно здравому смыслу, надо бы написать хотя бы так: 225 | 226 | ``` 227 | 228 | ``` 229 | 230 | Но где же это все? где name? где id? где в конце концов type="text". 231 | Вы не поверите. Этого всего нет. И это в официальном примере. Вы думаете, что обычный (ленивый) разработчик будет заводить атрибуты name или id специально для вас? 232 | Ведь разработчику -- они не нужны. 233 | 234 | Knockout сам разруливает все привязки. Для этого, ему достаточно строки: 235 | `data-bind="value: firstName"` 236 | 237 | Следовательно, и локаторы в тестах нужно привязывать к этой строке: 238 | 239 | `CSS: input[data-bind="value: firstName"] ` 240 | или 241 | `CSS: input[data-bind*="firstName"] (менее предсказуемо)` 242 | 243 | 244 | ### Фреймворк ExtJS 245 | Если с Knockout (а также AngularJS) -- все ясно -- можно использовать их кастомные атрибуты, то ExtJS -- это абсолютно другое дело. В ExtJS для каждого HTML элемента присваивается свой ID! 246 | ([А вот и примеры](http://dev.sencha.com/deploy/ext-4.0.0/examples/)) 247 | Ура? 248 | 249 | Только... при следующем обновлении страницы, эти ID (все) будут меняться. 250 | В таком случае, без JavaScript не обойтись. Вот что мне порекомендовали: 251 | [Ссылка на оригинал](http://habrahabr.ru/post/181660/#comment_6322250) 252 | 253 | >Если в кратце, то наберите в отладчике на указанной вами странице это: 254 | 255 | >`Ext.ComponentManager.each(function (id, item){console.log(id, item.getXTypes(), item.initialConfig)})` 256 | 257 | >В консоль выведется весь список компонентов. Из них уже можно вытянуть Id DOM узла, или ссылку на него (Ext.getCmp('id').body.dom — например). Далее этот Id или ссылку (да, да, именно ссылку на DOM) можно передать в Selenium. 258 | 259 | >Тут все более менее понятно. Вопрос как найти нужный компонент. 260 | 261 | >Как правило в компонент добавляют какие-то дополнительные данные для реализации прикладного назначения этого компонента. Например имя сущности, идентификатор формы, или адрес поставки данных для хранилища списка. Зная что и где искать можно без труда найти целевой компонент. 262 | 263 | 264 | Кто может продолжить эту тему боли автоматизатора? :D 265 | 266 | 267 | ## WebDriverWait и PageObject 268 | [Оригинал: automated-testing.info](http://automated-testing.info/t/zametka-c-webdriverwait-i-pageobject/3531) 269 | 270 | ### Проблема 271 | Казалось бы, реализация PageObjects и WebDriverWait находятся очень близко друг к другу, прямо в соседних пространствах имен, соответственно: 272 | 273 | * OpenQA.Selenium.Support.PageObjects 274 | * OpenQA.Selenium.Support.UI 275 | 276 | И я думаю, многие задавались вопросом: а почему WebDriverWait не умеет работать с элементами PageObject «из коробки?» 277 | Можно, конечно же, «подружить» их между собой… но, тут есть возникает несколько проблем: 278 | 279 | 1. Во-первых, вам нужно будет самостоятельно отлавливать некоторые исключения, например, StaleElementReferenceException 280 | 2. Для создания класса WebDriverWait, в конструктор необходимо отдельно передать экземпляр вебдрайвера или веб-элемента… Но, по сути, внутри элемента PageObject уже есть этот экземпляр… 281 | 282 | Конечно же, магией внедрения зависимостей и реализовывая все интерфейсы на своем пути, можно добиться «правильного и благородного» решения этой проблемы… И, при этом, переопределить 95% кода. 283 | 284 | Раз так, то почему бы не переписать все 100%, и сделать код проще? 285 | 286 | ### Решение 287 | В C# есть одна замечательная фича – это методы-расширения. 288 | Если вы еще не знакомы с этой темой, то рекомендую послушать .NET-девочку: 289 | http://www.youtube.com/watch?v=e5kH3BHoeiQ 290 | 291 | А в нашем примере, специальный метод-расширение `.WaitUntilVisible()`, будет «прилипать» ко всем элемента типа `IWebElement`, и выглядеть это будет так: 292 | 293 | ``` csharp 294 | var page = new YandexPage(); 295 | page.txtSearchBox.WaitUntilVisible().SendKeys("Google"); 296 | ``` 297 | В данном примере `WaitUntilVisible()` – будет вызван для `txtSearchBox`. 298 | 299 | Он подождет в течении секунды появления элемента, а дальше: 300 | Либо, выбросит исключение, если элемент не найден 301 | Либо, продолжит выполнение операции SendKeys. 302 | 303 | Сам алгоритм ожидания реализован в классе Wait (Wait.cs). 304 | `Wait.UntilVisible(…)` – принимает элемент страницы, второй – граничное время ожидания. 305 | 306 | Расширения реализованы в классе WebElementExtensions 307 | Которые, просто передают нужные параметры в `Wait.UntilVisible(element, timeOut)`; 308 | 309 | 310 | https://gist.github.com/dzhariy/7013245 311 | 312 | 313 | 314 | А сам тест и реализованный PageObject – ниже. 315 | В этом примере элемент page.txtSearchBox, будет найден сразу же. 316 | 317 | `page.txtSearchBox.WaitUntilVisible().SendKeys("Google");` 318 | 319 | А вот появления элемента page.txtInvalidSearchBox – код подождет один день, и выбросит TimeOutException (если за это время, элемент не появится на странице) 320 | 321 | `page.txtInvalidSearchBox.WaitUntilVisible(TimeSpan.FromDays(1)).SendKeys("Google");` 322 | 323 | ``` csharp 324 | [TestFixture] 325 | public class Class1 326 | { 327 | 328 | public class YandexPage : CorePage 329 | { 330 | [FindsBy(How = How.XPath, Using = @"id(""text"")")] 331 | public IWebElement txtSearchBox { get; set; } 332 | 333 | [FindsBy(How = How.XPath, Using = @"id(""Trololo-locator"")")] 334 | public IWebElement txtInvalidSearchBox { get; set; } 335 | } 336 | 337 | [Test] 338 | public void FirstTest() 339 | { 340 | SwdBrowser.Driver.Navigate().GoToUrl(@"http://yandex.ru"); 341 | var page = new YandexPage(); 342 | 343 | page.txtSearchBox.WaitUntilVisible().SendKeys("Google"); 344 | page.txtInvalidSearchBox.WaitUntilVisible(TimeSpan.FromDays(1)).SendKeys("Google"); 345 | 346 | } 347 | } 348 | ``` 349 | 350 | Еще, значение можно передавать в миллисекундах, например, следующий код, будет ждать появления элемента ровно 5 сек: 351 | `page.txtInvalidSearchBox.WaitUntilVisible(5000).SendKeys("Google");` 352 | 353 | 354 | ### Минутка наглого пиара: 355 | Сейчас, я работаю над фреймворком для автоматизированного тестирования на Selenium WebDriver. Задумка в том, что для начала автоматизации, вам нужно будет лишь скачать его с Github… и просто писать тесты, забыв о львиной доли рутинной работы. 356 | 357 | Все эти, и другие хорошие практики работы с Вебдрайвером, несомненно войдут в фреймворк. 358 | 359 | Сейчас там еще мало чего реализовано и работа кипит. 360 | 361 | Код фреймворка можно найти тут: 362 | 363 | https://github.com/dzhariy/SWD.Starter 364 | 365 | 366 | ## WebDriver. Метод-расширение для C#, возвращающий значение элемента для input и select тегов 367 | [Оригинал: automated-testing.info](http://automated-testing.info/t/zametka-webdriver-metod-rasshirenie-dlya-c-vozvrashhayushhij-znachenie-elementa-dlya-input-i-select-tegov/3797) 368 | 369 | Как многие уже успели заметить, а особенно в начале работы с Selenium WebDriver, свойство Text у IWebElement, не всегда возвращает ожидаемое значение. 370 | Например, для многострочного текстового поля: 371 | 372 | `` 373 | 374 | Свойство .Text: `driver.FindElement(By.Id(“mytext”)).Text` – вернет ожидаемый результат, т.е. значение «Hello». 375 | А вот для тега, описывающего однострочное текстовое поле: 376 | 377 | `` – почему-то будет всегда возвращать пустую строку, вместо ожидаемого 378 | текста ”SingleLine Hello”. 379 | 380 | Тут все дело в том, что стандартный element.Text из IWebElement – всегда возвращает текст, заключенный внутри открытого и закрытого тэга, а в случае с input, нужно прочитать не текст внутри, а атрибут с именем “value”. 381 | 382 | Аналогично для выпадающего списка, образуемого элементов select: в начале, нужно найти выбранный элемент `