├── SimpleBrowser.UnitTests ├── packages.config ├── SampleDocs │ ├── DecodedValue.htm │ ├── DecodedValue-malformed.htm │ ├── framecontainer.htm │ ├── framecontent.htm │ ├── FileUpload.htm │ ├── Elements.htm │ ├── iframecontainer.htm │ ├── Axefrog_Basic2.htm │ ├── CommentElements.htm │ ├── SimpleForm.htm │ ├── movies2.htm │ └── HTML5Elements.htm ├── OfflineTests │ ├── HttpHeaderTests.cs │ ├── WeirdUrls.cs │ ├── Parsing.cs │ ├── Uploading.cs │ ├── DecodedValue.cs │ ├── FileUri.cs │ ├── Selectors.cs │ └── Namespace.cs ├── OnlineTests │ ├── VerifyGZipEncoding.cs │ └── HttpHeaderTests.cs ├── Properties │ └── AssemblyInfo.cs ├── Issues.cs └── SimpleBrowser.UnitTests.csproj ├── SimpleBrowser ├── Query │ ├── XQuerySelector.cs │ ├── XQuerySelectorCreator.cs │ ├── ExpressionUtil.cs │ ├── Selectors │ │ ├── CommaSelector.cs │ │ ├── ChildSelector.cs │ │ ├── AllSelector.cs │ │ ├── NeighbourSelector.cs │ │ ├── DescendentSelector.cs │ │ ├── ElementSelector.cs │ │ ├── IdSelector.cs │ │ └── ClassSelector.cs │ ├── SelectorParserCatalog.cs │ ├── XQueryResultsContext.cs │ ├── XQueryException.cs │ └── XQuery.cs ├── KeyStateOption.cs ├── Parser │ ├── PositioningRules │ │ └── A.cs │ ├── DocumentCleaner.cs │ ├── HtmlParser.cs │ └── ElementPositioningRule.cs ├── ISessionRenderService.cs ├── Network │ ├── IWebRequestFactory.cs │ ├── IHttpWebResponse.cs │ ├── IHttpWebRequest.cs │ └── WebResponseWrapper.cs ├── SimpleBrowser.nuspec ├── NavigationState.cs ├── Enumerations.cs ├── ClassDesign.md ├── Elements │ ├── FrameElement.cs │ ├── FormElementValidationException.cs │ ├── SelectableInputElement.cs │ ├── CheckboxInputElement.cs │ ├── ButtonInputElement.cs │ ├── LabelElement.cs │ ├── ColorInputElement.cs │ ├── FileUploadElement.cs │ ├── UrlInputElement.cs │ ├── RadioInputElement.cs │ ├── ImageInputElement.cs │ ├── EmailInputElement.cs │ ├── OptionElement.cs │ ├── TextAreaElement.cs │ ├── AnchorElement.cs │ └── InputElement.cs ├── Properties │ └── AssemblyInfo.cs ├── Internal │ ├── ObjectExtensions.cs │ ├── XmlExtensions.cs │ ├── StringUtil.cs │ ├── CollectionExtensions.cs │ └── StringExtensions.cs ├── SimpleBrowser.csproj ├── HttpRequestLog.cs └── BasicAuthenticationToken.cs ├── .gitignore ├── SimpleBrowser.RazorSessionLogger ├── stylecop.json ├── RazorModel.cs ├── SimpleBrowser.RazorSessionLogger.csproj ├── RazorLogFormatter.cs └── Properties │ ├── Resources.Designer.cs │ └── Resources.resx ├── .vscode ├── tasks.json └── launch.json ├── Sample ├── Sample.csproj └── Properties │ └── AssemblyInfo.cs ├── azure-pipelines.yml ├── license.txt ├── readme.md └── SimpleBrowser.sln /SimpleBrowser.UnitTests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/SampleDocs/DecodedValue.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
£ sign
7 |
üü
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/SampleDocs/DecodedValue-malformed.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
Blah 5 |
£ sign
6 |
üü 7 | 8 | 9 | -------------------------------------------------------------------------------- /SimpleBrowser/Query/XQuerySelector.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace SimpleBrowser.Query 4 | { 5 | public interface IXQuerySelector 6 | { 7 | void Execute(XQueryResultsContext context); 8 | bool IsTransposeSelector { get; } 9 | } 10 | } -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/SampleDocs/framecontainer.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/SampleDocs/framecontent.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Open in arent frame 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /SimpleBrowser/Properties/Resources.resources 2 | /SimpleBrowser.Mono.userprefs 3 | axefrogcore/* 4 | debug/* 5 | release/* 6 | Bin 7 | Bin/* 8 | obj/* 9 | bin 10 | bin/* 11 | obj 12 | *.csuser 13 | *.suo 14 | *.ReSharper 15 | *.user 16 | *.tmp 17 | *.db 18 | *.orig 19 | *.bak 20 | .hg/* 21 | *.nupkg 22 | *.ncrunchsolution 23 | */StyleCop.Cache 24 | /.vs 25 | packages/* 26 | -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/SampleDocs/FileUpload.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /SimpleBrowser/Query/XQuerySelectorCreator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace SimpleBrowser.Query 5 | { 6 | public abstract class XQuerySelectorCreator 7 | { 8 | public abstract Regex MatchNext { get; } 9 | public abstract IXQuerySelector Create(XQueryParserContext context, Match match); 10 | public virtual int Priority { get { return 0; } } 11 | } 12 | } -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/SampleDocs/Elements.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
 8 | Text in a pre element
 9 | is displayed in a fixed-width
10 | font, and it preserves
11 | both      spaces and
12 | line breaks as well as trailing white space         
13 |             and leading white space.
14 | 
15 | 16 | -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/SampleDocs/iframecontainer.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | 17 | open in frame 18 | 19 | -------------------------------------------------------------------------------- /SimpleBrowser/KeyStateOption.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser 9 | { 10 | using System; 11 | 12 | [Flags] 13 | public enum KeyStateOption 14 | { 15 | None = 0, 16 | Shift = 1, 17 | Ctrl = 2, 18 | Alt = 4 19 | } 20 | } -------------------------------------------------------------------------------- /SimpleBrowser/Parser/PositioningRules/A.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Parser.PositioningRules 9 | { 10 | internal class A : BodyElementPositioningRule 11 | { 12 | // static readonly string[] _allowedParentTags = new [] { "" }; 13 | public override string TagName { get { return "a"; } } 14 | } 15 | } -------------------------------------------------------------------------------- /SimpleBrowser.RazorSessionLogger/stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | // ACTION REQUIRED: This file was automatically added to your project, but it 3 | // will not take effect until additional steps are taken to enable it. See the 4 | // following page for additional information: 5 | // 6 | // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md 7 | 8 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 9 | "settings": { 10 | "documentationRules": { 11 | "companyName": "SimpleBrowser", 12 | "copyrightText": "Copyright © 2010 - 2023, Nathan Ridley and the SimpleBrowser contributors.\r\n See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/OfflineTests/HttpHeaderTests.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.UnitTests.OfflineTests 9 | { 10 | using NUnit.Framework; 11 | 12 | [TestFixture] 13 | public class HttpHeaderTests 14 | { 15 | [Test] 16 | public void AddHostHeaderDoesNotThrow() 17 | { 18 | Browser browser = new Browser(); 19 | Assert.DoesNotThrow(() => browser.SetHeader("host:www.google.com")); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/OfflineTests/WeirdUrls.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.UnitTests.OfflineTests 9 | { 10 | using NUnit.Framework; 11 | 12 | [TestFixture] 13 | internal class WeirdUrls 14 | { 15 | [Test] 16 | public void JavascriptUrl() 17 | { 18 | Browser b = new Browser(); // does not need network to fail 19 | bool res = b.Navigate("javascript:'';"); 20 | Assert.False(res); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleBrowser/Query/ExpressionUtil.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace SimpleBrowser.Query 5 | { 6 | internal static class ExpressionUtil 7 | { 8 | static readonly Regex RxNoQuoting = new Regex(@"^[A-Za-z_][A-Za-z0-9_]*$"); 9 | public static void WriteString(this TextWriter writer, string str) 10 | { 11 | if(!RxNoQuoting.IsMatch(str)) 12 | { 13 | var c = str.Contains("'") ? '"' : '\''; 14 | str = str.Replace(@"\", "\0x27"); // preserve existing backslashes by replacing with the ESC control character 15 | // escape anything that needs to be escaped 16 | if(c == '"') 17 | str = str.Replace(@"""", @"\"""); 18 | else 19 | str = str.Replace("'", @"\'"); 20 | // restore and escape original backslashes 21 | str = string.Concat(c, str.Replace("\0x27", @"\\"), c); 22 | } 23 | writer.Write(str); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "command": "dotnet build", 9 | "type": "shell", 10 | "args": [ 11 | ], 12 | "group": "build", 13 | "presentation": { 14 | "reveal": "silent" 15 | }, 16 | "problemMatcher": "$msCompile" 17 | }, 18 | { 19 | "label": "copytestdocs", 20 | "command": "cp SimpleBrowser.UnitTests/SampleDocs SimpleBrowser.UnitTests/bin/Debug/", 21 | "type": "shell", 22 | "args": [ 23 | ], 24 | "group": "build", 25 | "presentation": { 26 | "reveal": "silent" 27 | }, 28 | "problemMatcher": "$msCompile" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /SimpleBrowser/ISessionRenderService.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2024, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser 9 | { 10 | using System.Collections.Generic; 11 | 12 | /// 13 | /// An interface to a session render service. 14 | /// 15 | public interface ISessionRenderService 16 | { 17 | // Task RenderToStringAsync(string viewName); 18 | 19 | // Task RenderToStringAsync(string viewName, TModel model); 20 | 21 | // string RenderToString(string viewPath, TModel model); 22 | 23 | string Render(List logs, string viewPath); 24 | } 25 | } -------------------------------------------------------------------------------- /SimpleBrowser/Network/IWebRequestFactory.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Network 9 | { 10 | using System; 11 | 12 | // TODO Review 13 | // 1) consider adding XML comments (documentation) to all public members 14 | 15 | public interface IWebRequestFactory 16 | { 17 | IHttpWebRequest GetWebRequest(Uri url); 18 | } 19 | 20 | public class DefaultRequestFactory : IWebRequestFactory 21 | { 22 | public IHttpWebRequest GetWebRequest(Uri url) 23 | { 24 | return new WebRequestWrapper(url); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /SimpleBrowser/Network/IHttpWebResponse.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Network 9 | { 10 | using System; 11 | using System.IO; 12 | using System.Net; 13 | 14 | // TODO Review 15 | // 1) consider adding XML comments (documentation) to all public members 16 | 17 | public interface IHttpWebResponse : IDisposable 18 | { 19 | Stream GetResponseStream(); 20 | 21 | string CharacterSet { get; set; } 22 | string ContentType { get; set; } 23 | WebHeaderCollection Headers { get; set; } 24 | HttpStatusCode StatusCode { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /SimpleBrowser/SimpleBrowser.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $version$ 5 | Nathan Ridley, Teun Duynstee, Kevin Yochum, Joe Feser 6 | Nathan Ridley 7 | https://github.com/axefrog/SimpleBrowser 8 | 9 | 10 | 11 | 12 | 13 | SimpleBrowser 14 | SimpleBrowser 15 | false 16 | SimpleBrowser is a lightweight, yet highly capable browser automation engine designed for automation and testing scenarios. 17 | 18 | headless browser http cookies browserautomation automation 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /SimpleBrowser/NavigationState.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser 9 | { 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Xml.Linq; 13 | 14 | internal class NavigationState 15 | { 16 | public Uri Uri; 17 | public string ContentType; 18 | public string Html; 19 | internal XDocument XDocument; 20 | public Uri Referer { get; set; } 21 | 22 | public readonly List Elements = new List(); 23 | 24 | public void Invalidate() 25 | { 26 | this.Elements.ForEach(e => e.Invalidate()); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Run NUnit Tests", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "/usr/bin/dotnet", 13 | "args": ["test"], 14 | "cwd": "${workspaceFolder}", 15 | "stopAtEntry": false, 16 | "console": "internalConsole" 17 | }, 18 | { 19 | "name": "Launch Sample Application", 20 | "type": "coreclr", 21 | "request": "launch", 22 | "preLaunchTask": "build", 23 | "program": "${workspaceFolder}/Sample/bin/Debug/netcoreapp2.0/Sample.dll", 24 | "args": [], 25 | "cwd": "${workspaceFolder}", 26 | "stopAtEntry": false, 27 | "console": "internalConsole" 28 | }, 29 | ] 30 | } -------------------------------------------------------------------------------- /SimpleBrowser/Parser/DocumentCleaner.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Parser 9 | { 10 | using System.Xml.Linq; 11 | 12 | /// 13 | /// Implements a class to clean up HTML 14 | /// 15 | public static class DocumentCleaner 16 | { 17 | /// 18 | /// Rebuilds the document 19 | /// 20 | /// The document to clean 21 | public static void Rebuild(XDocument doc) 22 | { 23 | if (string.Compare(doc.Root.Name.LocalName, "html", true) != 0) 24 | { 25 | XElement root = new XElement("html"); 26 | doc.Root.ReplaceWith(root); 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /SimpleBrowser/Parser/HtmlParser.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Parser 9 | { 10 | using System.Xml.Linq; 11 | 12 | public static class HtmlParser 13 | { 14 | public static XDocument ParseHtml(this string html, bool removeExtraWhiteSpace = true) 15 | { 16 | System.Collections.Generic.List tokens = HtmlTokenizer.Parse(html, removeExtraWhiteSpace); 17 | XDocument doc = DocumentBuilder.Parse(tokens); 18 | DocumentCleaner.Rebuild(doc); 19 | return doc; 20 | } 21 | 22 | public static XDocument CreateBlankHtmlDocument() 23 | { 24 | return XDocument.Parse("\r\n"); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/OnlineTests/VerifyGZipEncoding.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.UnitTests.OnlineTests 9 | { 10 | using System; 11 | using NUnit.Framework; 12 | 13 | [TestFixture] 14 | public class VerifyGZipEncoding 15 | { 16 | [Test] 17 | public void When_Setting_GZip_Encoding_Content_Should_Still_Be_Returned_As_Text() 18 | { 19 | Browser browser = new Browser { UseGZip = true }; 20 | browser.Navigate("http://www.facebook.com/"); 21 | Assert.That(browser.Url.Host == "www.facebook.com"); 22 | Assert.That(browser.Select("Title") != null); 23 | Assert.That(browser.Select("Title").Value.IndexOf("Facebook", StringComparison.OrdinalIgnoreCase) > -1); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /SimpleBrowser/Enumerations.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser 9 | { 10 | public enum FindBy 11 | { 12 | Name, 13 | Id, 14 | Class, 15 | Value, 16 | Text, 17 | PartialText, 18 | PartialName, 19 | PartialClass, 20 | PartialValue, 21 | PartialId 22 | } 23 | 24 | public enum ElementType 25 | { 26 | Anchor, 27 | TextField, 28 | Button, 29 | RadioButton, 30 | Checkbox, 31 | SelectBox, 32 | Script 33 | } 34 | 35 | public enum ClickResult 36 | { 37 | Failed, 38 | SucceededNoOp, 39 | SucceededNoNavigation, 40 | SucceededNavigationComplete, 41 | SucceededNavigationError 42 | } 43 | } -------------------------------------------------------------------------------- /SimpleBrowser/ClassDesign.md: -------------------------------------------------------------------------------- 1 | Class Design 2 | ============ 3 | 4 | The classes inside SimpleBrowser have a fairly simple design, This page tries to describe the responsibilities of the classes and how the (should) interact. 5 | 6 | Browser 7 | ------- 8 | This is the central class that the clients interact with. It is responsible for navigation and parsing the responses. 9 | 10 | HtmlResult 11 | ---------- 12 | This is the class that is passed out to client code to interact with elements in a page. The object can represent one or more HtmlElement instances. HtmlResult is the result of a query on the Browser object (unsing any of the FindXxx methods or Select(). HtmlResult is also the strtingpoint for new queries using the Select or Refine methods). 13 | 14 | HtmlElement 15 | ----------- 16 | This is a family of classes that wrap the XElement objects that form the inner storage of the page. The interaction with the underlying XML is primarily done through these classes. To get the correct kind of instance wrapping a certain XElement, you can call HtmlElement.CreateFor(element). These classes contain the implementation of the actions that should folow after clicking an anchor, checkbox, etc. They also implement the logic around being selected/unselected for options and form elements. -------------------------------------------------------------------------------- /SimpleBrowser/Query/Selectors/CommaSelector.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Query.Selectors 9 | { 10 | using System.Text.RegularExpressions; 11 | 12 | public class CommaSelector : IXQuerySelector 13 | { 14 | public void Execute(XQueryResultsContext context) 15 | { 16 | context.NewResultSet(); 17 | } 18 | 19 | public bool IsTransposeSelector { get { return true; } } 20 | 21 | internal static readonly Regex RxSelector = new Regex(@"^\s*,\s*"); 22 | } 23 | 24 | public class CommaSelectorCreator : XQuerySelectorCreator 25 | { 26 | public override Regex MatchNext { get { return CommaSelector.RxSelector; } } 27 | 28 | public override IXQuerySelector Create(XQueryParserContext context, Match match) 29 | { 30 | return new CommaSelector(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /SimpleBrowser/Query/Selectors/ChildSelector.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Query.Selectors 9 | { 10 | using System.Text.RegularExpressions; 11 | using System.Xml.Linq; 12 | 13 | public class ChildSelector : IXQuerySelector 14 | { 15 | public void Execute(XQueryResultsContext context) 16 | { 17 | context.PreTranslateResultSet = x => { return x.Elements(); }; 18 | } 19 | 20 | public bool IsTransposeSelector { get { return true; } } 21 | 22 | internal static readonly Regex RxSelector = new Regex(@"^\s*\>\s*"); 23 | } 24 | 25 | public class ChildSelectorCreator : XQuerySelectorCreator 26 | { 27 | public override Regex MatchNext { get { return ChildSelector.RxSelector; } } 28 | 29 | public override IXQuerySelector Create(XQueryParserContext context, Match match) 30 | { 31 | return new ChildSelector(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /SimpleBrowser/Query/Selectors/AllSelector.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Query.Selectors 9 | { 10 | using System.Text.RegularExpressions; 11 | 12 | public class AllSelector : IXQuerySelector 13 | { 14 | public AllSelector() 15 | { 16 | } 17 | 18 | public bool IsTransposeSelector { get { return false; } } 19 | 20 | public void Execute(XQueryResultsContext context) 21 | { 22 | context.ResultSetInternal = context.ResultSetInternal; 23 | } 24 | 25 | internal static readonly Regex RxSelector = new Regex(@"^\*"); 26 | } 27 | 28 | public class AllSelectorCreator : XQuerySelectorCreator 29 | { 30 | public override Regex MatchNext { get { return AllSelector.RxSelector; } } 31 | 32 | public override IXQuerySelector Create(XQueryParserContext context, Match match) 33 | { 34 | return new AllSelector(); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/OfflineTests/Parsing.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.UnitTests.OfflineTests 9 | { 10 | using NUnit.Framework; 11 | 12 | /// 13 | /// A class for testing the HTML parser 14 | /// 15 | [TestFixture] 16 | public class Parsing 17 | { 18 | /// 19 | /// Tests that the pre element contains the exact pre-formatted content from the HTML document. 20 | /// 21 | [Test] 22 | public void PreElement() 23 | { 24 | Browser b = new Browser(); 25 | b.SetContent(Helper.GetFromResources("SimpleBrowser.UnitTests.SampleDocs.Elements.htm")); 26 | 27 | HtmlResult preContent = b.Find("preTestElement"); 28 | Assert.IsTrue( 29 | preContent.Value.Contains("space \r\n and") || 30 | preContent.Value.Contains("space \n and")); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /SimpleBrowser/Query/Selectors/NeighbourSelector.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Query.Selectors 9 | { 10 | using System.Linq; 11 | using System.Text.RegularExpressions; 12 | 13 | public class NeighbourSelector : IXQuerySelector 14 | { 15 | public void Execute(XQueryResultsContext context) 16 | { 17 | context.PreTranslateResultSet = x => { return x.Select(e => e.ElementsAfterSelf().FirstOrDefault()); }; 18 | } 19 | 20 | public bool IsTransposeSelector { get { return true; } } 21 | 22 | internal static readonly Regex RxSelector = new Regex(@"^\s*\+\s*"); 23 | } 24 | 25 | public class NeighbourSelectorCreator : XQuerySelectorCreator 26 | { 27 | public override Regex MatchNext { get { return NeighbourSelector.RxSelector; } } 28 | 29 | public override IXQuerySelector Create(XQueryParserContext context, Match match) 30 | { 31 | return new NeighbourSelector(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /SimpleBrowser/Query/Selectors/DescendentSelector.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Query.Selectors 9 | { 10 | using System.Text.RegularExpressions; 11 | using System.Xml.Linq; 12 | 13 | public class DescendentSelector : IXQuerySelector 14 | { 15 | public void Execute(XQueryResultsContext context) 16 | { 17 | context.PreTranslateResultSet = x => x.Descendants(); 18 | } 19 | 20 | public bool IsTransposeSelector { get { return true; } } 21 | 22 | internal static readonly Regex RxSelector = new Regex(@"^\s+"); 23 | } 24 | 25 | public class DescendentSelectorCreator : XQuerySelectorCreator 26 | { 27 | public override Regex MatchNext { get { return DescendentSelector.RxSelector; } } 28 | 29 | public override IXQuerySelector Create(XQueryParserContext context, Match match) 30 | { 31 | return new DescendentSelector(); 32 | } 33 | 34 | public override int Priority { get { return -1000; } } 35 | } 36 | } -------------------------------------------------------------------------------- /SimpleBrowser/Query/SelectorParserCatalog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace SimpleBrowser.Query 7 | { 8 | public class SelectorParserCatalog 9 | { 10 | private static XQuerySelectorCreator[] _selectors; 11 | 12 | static SelectorParserCatalog() 13 | { 14 | _selectors = typeof(SelectorParserCatalog) 15 | .Assembly 16 | .GetTypes() 17 | .Where(t => t.IsSubclassOf(typeof(XQuerySelectorCreator))) 18 | .Select(xqstype => (XQuerySelectorCreator)Activator.CreateInstance(xqstype)) 19 | .OrderBy(xqsc => xqsc.Priority) // we want high priority at the end of the line 20 | .ToArray(); 21 | } 22 | 23 | internal IXQuerySelector GetNextSelector(XQueryParserContext context) 24 | { 25 | Match match = null; 26 | XQuerySelectorCreator xqsc = null; 27 | int matchLength = 0; 28 | var str = context.Query.Substring(context.Index); 29 | XQuerySelectorCreator[] selectors; 30 | lock(_selectors) 31 | selectors = _selectors.ToArray(); 32 | foreach(var qs in selectors) 33 | { 34 | var m = qs.MatchNext.Match(str); 35 | if(m.Success && m.Length > matchLength) 36 | { 37 | matchLength = m.Length; 38 | xqsc = qs; 39 | match = m; 40 | } 41 | } 42 | if(xqsc == null) 43 | return null; 44 | var sel = xqsc.Create(context, match); 45 | context.Index += match.Length; 46 | return sel; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /SimpleBrowser.RazorSessionLogger/RazorModel.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2023, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.RazorSessionLogger 9 | { 10 | /// 11 | /// A data model to pass to RazorLight for rendering. 12 | /// 13 | public class RazorModel 14 | { 15 | /// 16 | /// Gets or sets a date time representing the time the log entry was rendered. 17 | /// 18 | public DateTime? CaptureDate { get; set; } 19 | 20 | /// 21 | /// Gets or sets a title for the rendered page. 22 | /// 23 | public string? Title { get; set; } 24 | 25 | /// 26 | /// Gets or sets a total duration of the session being rendered. 27 | /// 28 | public TimeSpan? TotalDuration { get; set; } 29 | 30 | /// 31 | /// Gets or sets a collection of log entries. 32 | /// 33 | public List? Logs { get; set; } 34 | 35 | /// 36 | /// Gets or sets a count of requests. 37 | /// 38 | public int? RequestsCount { get; set; } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/OfflineTests/Uploading.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.UnitTests.OfflineTests 9 | { 10 | using System; 11 | using System.IO; 12 | using NUnit.Framework; 13 | 14 | [TestFixture] 15 | public class Uploading 16 | { 17 | [Test] 18 | public void Uploading_A_File_With_Enctype_MultipartMime() 19 | { 20 | Browser b = new Browser(Helper.GetAllways200RequestMocker()); 21 | b.SetContent(Helper.GetFromResources("SimpleBrowser.UnitTests.SampleDocs.FileUpload.htm")); 22 | 23 | HttpRequestLog lastLog = null; 24 | b.RequestLogged += (br, l) => 25 | { 26 | lastLog = l; 27 | }; 28 | 29 | HtmlResult form = b.Select("form"); 30 | HtmlResult file = b.Select("input[name=theFile]"); 31 | DirectoryInfo dir = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory); 32 | 33 | file.Value = dir.GetFiles()[3].FullName; 34 | form.SubmitForm(); 35 | 36 | Assert.NotNull(lastLog); 37 | Assert.That(lastLog.Method == "POST"); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/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: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("SimpleBrowser.UnitTests")] 12 | [assembly: AssemblyCopyright("Copyright © 2012")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("8297087c-a1b4-4ba6-8264-3c8a8b625600")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("0.6.0.0")] 35 | [assembly: AssemblyFileVersion("0.6.0.0")] 36 | -------------------------------------------------------------------------------- /SimpleBrowser/Network/IHttpWebRequest.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Network 9 | { 10 | using System; 11 | using System.IO; 12 | using System.Net; 13 | using System.Security.Cryptography.X509Certificates; 14 | // TODO Review 15 | // 1) consider adding XML comments (documentation) to all public members 16 | 17 | public interface IHttpWebRequest 18 | { 19 | Stream GetRequestStream(); 20 | 21 | IHttpWebResponse GetResponse(); 22 | 23 | long ContentLength { get; set; } 24 | WebHeaderCollection Headers { get; set; } 25 | DecompressionMethods AutomaticDecompression { get; set; } 26 | string ContentType { get; set; } 27 | string Method { get; set; } 28 | string UserAgent { get; set; } 29 | string Accept { get; set; } 30 | int Timeout { get; set; } 31 | bool AllowAutoRedirect { get; set; } 32 | CookieContainer CookieContainer { get; set; } 33 | IWebProxy Proxy { get; set; } 34 | string Referer { get; set; } 35 | Uri Address { get; } 36 | string Host { get; set; } 37 | X509CertificateCollection ClientCertificates { get; set; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sample/Sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sample for SimpleBrowser 5 | Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 6 | Nathan Ridley and the SimpleBrowser contributors. 7 | Sample 8 | net8.0 9 | true 10 | portable 11 | Sample 12 | Exe 13 | Sample.Program 14 | false 15 | false 16 | false 17 | false 18 | false 19 | false 20 | false 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # ASP.NET Core 2 | # Build and test ASP.NET Core projects targeting .NET Core. 3 | # Add steps that run tests, create a NuGet package, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core 5 | 6 | trigger: 7 | - master 8 | 9 | pool: 10 | vmImage: 'ubuntu-latest' 11 | 12 | variables: 13 | buildConfiguration: 'Release' 14 | 15 | steps: 16 | - script: dotnet build --configuration $(buildConfiguration) 17 | displayName: 'dotnet build $(buildConfiguration)' 18 | 19 | - task: DotNetCoreCLI@2 20 | displayName: 'dotnet test $(buildConfiguration)' 21 | inputs: 22 | command: test 23 | projects: '**/*Tests/*.csproj' 24 | arguments: '--configuration $(buildConfiguration)' 25 | 26 | - task: NuGetToolInstaller@1 27 | displayName: 'Use NuGet' 28 | 29 | - task: NuGetCommand@2 30 | inputs: 31 | command: 'pack' 32 | packagesToPack: '**/SimpleBrowser.nuspec' 33 | versioningScheme: 'off' 34 | buildProperties: 'version=0.6.0' 35 | 36 | - task: CopyFiles@2 37 | displayName: 'Copy Files to: $(build.artifactstagingdirectory)' 38 | inputs: 39 | SourceFolder: '$(agent.builddirectory)' 40 | Contents: | 41 | **/SimpleBrowser/bin/**/SimpleBrowser.dll 42 | **/SimpleBrowser/bin/**/SimpleBrowser.pdb 43 | TargetFolder: '$(build.artifactstagingdirectory)' 44 | condition: succeededOrFailed() 45 | 46 | - task: PublishBuildArtifacts@1 47 | displayName: 'Publish Artifact: drop' 48 | inputs: 49 | PathtoPublish: '$(build.artifactstagingdirectory)' 50 | artifactName: drop 51 | condition: succeededOrFailed() -------------------------------------------------------------------------------- /SimpleBrowser.RazorSessionLogger/SimpleBrowser.RazorSessionLogger.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | True 8 | 9 | 10 | 11 | 1701;1702 12 | 9999 13 | True 14 | 15 | 16 | 17 | 1701;1702 18 | 9999 19 | True 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | all 35 | runtime; build; native; contentfiles; analyzers; buildtransitive 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/SampleDocs/Axefrog_Basic2.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 |
17 | 18 | 22 | 37 | 38 | 39 |
40 | 41 | Teun 42 | Teun 43 | 44 | 45 | -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/OfflineTests/DecodedValue.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.UnitTests.OfflineTests 9 | { 10 | using System.Linq; 11 | using NUnit.Framework; 12 | 13 | [TestFixture] 14 | public class DecodedValue 15 | { 16 | [Test] 17 | public void HtmlElement_DecodedValue() 18 | { 19 | Browser b = new Browser(); 20 | b.SetContent(Helper.GetFromResources("SimpleBrowser.UnitTests.SampleDocs.DecodedValue.htm")); 21 | 22 | HtmlResult div = b.Select("div"); 23 | Assert.That(div.ToList()[0].DecodedValue, Is.EqualTo("£ sign")); 24 | Assert.That(div.ToList()[1].DecodedValue, Is.EqualTo("üü")); 25 | } 26 | 27 | [Test] 28 | public void HtmlElement_DecodedValue_MalformedDocument() 29 | { 30 | Browser b = new Browser(); 31 | b.SetContent(Helper.GetFromResources("SimpleBrowser.UnitTests.SampleDocs.DecodedValue-malformed.htm")); 32 | 33 | HtmlResult div = b.Select("div"); 34 | Assert.That(div.ToList()[0].DecodedValue, Is.EqualTo("Blah £ sign üü")); 35 | Assert.That(div.ToList()[1].DecodedValue, Is.EqualTo("£ sign")); 36 | Assert.That(div.ToList()[2].DecodedValue, Is.EqualTo("üü")); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Revised BSD License 2 | 3 | Copyright (c) 2013, Nathan Ridley 4 | All rights reserved. 5 | 6 | Also, with great thanks, contributed to and improved by: 7 | Kevin Yochum (https://github.com/kevingy) 8 | Teun Duynstee (https://github.com/Teun) 9 | Joe Feser (https://github.com/joefeser) 10 | and others (https://github.com/SimpleBrowserDotNet/SimpleBrowser/graphs/contributors) 11 | 12 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 13 | 14 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 15 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 16 | Neither the name of the nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 18 | -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/Issues.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.UnitTests 9 | { 10 | using NUnit.Framework; 11 | 12 | [TestFixture] 13 | public class Issues 14 | { 15 | [Test] 16 | public void SampleApp() 17 | { 18 | Browser b = new Browser(Helper.GetMoviesRequestMocker()); 19 | HttpRequestLog lastRequest = null; 20 | b.RequestLogged += (br, l) => 21 | { 22 | lastRequest = l; 23 | }; 24 | b.Navigate("http://localhost/movies/"); 25 | HtmlResult link = b.Find(ElementType.Anchor, FindBy.Text, "Create New"); 26 | link.Click(); 27 | HtmlResult box = b.Select("input[name=Title]"); 28 | box.Value = "1234"; 29 | box = b.Select("input[name=ReleaseDate]"); 30 | box.Value = "2011-01-01"; 31 | box = b.Select("input[name=Genre]"); 32 | box.Value = "dark"; 33 | box = b.Select("input[name=Price]"); 34 | box.Value = "51"; 35 | box = b.Select("input[name=Rating]"); 36 | box.Value = "***"; 37 | link = b.Select("input[type=submit]"); 38 | link.Click(); 39 | Assert.That(b.LastWebException == null, "Webexception detected"); 40 | Assert.That(lastRequest.PostBody.Contains("&Price=51&")); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /SimpleBrowser/Query/Selectors/ElementSelector.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Query.Selectors 9 | { 10 | using System.Collections.Generic; 11 | using System.Diagnostics; 12 | using System.Linq; 13 | using System.Text.RegularExpressions; 14 | using System.Xml.Linq; 15 | 16 | public class ElementSelector : IXQuerySelector 17 | { 18 | private readonly string _name; 19 | 20 | public ElementSelector(string name) 21 | { 22 | this._name = name.ToLower(); 23 | } 24 | 25 | public bool IsTransposeSelector { get { return false; } } 26 | 27 | public void Execute(XQueryResultsContext context) 28 | { 29 | IEnumerable set = context.ResultSetInternal; 30 | Debug.WriteLine("selecting <" + this._name + "> from " + set.Count() + " nodes"); 31 | context.ResultSetInternal = set 32 | .Where(x => string.Compare(x.Name.LocalName, this._name, true) == 0); 33 | } 34 | 35 | internal static readonly Regex RxSelector = new Regex(@"^[A-Za-z][A-Za-z0-9_\-]*"); 36 | } 37 | 38 | public class ElementSelectorCreator : XQuerySelectorCreator 39 | { 40 | public override Regex MatchNext { get { return ElementSelector.RxSelector; } } 41 | 42 | public override IXQuerySelector Create(XQueryParserContext context, Match match) 43 | { 44 | return new ElementSelector(match.Value); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /SimpleBrowser/Query/Selectors/IdSelector.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Query.Selectors 9 | { 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Text.RegularExpressions; 13 | using System.Xml.Linq; 14 | using SimpleBrowser; 15 | 16 | public class IdSelector : IXQuerySelector 17 | { 18 | private readonly string _id; 19 | 20 | public IdSelector(string id) 21 | { 22 | this._id = id.ToLower(); 23 | } 24 | 25 | public bool IsTransposeSelector { get { return false; } } 26 | 27 | public void Execute(XQueryResultsContext context) 28 | { 29 | // var ids = context.ResultSetInternal.Where(x => x.HasAttributeCI("id")).Select(x => x.GetAttributeCI("id")).ToArray(); 30 | IEnumerable results = context.ResultSetInternal.Where(x => string.Compare(x.GetAttributeCI("id"), this._id, true) == 0); 31 | context.ResultSetInternal = results; 32 | } 33 | 34 | internal static readonly Regex RxSelector = new Regex(@"^\#(?[A-Za-z_][A-Za-z0-9_\-:\.]+)"); 35 | } 36 | 37 | public class IdSelectorCreator : XQuerySelectorCreator 38 | { 39 | public override Regex MatchNext { get { return IdSelector.RxSelector; } } 40 | 41 | public override IXQuerySelector Create(XQueryParserContext context, Match match) 42 | { 43 | return new IdSelector(match.Groups["id"].Value); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Sample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | using System.Reflection; 9 | using System.Runtime.InteropServices; 10 | 11 | // General Information about an assembly is controlled through the following 12 | // set of attributes. Change these attribute values to modify the information 13 | // associated with an assembly. 14 | [assembly: AssemblyDescription("")] 15 | [assembly: AssemblyConfiguration("")] 16 | [assembly: AssemblyCompany("SimpleBrowser")] 17 | [assembly: AssemblyProduct("Sample")] 18 | [assembly: AssemblyCopyright("Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors.")] 19 | [assembly: AssemblyTrademark("")] 20 | [assembly: AssemblyCulture("")] 21 | 22 | // Setting ComVisible to false makes the types in this assembly not visible 23 | // to COM components. If you need to access a type in this assembly from 24 | // COM, set the ComVisible attribute to true on that type. 25 | [assembly: ComVisible(false)] 26 | 27 | // The following GUID is for the ID of the typelib if this project is exposed to COM 28 | [assembly: Guid("cf0c20f6-ca26-42a7-8a7f-036eabf2e54a")] 29 | 30 | // Version information for an assembly consists of the following four values: 31 | // 32 | // Major Version 33 | // Minor Version 34 | // Build Number 35 | // Revision 36 | // 37 | // You can specify all the values or you can default the Build and Revision Numbers 38 | // by using the '*' as shown below: 39 | // [assembly: AssemblyVersion("1.0.*")] 40 | [assembly: AssemblyVersion("0.6.0.0")] 41 | [assembly: AssemblyFileVersion("0.6.0.0")] -------------------------------------------------------------------------------- /SimpleBrowser/Query/Selectors/ClassSelector.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Query.Selectors 9 | { 10 | using System.Linq; 11 | using System.Text.RegularExpressions; 12 | using SimpleBrowser; 13 | 14 | public class ClassSelector : IXQuerySelector 15 | { 16 | private readonly string _class; 17 | 18 | public ClassSelector(string @class) 19 | { 20 | this._class = @class; 21 | } 22 | 23 | public bool IsTransposeSelector { get { return false; } } 24 | 25 | public void Execute(XQueryResultsContext context) 26 | { 27 | context.ResultSetInternal = context.ResultSetInternal 28 | .Where(x => 29 | { 30 | string c = x.GetAttributeCI("class"); 31 | if (c == null) 32 | { 33 | return false; 34 | } 35 | 36 | return c.Split(' ').Contains(this._class); 37 | }); 38 | } 39 | 40 | internal static readonly Regex RxSelector = new Regex(@"^\.(?[A-Za-z0-9_\-]+)"); 41 | } 42 | 43 | public class ClassSelectorCreator : XQuerySelectorCreator 44 | { 45 | public override Regex MatchNext { get { return ClassSelector.RxSelector; } } 46 | 47 | public override IXQuerySelector Create(XQueryParserContext context, Match match) 48 | { 49 | return new ClassSelector(match.Groups["class"].Value); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/SampleDocs/CommentElements.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | Comment Elements Unit Test Sample HTML Document 15 | 16 | 17 | 32 | 33 | Downlevel-revealed conditional comment test 34 | 35 | 38 | 39 |

40 | This is a test of the various types of elements with tags that open with <!
41 | As of this writing, those include:
42 | <!DOCTYPE>
43 | <!-- comments > (well-formed and malformed comment opening tag)
44 | <![CDATA[data]]>
45 | <![conditional comments]> (for example, the Microsoft specific <![if gt IE 7]>)
46 |

47 | 48 | -------------------------------------------------------------------------------- /SimpleBrowser/Query/XQueryResultsContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Xml.Linq; 5 | 6 | namespace SimpleBrowser.Query 7 | { 8 | public class XQueryResultsContext 9 | { 10 | public XQueryResultsContext(XDocument doc) 11 | { 12 | Document = doc; 13 | } 14 | 15 | private List> _resultSets = new List>(); 16 | private IEnumerable _currentResultSet; 17 | 18 | internal Func, IEnumerable> PreTranslateResultSet { get; set; } 19 | internal XDocument Document { get; private set; } 20 | 21 | internal XElement[] ResultSet 22 | { 23 | get 24 | { 25 | return _resultSets 26 | .SelectMany(x => x) 27 | .Distinct() // new XElementEqualityComparer() 28 | .ToArray(); 29 | } 30 | } 31 | 32 | internal IEnumerable ResultSetInternal 33 | { 34 | get 35 | { 36 | if(_currentResultSet != null) 37 | { 38 | if(PreTranslateResultSet != null) 39 | { 40 | var results = PreTranslateResultSet(_currentResultSet); 41 | PreTranslateResultSet = null; 42 | _currentResultSet = results; 43 | } 44 | return _currentResultSet; 45 | } 46 | return Document.Descendants(); 47 | } 48 | 49 | set 50 | { 51 | if(_currentResultSet == null) 52 | _resultSets.Add(value); 53 | else 54 | _resultSets[_resultSets.Count - 1] = value; 55 | _currentResultSet = value; 56 | } 57 | } 58 | 59 | internal void NewResultSet() 60 | { 61 | _currentResultSet = null; 62 | } 63 | 64 | class XElementEqualityComparer : IEqualityComparer 65 | { 66 | private XNodeEqualityComparer _comparer = new XNodeEqualityComparer(); 67 | public bool Equals(XElement x, XElement y) 68 | { 69 | return ReferenceEquals(x, y); 70 | } 71 | 72 | public int GetHashCode(XElement obj) 73 | { 74 | return _comparer.GetHashCode(obj); 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /SimpleBrowser/Elements/FrameElement.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System; 11 | using System.Text.RegularExpressions; 12 | using System.Xml.Linq; 13 | 14 | internal class FrameElement : HtmlElement 15 | { 16 | public FrameElement(XElement element) 17 | : base(element) 18 | { 19 | } 20 | 21 | public Browser FrameBrowser { get; private set; } 22 | 23 | internal override string GetAttributeValue(string name) 24 | { 25 | if (name == "SimpleBrowser.WebDriver:frameWindowHandle") 26 | { 27 | return this.FrameBrowser.WindowHandle; 28 | } 29 | 30 | return base.GetAttributeValue(name); 31 | } 32 | 33 | public string Src 34 | { 35 | get 36 | { 37 | return Regex.Replace(this.Element.GetAttributeCI("src"), @"\s+", ""); 38 | } 39 | } 40 | 41 | public string Name 42 | { 43 | get 44 | { 45 | return this.Element.GetAttributeCI("name"); 46 | } 47 | } 48 | 49 | internal override Browser OwningBrowser 50 | { 51 | get 52 | { 53 | return base.OwningBrowser; 54 | } 55 | set 56 | { 57 | base.OwningBrowser = value; 58 | this.FrameBrowser = this.OwningBrowser.CreateChildBrowser(this.Name); 59 | this.FrameBrowser.Navigate(new Uri(this.OwningBrowser.Url, this.Src)); 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /SimpleBrowser/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | using System.Reflection; 9 | using System.Runtime.CompilerServices; 10 | using System.Runtime.InteropServices; 11 | 12 | // General Information about an assembly is controlled through the following 13 | // set of attributes. Change these attribute values to modify the information 14 | // associated with an assembly. 15 | [assembly: AssemblyDescription("")] 16 | [assembly: AssemblyConfiguration("")] 17 | [assembly: AssemblyCompany("SimpleBrowser")] 18 | [assembly: AssemblyProduct("SimpleBrowser")] 19 | [assembly: AssemblyCopyright("Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors.")] 20 | [assembly: AssemblyTrademark("")] 21 | [assembly: AssemblyCulture("")] 22 | 23 | // Setting ComVisible to false makes the types in this assembly not visible 24 | // to COM components. If you need to access a type in this assembly from 25 | // COM, set the ComVisible attribute to true on that type. 26 | [assembly: ComVisible(false)] 27 | 28 | // The following GUID is for the ID of the typelib if this project is exposed to COM 29 | [assembly: Guid("532935fe-16c4-4fa2-941e-6089833bf6b1")] 30 | 31 | //Allow for unit tests 32 | [assembly: InternalsVisibleTo("SimpleBrowser.UnitTests")] 33 | 34 | // Version information for an assembly consists of the following four values: 35 | // 36 | // Major Version 37 | // Minor Version 38 | // Build Number 39 | // Revision 40 | // 41 | // You can specify all the values or you can default the Build and Revision Numbers 42 | // by using the '*' as shown below: 43 | // [assembly: AssemblyVersion("1.0.*")] 44 | [assembly: AssemblyVersion("0.6.0.0")] 45 | [assembly: AssemblyFileVersion("0.6.0.0")] -------------------------------------------------------------------------------- /SimpleBrowser/Elements/FormElementValidationException.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System; 11 | 12 | /// 13 | /// Implements a custom exception indicating a form valudation error that interrupts a form submission. 14 | /// 15 | public class FormElementValidationException : Exception 16 | { 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | public FormElementValidationException() 21 | { 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class with a specified error message. 26 | /// 27 | /// The message that describes the error. 28 | public FormElementValidationException(string message) 29 | : base(message) 30 | { 31 | } 32 | 33 | /// 34 | /// Initializes a new instance of the with a specified error message and a reference to the inner exception that is the cause of this exception. 35 | /// 36 | /// The message that describes the error. 37 | /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. 38 | public FormElementValidationException(string message, Exception inner) 39 | : base(message, inner) 40 | { 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /SimpleBrowser/Elements/SelectableInputElement.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System.Collections.Generic; 11 | using System.Xml.Linq; 12 | 13 | /// 14 | /// Implements an abstract selectable input element, not corresponding with any specific input type. 15 | /// 16 | internal abstract class SelectableInputElement : InputElement 17 | { 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The associated with this element. 22 | public SelectableInputElement(XElement element) 23 | : base(element) 24 | { } 25 | 26 | /// 27 | /// Gets or sets a value indicating whether the selectable input element is selected. 28 | /// 29 | public virtual bool Selected { get; set; } 30 | 31 | /// 32 | /// Gets the form values to submit for this input 33 | /// 34 | /// True, if the action to submit the form was clicking this element. Otherwise, false. 35 | /// A collection of objects. 36 | public override IEnumerable ValuesToSubmit(bool isClickedElement, bool validate) 37 | { 38 | if (this.Selected && !string.IsNullOrEmpty(this.Name) && !this.Disabled) 39 | { 40 | yield return new UserVariableEntry() { Name = Name, Value = string.IsNullOrEmpty(this.Value) ? "on" : this.Value }; 41 | } 42 | 43 | yield break; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/OnlineTests/HttpHeaderTests.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.UnitTests.OnlineTests 9 | { 10 | using System; 11 | using NUnit.Framework; 12 | 13 | [TestFixture] 14 | public class HttpHeaderTests 15 | { 16 | [Test] 17 | public void CustomHostHeaderIsSent() 18 | { 19 | Browser browser = new Browser(); 20 | 21 | browser.Navigate("http://204.144.122.42"); 22 | Assert.That(browser.RequestData().Host, Is.EqualTo("204.144.122.42"), "Expected host header to be default from url."); 23 | 24 | // I happen to know that this domain name is not in dns (my company owns it) 25 | // but that ip (also ours) is serving content for said domain. 26 | // Is there another way to confirm the overriden header is sent that does 27 | // not depend on some random internet server? 28 | browser.SetHeader("host:uscloud.asldkfhjawoeif.com"); 29 | browser.Navigate("http://204.144.122.42"); 30 | 31 | Assert.That(browser.RequestData().Address, Is.EqualTo(new Uri("http://204.144.122.42")), "Expected the address to be the website url."); 32 | Assert.That(browser.RequestData().Host, Is.EqualTo("uscloud.asldkfhjawoeif.com"), "Expected the manually set host."); 33 | } 34 | 35 | [Test] 36 | public void CustomHeaderIsSent() 37 | { 38 | const string headername = "X-MyCustomHeader"; 39 | const string headervalue = "hello.world"; 40 | 41 | Browser browser = new Browser(); 42 | browser.SetHeader($"{headername}:{headervalue}"); 43 | browser.Navigate("http://localhost.me"); 44 | 45 | Assert.That( 46 | browser.RequestData()?.RequestHeaders?[headername], 47 | Is.EqualTo(headervalue), 48 | $"Expected header {headername} to be inserted with value {headervalue}"); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/SampleDocs/SimpleForm.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 18 | 19 | 22 | 23 | 29 | 30 | 31 |
32 | 33 | 34 |
35 | 36 |
37 | 38 | 39 |
40 | 41 | 42 | 46 | 61 | 62 | 63 | 64 | Teun 65 | Teun 66 | 67 | 68 |
69 | 70 | 71 | -------------------------------------------------------------------------------- /SimpleBrowser.RazorSessionLogger/RazorLogFormatter.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2023, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.RazorSessionLogger 9 | { 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | using Microsoft.CodeAnalysis; 14 | using SimpleBrowser.RazorSessionLogger.Properties; 15 | 16 | /// 17 | /// A log formatter supporting RazorLight. 18 | /// 19 | public class RazorLogFormatter : ISessionRenderService 20 | { 21 | /// 22 | /// Render the log file. 23 | /// 24 | /// A collection of log entries to render. 25 | /// A title. 26 | /// A string containing the rendered content. 27 | public string Render(List logs, string title) 28 | { 29 | RazorModel model = new () 30 | { 31 | CaptureDate = DateTime.UtcNow, 32 | TotalDuration = logs.Count == 0 ? TimeSpan.MinValue : logs.Last().ServerTime - logs.First().ServerTime, 33 | Title = title, 34 | Logs = logs, 35 | RequestsCount = logs.Count(l => l is HttpRequestLog), 36 | }; 37 | 38 | var references = new MetadataReference[] 39 | { 40 | MetadataReference.CreateFromFile("SimpleBrowser.dll"), 41 | }; 42 | 43 | var engine = new RazorLight.RazorLightEngineBuilder() 44 | .UseEmbeddedResourcesProject(typeof(RazorLogFormatter)) 45 | .AddMetadataReferences(references) 46 | .SetOperatingAssembly(typeof(RazorLogFormatter).Assembly) 47 | .AddDefaultNamespaces(["SimpleBrowser", "System.Web", "SimpleBrowser.RazorSessionLogger"]) 48 | .UseMemoryCachingProvider() 49 | .Build(); 50 | 51 | string result = engine.CompileRenderStringAsync("ServerTime", Resources.HtmlLogTemplate, model).Result; 52 | 53 | return result; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /SimpleBrowser/Elements/CheckboxInputElement.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System.Xml.Linq; 11 | 12 | /// 13 | /// Implements an input element of type checkbox. 14 | /// 15 | internal class CheckboxInputElement : SelectableInputElement 16 | { 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// The associated with this element. 21 | public CheckboxInputElement(XElement element) 22 | : base(element) 23 | { } 24 | 25 | /// 26 | /// Gets or sets the selected (checked) state of the checkbox 27 | /// 28 | public override bool Selected 29 | { 30 | get 31 | { 32 | return this.GetAttribute("checked") != null; 33 | } 34 | 35 | set 36 | { 37 | if (this.Disabled) 38 | { 39 | return; 40 | } 41 | 42 | if (value) 43 | { 44 | this.Element.SetAttributeValue("checked", "checked"); 45 | } 46 | else 47 | { 48 | this.Element.RemoveAttributeCI("checked"); 49 | } 50 | } 51 | } 52 | 53 | /// 54 | /// Perform a click action on the checkbox input element. 55 | /// 56 | /// The of the operation. 57 | public override ClickResult Click() 58 | { 59 | if (this.Disabled) 60 | { 61 | return ClickResult.SucceededNoOp; 62 | } 63 | 64 | base.Click(); 65 | this.Selected = !this.Selected; 66 | return ClickResult.SucceededNoNavigation; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /SimpleBrowser/Network/WebResponseWrapper.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Network 9 | { 10 | using System; 11 | using System.IO; 12 | using System.Net; 13 | 14 | internal class WebResponseWrapper : IHttpWebResponse 15 | { 16 | private HttpWebResponse _wr; 17 | 18 | public WebResponseWrapper(HttpWebResponse resp) 19 | { 20 | this._wr = resp; 21 | } 22 | 23 | #region IHttpWebResponse Members 24 | 25 | public Stream GetResponseStream() 26 | => this._wr.GetResponseStream(); 27 | 28 | public string CharacterSet 29 | { 30 | get 31 | { 32 | return this._wr.CharacterSet; 33 | } 34 | 35 | set 36 | { 37 | throw new NotImplementedException(); 38 | } 39 | } 40 | 41 | public string ContentType 42 | { 43 | get 44 | { 45 | return this._wr.ContentType; 46 | } 47 | 48 | set 49 | { 50 | this._wr.ContentType = value; 51 | } 52 | } 53 | 54 | public WebHeaderCollection Headers 55 | { 56 | get 57 | { 58 | return this._wr.Headers; 59 | } 60 | 61 | set 62 | { 63 | throw new NotImplementedException(); 64 | } 65 | } 66 | 67 | public HttpStatusCode StatusCode 68 | { 69 | get 70 | { 71 | return this._wr.StatusCode; 72 | } 73 | 74 | set 75 | { 76 | throw new NotImplementedException(); 77 | } 78 | } 79 | 80 | #endregion IHttpWebResponse Members 81 | 82 | #region IDisposable Members 83 | 84 | public void Dispose() 85 | { 86 | (this._wr as IDisposable)?.Dispose(); 87 | } 88 | 89 | #endregion IDisposable Members 90 | } 91 | } -------------------------------------------------------------------------------- /SimpleBrowser/Elements/ButtonInputElement.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System.Collections.Generic; 11 | using System.Xml.Linq; 12 | 13 | /// 14 | /// Implements an HTML button or input submit element 15 | /// 16 | internal class ButtonInputElement : InputElement 17 | { 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The associated with this element. 22 | public ButtonInputElement(XElement element) 23 | : base(element) 24 | { 25 | } 26 | 27 | /// 28 | /// Gets the form values to submit for this input 29 | /// 30 | /// True, if the action to submit the form was clicking this element. Otherwise, false. 31 | /// A collection of objects. 32 | public override IEnumerable ValuesToSubmit(bool isClickedElement, bool validate) 33 | { 34 | if (isClickedElement && !string.IsNullOrEmpty(this.Name)) 35 | { 36 | return base.ValuesToSubmit(isClickedElement, validate); 37 | } 38 | 39 | return new UserVariableEntry[0]; 40 | } 41 | 42 | /// 43 | /// Perform a click action on the label element. 44 | /// 45 | /// The of the operation. 46 | public override ClickResult Click() 47 | { 48 | if (this.Disabled) 49 | { 50 | return ClickResult.SucceededNoOp; 51 | } 52 | 53 | base.Click(); 54 | if (this.SubmitForm(clickedElement: this)) 55 | { 56 | return ClickResult.SucceededNavigationComplete; 57 | } 58 | 59 | return ClickResult.SucceededNavigationError; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /SimpleBrowser/Internal/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser 9 | { 10 | using System.Collections.Specialized; 11 | using System.Linq; 12 | using System.Reflection; 13 | 14 | // TODO Review 15 | // 1) consider making thing class internal, as it resides in the Internal directory 16 | // --> prefered, though a breaking change 17 | // 2) or if keeping public 18 | // --> consider adding XML comments (documentation) to all public members 19 | 20 | public static class ObjectExtensions 21 | { 22 | public static bool EqualsAny(this object source, params object[] comparisons) 23 | { 24 | return comparisons.Any(o => Equals(source, o)); 25 | } 26 | 27 | public static NameValueCollection ToNameValueCollection(this object o) 28 | { 29 | NameValueCollection nvc = new NameValueCollection(); 30 | foreach (PropertyInfo p in o.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance)) 31 | { 32 | nvc.Add(p.Name, (p.GetValue(o, null) ?? "").ToString()); 33 | } 34 | 35 | return nvc; 36 | } 37 | 38 | public static PropertyInfo[] GetSettableProperties(this object o) 39 | { 40 | return o.GetType() 41 | .GetProperties(BindingFlags.GetProperty | BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance) 42 | .Where(p => p.GetSetMethod() != null) 43 | .ToArray(); 44 | } 45 | 46 | public static string ToQueryString(this object o) 47 | { 48 | return o.ToNameValueCollection().ToQueryString(); 49 | } 50 | 51 | public static string ToJson(this object obj) 52 | { 53 | return Newtonsoft.Json.JsonConvert.SerializeObject(obj); 54 | } 55 | 56 | public static T DuckTypeAs(this object o) 57 | { 58 | return Newtonsoft.Json.JsonConvert.DeserializeObject(Newtonsoft.Json.JsonConvert.SerializeObject(o)); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /SimpleBrowser/Elements/LabelElement.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System.Linq; 11 | using System.Xml.Linq; 12 | 13 | /// 14 | /// Implements a label element. 15 | /// 16 | internal class LabelElement : FormElementElement 17 | { 18 | /// 19 | /// The element associated with this label element. 20 | /// 21 | private HtmlElement associatedElement = null; 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | /// The associated with this element. 27 | public LabelElement(XElement element) 28 | : base(element) 29 | { } 30 | 31 | /// 32 | /// Gets the value of the input element associated with this label element. 33 | /// 34 | public HtmlElement For 35 | { 36 | get 37 | { 38 | if (this.associatedElement == null) 39 | { 40 | string id = Element.GetAttributeCI("for"); 41 | if (id == null) 42 | { 43 | return null; 44 | } 45 | 46 | var element = Element.Document.Descendants().Where(e => e.GetAttributeCI("id") == id).FirstOrDefault(); 47 | if (element == null) 48 | { 49 | return null; 50 | } 51 | 52 | this.associatedElement = OwningBrowser.CreateHtmlElement(element); 53 | } 54 | 55 | return this.associatedElement; 56 | } 57 | } 58 | 59 | /// 60 | /// Perform a click action on the label element. 61 | /// 62 | /// The of the operation. 63 | public override ClickResult Click() 64 | { 65 | if (Disabled) 66 | { 67 | return ClickResult.SucceededNoOp; 68 | } 69 | 70 | base.Click(); 71 | 72 | // Click on the associated (For) item or else return success without any operation 73 | return For?.Click() ?? ClickResult.SucceededNoOp; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /SimpleBrowser/Elements/ColorInputElement.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System.Collections.Generic; 11 | using System.Text.RegularExpressions; 12 | using System.Xml.Linq; 13 | 14 | internal class ColorInputElement : InputElement 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The associated with this element. 20 | public ColorInputElement(XElement element) 21 | : base(element) 22 | { 23 | } 24 | 25 | /// 26 | /// Gets or sets the value of the input element value attribute. 27 | /// 28 | public override string Value 29 | { 30 | get 31 | { 32 | XAttribute attribute = this.GetAttribute("value"); 33 | if (attribute == null) 34 | { 35 | return string.Empty; // no value attribute means empty string 36 | } 37 | 38 | return attribute.Value; 39 | } 40 | 41 | set 42 | { 43 | // Don't set the value of a read only or disabled input 44 | if (this.ReadOnly || this.Disabled) 45 | { 46 | return; 47 | } 48 | 49 | if (Regex.Match(value, "^#(?:[0-9a-fA-F]{3}){1,2}$").Success) 50 | { 51 | this.Element.SetAttributeValue("value", value); 52 | } 53 | 54 | return; 55 | } 56 | } 57 | 58 | /// 59 | /// Gets the form values to submit for this input 60 | /// 61 | /// True, if the action to submit the form was clicking this element. Otherwise, false. 62 | /// A collection of objects. 63 | public override IEnumerable ValuesToSubmit(bool isClickedElement, bool validate) 64 | { 65 | if (!string.IsNullOrEmpty(this.Name) && !this.Disabled) 66 | { 67 | yield return new UserVariableEntry() { Name = this.Name, Value = this.Value }; 68 | } 69 | 70 | yield break; 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /SimpleBrowser/Elements/FileUploadElement.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System.Collections.Generic; 11 | using System.IO; 12 | using System.Text; 13 | using System.Xml.Linq; 14 | using SimpleBrowser.Internal; 15 | 16 | /// 17 | /// Implements an HTML file upload element 18 | /// 19 | internal class FileUploadElement : InputElement 20 | { 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// The associated with this element. 25 | public FileUploadElement(XElement element) 26 | : base(element) 27 | { } 28 | 29 | /// 30 | /// Returns the values to send with a form submission for this form element 31 | /// 32 | /// A value indicating whether the clicking of this element caused the form submission. 33 | /// An empty collection of 34 | public override IEnumerable ValuesToSubmit(bool isClickedElement, bool validate) 35 | { 36 | string filename = string.Empty; 37 | string extension = string.Empty; 38 | string contentType = string.Empty; 39 | 40 | if (File.Exists(this.Value)) 41 | { 42 | // Todo: create a mime type for extensions 43 | filename = this.Value; 44 | byte[] allBytes = File.ReadAllBytes(filename); 45 | 46 | FileInfo fileInfo = new FileInfo(filename); 47 | extension = fileInfo.Extension; 48 | filename = fileInfo.Name; 49 | 50 | contentType = string.Format( 51 | "Content-Type: {0}\r\nContent-Transfer-Encoding: binary\r\n\r\n{1}", 52 | ApacheMimeTypes.MimeForExtension(extension), 53 | Encoding.GetEncoding(28591).GetString(allBytes)); 54 | } 55 | else 56 | { 57 | contentType = string.Format( 58 | "Content-Type: {0}\r\n\r\n\r\n", 59 | ApacheMimeTypes.MimeForExtension(extension)); 60 | } 61 | 62 | yield return new UserVariableEntry() { Name = filename, Value = contentType }; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /SimpleBrowser/Query/XQueryException.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // See https://github.com/axefrog/SimpleBrowser/blob/master/readme.md 4 | // 5 | // ----------------------------------------------------------------------- 6 | 7 | namespace SimpleBrowser.Query 8 | { 9 | using System; 10 | using System.Runtime.Serialization; 11 | using System.Security.Permissions; 12 | 13 | /// 14 | /// Implements an XQuery exception 15 | /// 16 | [Serializable] 17 | public class XQueryException : Exception 18 | { 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// A descriptive error message 23 | /// The query causing the exception to be thrown 24 | /// The location of the error in the query 25 | /// The length of the query 26 | public XQueryException(string message, string query, int index, int length) 27 | : base(message) 28 | { 29 | this.Query = query; 30 | if (index >= query.Length) 31 | { 32 | index = query.Length - 1; 33 | } 34 | 35 | this.Index = index; 36 | if (index + length >= query.Length) 37 | { 38 | length = query.Length - index; 39 | } 40 | 41 | this.Length = length; 42 | } 43 | 44 | /// 45 | /// Gets or sets the query causing the exception to be thrown 46 | /// 47 | public string Query { get; set; } 48 | 49 | /// 50 | /// Gets or sets the index of the error in the query 51 | /// 52 | public int Index { get; set; } 53 | 54 | /// 55 | /// Gets or sets the length of the query 56 | /// 57 | public int Length { get; set; } 58 | 59 | /// 60 | /// Populates a SerializationInfo with the data needed to serialize the target object. 61 | /// 62 | /// A to populate/> 63 | /// The of the 64 | [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] 65 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 66 | { 67 | base.GetObjectData(info, context); 68 | 69 | info.AddValue("Query", this.Query); 70 | info.AddValue("Index", this.Index); 71 | info.AddValue("Length", this.Length); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /SimpleBrowser/Elements/UrlInputElement.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Xml.Linq; 13 | 14 | internal class UrlInputElement : InputElement 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The associated with this element. 20 | public UrlInputElement(XElement element) 21 | : base(element) 22 | { 23 | } 24 | 25 | /// 26 | /// Gets the form values to submit for this input 27 | /// 28 | /// True, if the action to submit the form was clicking this element. Otherwise, false. 29 | /// A collection of objects. 30 | public override IEnumerable ValuesToSubmit(bool isClickedElement, bool validate) 31 | { 32 | if (validate) 33 | { 34 | if (this.IsValidUrl(this.Value, base.Required) == false) 35 | { 36 | throw new FormElementValidationException(string.Format("{0} is an invalid URL.", this.Value)); 37 | } 38 | 39 | try 40 | { 41 | // Apply minimum length validation 42 | this.ValidateMinimumLength(); 43 | 44 | // Apply pattern validation 45 | this.ValidatePattern(); 46 | } 47 | catch 48 | { 49 | throw; 50 | } 51 | } 52 | 53 | if (!string.IsNullOrEmpty(this.Name) && !this.Disabled) 54 | { 55 | yield return new UserVariableEntry() { Name = Name, Value = Value }; 56 | } 57 | 58 | yield break; 59 | } 60 | 61 | /// 62 | /// Validates a string as a URL. 63 | /// 64 | /// The URL to validate 65 | /// True if the string is a valid URL. Otherwise, returns false. 66 | private bool IsValidUrl(string url, bool required) 67 | { 68 | if (required == false && string.IsNullOrWhiteSpace(url)) 69 | { 70 | return true; 71 | } 72 | 73 | try 74 | { 75 | new Uri(url); 76 | return true; 77 | } 78 | catch 79 | { 80 | return false; 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /SimpleBrowser/Query/XQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | using System.Xml.Linq; 6 | 7 | namespace SimpleBrowser.Query 8 | { 9 | /* Some simple rules about jQuery: 10 | * 11 | * parsing operates in a context which is a set of included elements 12 | * no elements are selected in a blank query- a selector is required in order to obtain a subset of elements from the context 13 | * a new (raw) context draws from any available node in the tree, unlike an empty context which is the result of selections with no matches 14 | * a filtered context only operates on nodes that are selected within the context (which could be none if selectors have filtered out all nodes) 15 | * a query should always start and end with a selector- a query starting or ending with a shift operator is invalid 16 | * context is cloned for subqueries, as the context may alter during a subquery but need to be reset upon resolution of the subquery e.g. :not(p > b) 17 | 18 | * there are five types of selectors: 19 | * #id - select an element by its 'id' attribute - starts with a hash 20 | * .class - select an element by its 'class' attribute - starts with a period 21 | * tag - selects an element according to its tag name - starts with a letter 22 | * [attribute filter] - selects an element by the existence of, or value of, an attribute with a given name - starts with a square bracket 23 | * :named filter - selects elements that match a defined rule (note that this type of selector can select and include elements outside of the current selection) - starts with a colon 24 | 25 | * shift operators traverse the context - they are: 26 | * > filter context to children of existing selection 27 | * [space] filter context to descendents of existing selection 28 | * + filter context to next adjacent siblings of existing selection 29 | 30 | * usage examples: http://ejohn.org/files/selectors.html 31 | */ 32 | 33 | public static class XQuery 34 | { 35 | internal static IXQuerySelector[] Parse(XQueryParserContext context) 36 | { 37 | var list = new List(); 38 | while(!context.EndOfQuery) 39 | { 40 | var selector = context.MatchNextSelector(); 41 | if(selector == null) 42 | throw new XQueryException("Unexpected character at position " + context.Index + " in query: " + context.Query.Substring(context.Index), context.Query, context.Index, 1); 43 | list.Add(selector); 44 | } 45 | return list.ToArray(); 46 | } 47 | 48 | public static XElement[] Execute(string query, XDocument doc, params XElement[] baseElements) 49 | { 50 | var parserContext = new XQueryParserContext(new SelectorParserCatalog(), query); 51 | var selectors = Parse(parserContext); 52 | var resultsContext = new XQueryResultsContext(doc); 53 | if(baseElements.Length > 0) 54 | resultsContext.ResultSetInternal = baseElements; 55 | if(selectors.Length > 0) 56 | if(selectors.Last().IsTransposeSelector || selectors.First().IsTransposeSelector) 57 | throw new XQueryException("A query may not start or end with a transposal selector (e.g. >)", query, 0, query.Length); 58 | foreach(var selector in selectors) 59 | selector.Execute(resultsContext); 60 | return resultsContext.ResultSet; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /SimpleBrowser/SimpleBrowser.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | SimpleBrowser is a lightweight, yet highly capable browser automation engine designed for automation and testing scenarios. 4 | Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 5 | SimpleBrowser 6 | en-US 7 | 0.6.0 8 | Nathan Ridley and the SimpleBrowser contributors. 9 | netstandard2.1 10 | true 11 | portable 12 | SimpleBrowser 13 | Library 14 | SimpleBrowser 15 | headless browser http cookies browserautomation automation 16 | 17 | https://github.com/SimpleBrowserDotNet/SimpleBrowser 18 | https://opensource.org/licenses/BSD-3-Clause 19 | false 20 | false 21 | false 22 | false 23 | false 24 | false 25 | false 26 | 27 | https://github.com/SimpleBrowserDotNet/SimpleBrowser.git 28 | Git 29 | true 30 | 31 | 32 | 33 | true 34 | 35 | 36 | 37 | 38 | all 39 | runtime; build; native; contentfiles; analyzers; buildtransitive 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | True 49 | True 50 | Resources.resx 51 | 52 | 53 | 54 | 55 | 56 | ResXFileCodeGenerator 57 | Resources.Designer.cs 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /SimpleBrowser/Parser/ElementPositioningRule.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Parser 9 | { 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | using System.Xml.Linq; 14 | 15 | internal abstract class ElementPositioningRule 16 | { 17 | /// 18 | /// The tag name for the element 19 | /// 20 | public abstract string TagName { get; } 21 | 22 | /// 23 | /// If the element is present in the wrong area, it will be removed and appended to the correct area 24 | /// 25 | public abstract DocumentArea Area { get; } 26 | 27 | /// 28 | /// Check the element position in relation to its parent 29 | /// 30 | /// The XElement instance to validate and reposition as needed 31 | public virtual void ValidateAndReposition(XElement element) 32 | { 33 | } 34 | 35 | /// 36 | /// If null, the tag can have both text and non-text children. If true, it can only have text children and if false, it cannot have text children. 37 | /// 38 | public virtual bool? TextChildren { get { return null; } } 39 | 40 | private static Dictionary _rules; 41 | 42 | public static ElementPositioningRule Get(string tagName) 43 | { 44 | lock (typeof(ElementPositioningRule)) 45 | { 46 | if (_rules == null) 47 | { 48 | _rules = new Dictionary(); 49 | foreach (Type type in typeof(ElementPositioningRule).Assembly.GetTypes().Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(ElementPositioningRule)))) 50 | { 51 | ElementPositioningRule rule = (ElementPositioningRule)Activator.CreateInstance(type); 52 | _rules.Add(rule.TagName, rule); 53 | } 54 | } 55 | ElementPositioningRule r; 56 | return _rules.TryGetValue(tagName, out r) ? r : null; 57 | } 58 | } 59 | } 60 | 61 | internal abstract class BodyElementPositioningRule : ElementPositioningRule 62 | { 63 | /// 64 | /// If the element is present in the wrong area, it will be removed and appended to the correct area 65 | /// 66 | public override DocumentArea Area { get { return DocumentArea.Body; } } 67 | } 68 | 69 | internal abstract class HeadElementPositioningRule : ElementPositioningRule 70 | { 71 | /// 72 | /// If the element is present in the wrong area, it will be removed and appended to the correct area 73 | /// 74 | public override DocumentArea Area { get { return DocumentArea.Head; } } 75 | } 76 | 77 | internal enum DocumentArea 78 | { 79 | Body, 80 | Head, 81 | Any 82 | } 83 | } -------------------------------------------------------------------------------- /SimpleBrowser/HttpRequestLog.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser 9 | { 10 | using System; 11 | using System.Collections.Specialized; 12 | using System.Net; 13 | using System.Xml.Linq; 14 | 15 | public abstract class LogItem 16 | { 17 | protected LogItem() 18 | { 19 | this.ServerTime = DateTime.UtcNow; 20 | } 21 | 22 | public DateTime ServerTime { get; set; } 23 | } 24 | 25 | public class HttpRequestLog : LogItem 26 | { 27 | public string Text { get; set; } 28 | public string ParsedHtml { get; set; } 29 | public string Method { get; set; } 30 | public NameValueCollection PostData { get; set; } 31 | public string PostBody { get; set; } 32 | public NameValueCollection QueryStringData { get; set; } 33 | public WebHeaderCollection RequestHeaders { get; set; } 34 | public WebHeaderCollection ResponseHeaders { get; set; } 35 | public int ResponseCode { get; set; } 36 | public Uri Url { get; set; } 37 | public Uri Address { get; set; } 38 | public string Host { get; set; } 39 | 40 | public XDocument ToXml() 41 | { 42 | XDocument doc = new XDocument( 43 | new XElement("HttpRequestLog", 44 | new XAttribute("Date", DateTime.UtcNow.ToString("u")), 45 | new XElement("Url", this.Url), 46 | new XElement("Method", this.Method), 47 | new XElement("ResponseCode", this.ResponseCode), 48 | new XElement("ResponseText", new XCData(this.Text)) 49 | ) 50 | ); 51 | if (this.PostData != null) 52 | { 53 | doc.Root.Add(this.PostData.ToXElement("PostData")); 54 | } 55 | 56 | if (this.RequestHeaders != null) 57 | { 58 | doc.Root.Add(this.RequestHeaders.ToXElement("RequestHeaders")); 59 | } 60 | 61 | if (this.ResponseHeaders != null) 62 | { 63 | doc.Root.Add(this.ResponseHeaders.ToXElement("ResponseHeaders")); 64 | } 65 | 66 | return doc; 67 | } 68 | 69 | public override string ToString() 70 | { 71 | return string.Concat("{", this.Method, " to ", this.Url.ToString().ShortenTo(50, true), "}"); 72 | } 73 | } 74 | 75 | public class LogMessage : LogItem 76 | { 77 | public LogMessage(string message, LogMessageType type = LogMessageType.User) 78 | { 79 | this.Message = message; 80 | this.Type = type; 81 | } 82 | 83 | public string Message { get; set; } 84 | public LogMessageType Type { get; set; } 85 | 86 | public override string ToString() 87 | { 88 | return string.Concat("{", this.Message.ShortenTo(80, true), "}"); 89 | } 90 | } 91 | 92 | public enum LogMessageType 93 | { 94 | User, 95 | Internal, 96 | Error, 97 | StackTrace 98 | } 99 | } -------------------------------------------------------------------------------- /SimpleBrowser/Elements/RadioInputElement.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Xml.Linq; 13 | 14 | /// 15 | /// Implements an input element of type radio. 16 | /// 17 | internal class RadioInputElement : SelectableInputElement 18 | { 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// The associated with this element. 23 | public RadioInputElement(XElement element) 24 | : base(element) 25 | { } 26 | 27 | /// 28 | /// Gets or sets the selected (checked) state of the radio 29 | /// 30 | public override bool Selected 31 | { 32 | get 33 | { 34 | return this.GetAttribute("checked") != null; 35 | } 36 | 37 | set 38 | { 39 | if (this.Disabled) 40 | { 41 | return; 42 | } 43 | 44 | if (value) 45 | { 46 | this.Element.SetAttributeValue("checked", "checked"); 47 | foreach (RadioInputElement other in this.Siblings) 48 | { 49 | if (other.Element != this.Element) 50 | { 51 | other.Selected = false; 52 | } 53 | } 54 | } 55 | else 56 | { 57 | this.Element.RemoveAttributeCI("checked"); 58 | } 59 | } 60 | } 61 | 62 | /// 63 | /// Gets a collection of the other radio buttons in the same group as this radio input. 64 | /// 65 | public IEnumerable Siblings 66 | { 67 | get 68 | { 69 | IEnumerable others = this.Element.Ancestors(XName.Get("form")).Descendants(XName.Get("input")) 70 | .Where(e => e.GetAttributeCI("type") == "radio" && e.GetAttributeCI("name") == this.Name) 71 | .Select(e => this.OwningBrowser.CreateHtmlElement(e)); 72 | return others; 73 | } 74 | } 75 | 76 | /// 77 | /// Perform a click action on the radio input element. 78 | /// 79 | /// The of the operation. 80 | public override ClickResult Click() 81 | { 82 | if (this.Disabled) 83 | { 84 | return ClickResult.SucceededNoOp; 85 | } 86 | 87 | base.Click(); 88 | if (!this.Selected) 89 | { 90 | this.Selected = true; 91 | } 92 | 93 | return ClickResult.SucceededNoNavigation; 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/OfflineTests/FileUri.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.UnitTests.OfflineTests 9 | { 10 | using System; 11 | using System.IO; 12 | using System.Linq; 13 | using NUnit.Framework; 14 | 15 | [TestFixture] 16 | public class FileUri 17 | { 18 | [Test] 19 | public void CanLoadHtmlFromFile() 20 | { 21 | FileInfo f = null; 22 | string uri = string.Empty; 23 | if (Environment.OSVersion.Platform == PlatformID.Win32NT) 24 | { 25 | f = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"SampleDocs\movies1.htm")); 26 | uri = string.Format("file:///{0}", f.FullName); 27 | uri = uri.Replace("\\", "/"); 28 | } 29 | else if (Environment.OSVersion.Platform == PlatformID.Unix) 30 | { 31 | f = new FileInfo(Path.Combine("home", AppDomain.CurrentDomain.BaseDirectory, @"SampleDocs/movies1.htm")); 32 | uri = string.Format("file://{0}", f.FullName); 33 | } 34 | else 35 | { 36 | throw new NotImplementedException("Please write unit tests for this unknown platform. (MacOS?)"); 37 | } 38 | 39 | Browser b = new Browser(); 40 | b.Navigate(uri); 41 | Assert.AreEqual(b.Select("ul#menu>li").Count(), 3, "Not loaded"); 42 | } 43 | 44 | [Test] 45 | public void CanLoadHtmlFromFilesWithAbsolutePath() 46 | { 47 | if (Environment.OSVersion.Platform == PlatformID.Win32NT && 48 | Directory.Exists("C:\\Windows\\Temp")) 49 | { 50 | File.Copy( 51 | Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"SampleDocs\movies1.htm"), 52 | @"C:\Windows\Temp\movies1.htm", true); 53 | 54 | Browser b = new Browser(); 55 | b.Navigate("file:///c:/Windows/Temp/movies1.htm"); 56 | Assert.AreEqual(b.Select("ul#menu>li").Count(), 3); 57 | 58 | b.Navigate("file:///c|/Windows/Temp/movies1.htm"); 59 | Assert.AreEqual(b.Select("ul#menu>li").Count(), 3); 60 | 61 | b.Navigate("file:///c|\\Windows\\Temp\\movies1.htm"); 62 | Assert.AreEqual(b.Select("ul#menu>li").Count(), 3); 63 | 64 | b.Navigate("file://\\c|\\Windows\\Temp\\movies1.htm"); 65 | Assert.AreEqual(b.Select("ul#menu>li").Count(), 3); 66 | 67 | File.Delete(@"C:\Windows\Temp\movies1.htm"); 68 | } 69 | else if (Environment.OSVersion.Platform == PlatformID.Unix && 70 | Directory.Exists("/tmp")) 71 | { 72 | File.Copy( 73 | Path.Combine("home", AppDomain.CurrentDomain.BaseDirectory, @"SampleDocs/movies1.htm"), 74 | @"/tmp/movies1.htm", true); 75 | 76 | Browser b = new Browser(); 77 | b.Navigate("file:///tmp/movies1.htm"); 78 | Assert.AreEqual(b.Select("ul#menu>li").Count(), 3); 79 | 80 | File.Delete(@"/tmp/movies1.htm"); 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /SimpleBrowser/Internal/XmlExtensions.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser 9 | { 10 | using System.Linq; 11 | using System.Xml.Linq; 12 | 13 | // TODO Review 14 | // 1) consider making thing class internal, as it resides in the Internal directory 15 | // --> prefered, though a breaking change 16 | // 2) or if keeping public 17 | // --> consider adding XML comments (documentation) to all public members 18 | 19 | public static class XmlExtensions 20 | { 21 | public static bool HasAttributeCI(this XElement x, string attributeName) 22 | { 23 | return x.Attributes().Where(a => a.Name.LocalName.CaseInsensitiveCompare(attributeName)).FirstOrDefault() != null; 24 | } 25 | 26 | public static string GetAttributeCI(this XElement x, string attributeName) 27 | { 28 | XAttribute attr = x.Attributes().Where(a => a.Name.LocalName.CaseInsensitiveCompare(attributeName)).FirstOrDefault(); 29 | return attr?.Value; 30 | } 31 | 32 | public static string GetAttributeCI(this XElement x, string attributeName, string namespaceUri) 33 | { 34 | XAttribute attr = x.Attributes().Where(a => a.Name.LocalName.CaseInsensitiveCompare(attributeName) && a.Name.NamespaceName == namespaceUri).FirstOrDefault(); 35 | return attr?.Value; 36 | } 37 | 38 | public static string GetAttribute(this XElement x, string attributeName) 39 | { 40 | return x.Attribute(attributeName)?.Value; 41 | } 42 | 43 | public static string GetAttribute(this XElement x, string attributeName, string namespaceUri) 44 | { 45 | XAttribute attr = x.Attributes().Where(a => a.Name.LocalName == attributeName && a.Name.NamespaceName == namespaceUri).FirstOrDefault(); 46 | return attr?.Value; 47 | } 48 | 49 | public static XAttribute RemoveAttributeCI(this XElement x, string attributeName) 50 | { 51 | XAttribute attr = x.Attributes().Where(a => a.Name.LocalName.CaseInsensitiveCompare(attributeName)).FirstOrDefault(); 52 | if (attr != null) 53 | { 54 | attr.Remove(); 55 | } 56 | 57 | return attr; 58 | } 59 | 60 | public static void SetAttributeCI(this XElement x, string attributeName, object value) 61 | { 62 | XAttribute attr = x.Attributes().Where(a => a.Name.LocalName.CaseInsensitiveCompare(attributeName)).FirstOrDefault(); 63 | if (attr != null && value != null) 64 | { 65 | attr.SetValue(value); 66 | } 67 | else if (attr == null) 68 | { 69 | x.SetAttributeValue(attributeName, value); 70 | } 71 | } 72 | 73 | public static XElement GetAncestorCI(this XElement x, string elementName) 74 | { 75 | return x.Ancestors().Where(a => a.Name.LocalName.ToLower() == elementName).FirstOrDefault(); 76 | } 77 | 78 | public static XElement GetAncestorOfSelfCI(this XElement x, string elementName) 79 | { 80 | return x.AncestorsAndSelf().Where(a => a.Name.LocalName.ToLower() == elementName).FirstOrDefault(); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /SimpleBrowser/BasicAuthenticationToken.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser 9 | { 10 | using System; 11 | using System.Text; 12 | 13 | /// 14 | /// A class for basic authentication credentials. 15 | /// 16 | public class BasicAuthenticationToken 17 | { 18 | /// 19 | /// Gets the basic authentication token. 20 | /// 21 | public string Token { get; private set; } 22 | 23 | /// 24 | /// Gets the basic authentication expiration date and time. 25 | /// 26 | public DateTime Expiration { get; private set; } 27 | 28 | /// 29 | /// Gets the domain associated with the token. 30 | /// 31 | public string Domain { get; private set; } 32 | 33 | /// 34 | /// Gets or sets the number of minutes before an inactive token expires. 35 | /// 36 | private static uint _timeout = 15; 37 | 38 | public static uint Timeout 39 | { 40 | get 41 | { 42 | return _timeout; 43 | } 44 | 45 | set 46 | { 47 | int timeout = BasicAuthenticationToken.Timeout > int.MaxValue ? int.MaxValue : (int)value; 48 | _timeout = (uint)timeout; 49 | } 50 | } 51 | 52 | /// 53 | /// Initializes a new instance of the class. 54 | /// 55 | /// The domain of the site being autheticated 56 | /// The user name of the user 57 | /// The password of the user 58 | public BasicAuthenticationToken(string domain, string username, string password) 59 | { 60 | if (string.IsNullOrWhiteSpace(domain)) 61 | { 62 | throw new ArgumentNullException("domain"); 63 | } 64 | 65 | if (string.IsNullOrWhiteSpace(username)) 66 | { 67 | throw new ArgumentNullException("username"); 68 | } 69 | 70 | if (string.IsNullOrWhiteSpace(password)) 71 | { 72 | throw new ArgumentNullException("password"); 73 | } 74 | 75 | this.Domain = domain; 76 | this.Token = Convert.ToBase64String(Encoding.UTF8.GetBytes(username + ":" + password)); 77 | this.UpdateExpiration(); 78 | } 79 | 80 | /// 81 | /// Refreshes the expiration date 82 | /// 83 | /// 84 | /// For example, when created, the token expires after X minutes. Each time the token is used, 85 | /// the expiration is updates to X minutes from the last time it was used. That way, If a user 86 | /// is active on a site for 30 minutes, he/she will not be logged out after the default 15 minutes. 87 | /// This method is called to update the token after it has been used. 88 | /// 89 | public void UpdateExpiration() 90 | { 91 | this.Expiration = DateTime.Now + new TimeSpan(0, (int)BasicAuthenticationToken.Timeout, 0); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/SimpleBrowser.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SimpleBrowser unit tests 5 | Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 6 | SimpleBrowser.UnitTests 7 | en-US 8 | 1.0.0 9 | Nathan Ridley and the SimpleBrowser contributors. 10 | net8.0 11 | true 12 | portable 13 | SimpleBrowser.UnitTests 14 | Library 15 | false 16 | false 17 | false 18 | false 19 | false 20 | false 21 | false 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Always 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /SimpleBrowser/Elements/ImageInputElement.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System.Collections.Generic; 11 | using System.Xml.Linq; 12 | 13 | /// 14 | /// Implements an HTML image input element 15 | /// 16 | internal class ImageInputElement : ButtonInputElement 17 | { 18 | /// 19 | /// The X-coordinate of the location clicked. 20 | /// 21 | private uint x = 0; 22 | 23 | /// 24 | /// The X-coordinate of the location clicked. 25 | /// 26 | private uint y = 0; 27 | 28 | /// 29 | /// Initializes a new instance of the class. 30 | /// 31 | /// The associated with this element. 32 | public ImageInputElement(XElement element) 33 | : base(element) 34 | { } 35 | 36 | /// 37 | /// Gets the form values to submit for this input 38 | /// 39 | /// True, if the action to submit the form was clicking this element. Otherwise, false. 40 | /// A collection of objects. 41 | public override IEnumerable ValuesToSubmit(bool isClickedElement, bool validate) 42 | { 43 | if (isClickedElement && !this.Disabled) 44 | { 45 | if (string.IsNullOrEmpty(this.Name)) 46 | { 47 | yield return new UserVariableEntry() { Name = "x", Value = this.x.ToString() }; 48 | yield return new UserVariableEntry() { Name = "y", Value = this.y.ToString() }; 49 | } 50 | else 51 | { 52 | yield return new UserVariableEntry() { Name = string.Format("{0}.x", this.Name), Value = this.x.ToString() }; 53 | yield return new UserVariableEntry() { Name = string.Format("{0}.y", this.Name), Value = this.y.ToString() }; 54 | if (!string.IsNullOrEmpty(this.Value)) 55 | { 56 | yield return new UserVariableEntry() { Name = Name, Value = Value }; 57 | } 58 | } 59 | } 60 | 61 | yield break; 62 | } 63 | 64 | /// 65 | /// Perform a click action on the label element. 66 | /// 67 | /// The x-coordinate of the location clicked 68 | /// The y-coordinate of the location clicked 69 | /// The of the operation. 70 | public override ClickResult Click(uint x, uint y) 71 | { 72 | if (this.Disabled) 73 | { 74 | return ClickResult.SucceededNoOp; 75 | } 76 | 77 | this.x = x; 78 | this.y = y; 79 | 80 | base.Click(); 81 | if (this.SubmitForm(clickedElement: this)) 82 | { 83 | return ClickResult.SucceededNavigationComplete; 84 | } 85 | 86 | return ClickResult.SucceededNavigationError; 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /SimpleBrowser/Elements/EmailInputElement.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System.Collections.Generic; 11 | using System.Net.Mail; 12 | using System.Xml.Linq; 13 | 14 | internal class EmailInputElement : InputElement 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The associated with this element. 20 | public EmailInputElement(XElement element) 21 | : base(element) 22 | { 23 | } 24 | 25 | /// 26 | /// Gets the form values to submit for this input 27 | /// 28 | /// True, if the action to submit the form was clicking this element. Otherwise, false. 29 | /// A collection of objects. 30 | public override IEnumerable ValuesToSubmit(bool isClickedElement, bool validate) 31 | { 32 | // Is the multiple attribute present? 33 | XAttribute attribute = base.GetAttribute("multiple"); 34 | if (attribute == null) 35 | { 36 | if (validate && this.IsValidEmail(this.Value, base.Required) == false) 37 | { 38 | throw new FormElementValidationException(string.Format("{0} is an invalid e-mail address.", this.Value)); 39 | } 40 | } 41 | else 42 | { 43 | string[] addresses = this.Value.Split(','); 44 | foreach (string address in addresses) 45 | { 46 | if (validate && this.IsValidEmail(address, base.Required) == false) 47 | { 48 | throw new FormElementValidationException(string.Format("{0} is an invalid e-mail address.", address)); 49 | } 50 | } 51 | } 52 | 53 | if (validate) 54 | { 55 | try 56 | { 57 | // Apply minimum length validation 58 | this.ValidateMinimumLength(); 59 | this.ValidatePattern(); 60 | } 61 | catch 62 | { 63 | throw; 64 | } 65 | } 66 | 67 | if (!string.IsNullOrEmpty(this.Name) && !this.Disabled) 68 | { 69 | yield return new UserVariableEntry() { Name = Name, Value = Value }; 70 | } 71 | 72 | yield break; 73 | } 74 | 75 | /// 76 | /// Validates a string as an e-mail address. 77 | /// 78 | /// The e-mail address to validate 79 | /// True if the string is a valid e-mail address. Otherwise, returns false. 80 | private bool IsValidEmail(string email, bool required) 81 | { 82 | if (required == false && string.IsNullOrWhiteSpace(email)) 83 | { 84 | return true; 85 | } 86 | 87 | try 88 | { 89 | MailAddress addr = new MailAddress(email); 90 | return addr.Address == email; 91 | } 92 | catch 93 | { 94 | return false; 95 | } 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/SampleDocs/movies2.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Create 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |

MVC Movie App

15 |
16 |
17 | [ Log On ] 18 |
19 | 26 |
27 |
28 | 29 | 30 |

Create

31 | 32 | 33 | 34 | 35 |
36 | Movie 37 | 38 |
39 | 40 |
41 |
42 | 43 | 44 |
45 | 46 |
47 | 48 |
49 |
50 | 51 | 52 |
53 | 54 |
55 | 56 |
57 |
58 | 59 | 60 |
61 | 62 |
63 | 64 |
65 |
66 | 67 | 68 |
69 |
70 | 71 |
72 |
73 | 74 | 75 |
76 |

77 | 78 |

79 |
80 |
81 |
82 | Back to List 83 |
84 | 85 |
86 |
87 |
88 |
89 | 90 | 91 | -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/OfflineTests/Selectors.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.UnitTests.OfflineTests 9 | { 10 | using System.Linq; 11 | using NUnit.Framework; 12 | 13 | [TestFixture] 14 | public class Selectors 15 | { 16 | [Test] 17 | public void SearchingAnInputElementBySeveralSelectingMethods() 18 | { 19 | Browser b = new Browser(); 20 | b.SetContent(Helper.GetFromResources("SimpleBrowser.UnitTests.SampleDocs.SimpleForm.htm")); 21 | 22 | HtmlResult colorBox = b.Find("colorBox"); // find by id 23 | Assert.That(colorBox.Count() == 1, "There should be exactly 1 element with ID colorBox"); 24 | 25 | colorBox = b.Find("input", new { name = "colorBox", type = "color" }); // find by attributes 26 | Assert.That(colorBox.Count() == 1, "There should be exactly 1 element with name colorBox and type color"); 27 | 28 | colorBox = b.Find("input", new { name = "colorBox", type = "Color" }); // find by attributes 29 | Assert.That(colorBox.Count() == 1, "There should be exactly 1 element with name colorBox and type color"); 30 | 31 | colorBox = b.Find("input", new { name = "colorBox", type = "Colors" }); // find by attributes 32 | Assert.That(colorBox.Exists == false, "There should be no element with name colorBox and type Colors"); 33 | 34 | colorBox = b.Find(ElementType.Checkbox, new { name = "colorBox", type = "Color" }); // find by attributes 35 | Assert.That(colorBox.Count() == 0, "Input elements with types other than the specified type should not be found"); 36 | 37 | colorBox = b.Find("input", FindBy.Name, "colorBox"); // find by FindBy 38 | Assert.That(colorBox.Count() == 1, "There should be exactly 1 element with name colorBox"); 39 | 40 | colorBox = b.Select("input[name=colorBox]"); // find by Css selector 41 | Assert.That(colorBox.Count() == 1, "There should be exactly 1 element with name colorBox"); 42 | 43 | colorBox = b.Select("input[type=color]"); // find by Css selector 44 | Assert.That(colorBox.Count() == 1, "There should be exactly 1 element with type color"); 45 | 46 | colorBox = b.Select("input[type=Color]"); // find by Css selector 47 | Assert.That(colorBox.Count() == 0, "There should be no element for the expression input[type=Color] (CSS is case sensitive)"); 48 | 49 | HtmlResult clickLink = b.Select(".clickLink"); // find by Css selector 50 | Assert.That(clickLink.Count() == 1, "There should be one element for the expression .clickLink"); 51 | } 52 | 53 | [Test] 54 | public void SearchingAnElementBySeveralSelectingMethods() 55 | { 56 | Browser b = new Browser(); 57 | b.SetContent(Helper.GetFromResources("SimpleBrowser.UnitTests.SampleDocs.SimpleForm.htm")); 58 | 59 | HtmlResult colorBox = b.Find("first-checkbox"); // find by id 60 | Assert.That(colorBox.Count() == 1, "There should be exactly 1 element with ID first-checkbox"); 61 | 62 | colorBox = b.Select("*[type=checkbox][checked]"); 63 | Assert.That(colorBox.Count() == 1, "There should be exactly 1 element with type checkbox and checked"); 64 | } 65 | 66 | [Test] 67 | public void UsePlusSelector() 68 | { 69 | Browser b = new Browser(); 70 | b.SetContent(Helper.GetFromResources("SimpleBrowser.UnitTests.SampleDocs.SimpleForm.htm")); 71 | HtmlResult inputDirectlyUnderForm = b.Select("div + input"); 72 | Assert.That(inputDirectlyUnderForm.Count() == 1); // only one comes directly after a div 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /SimpleBrowser.RazorSessionLogger/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace SimpleBrowser.RazorSessionLogger.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SimpleBrowser.RazorSessionLogger.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to @using SimpleBrowser; 65 | ///@using System.Web; 66 | ///@{ 67 | /// bool lastItemIsLogMessage = false; 68 | ///} 69 | ///<!doctype html> 70 | ///<html> 71 | /// <head> 72 | /// <title>@Model.Title</title> 73 | /// <style type="text/css"> 74 | /// body { font-family: Verdana, Sans-Serif; font-size: 11px; margin: 30px; color: #333; } 75 | /// h1, h2, h3, p { margin: 0 0 15px 0; } 76 | /// h1, h2, h3, h4 { font-family: Arial; color: black; } 77 | /// h1 { font-size: 32px; color: #468966; letter-spacing: -1px; } 78 | /// h2 { font-size: 24px; color: #8E2800; letter-spacing: -1px; } 79 | /// h3 { font-size: 18px; } [rest of string was truncated]";. 80 | /// 81 | internal static string HtmlLogTemplate { 82 | get { 83 | return ResourceManager.GetString("HtmlLogTemplate", resourceCulture); 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /SimpleBrowser/Elements/OptionElement.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System; 11 | using System.Linq; 12 | using System.Xml.Linq; 13 | 14 | /// 15 | /// Implements an HTML option element 16 | /// 17 | internal class OptionElement : FormElementElement 18 | { 19 | /// 20 | /// The parent select of this option element. 21 | /// 22 | private SelectElement owner = null; 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The associated with this element. 28 | public OptionElement(XElement element) 29 | : base(element) 30 | { } 31 | 32 | /// 33 | /// Gets the option value of the option element 34 | /// 35 | public string OptionValue 36 | { 37 | get 38 | { 39 | var attr = GetAttribute("value"); 40 | if (attr == null) 41 | { 42 | return Element.Value.Trim(); 43 | } 44 | 45 | return attr.Value.Trim(); 46 | } 47 | } 48 | 49 | /// 50 | /// Gets or sets the value of the option element 51 | /// 52 | public override string Value 53 | { 54 | get 55 | { 56 | return Element.Value.Trim(); 57 | } 58 | 59 | set 60 | { 61 | throw new InvalidOperationException("Cannot change the value for an option element. Set the value attibute."); 62 | } 63 | } 64 | 65 | /// 66 | /// Gets the parent select element of this option element 67 | /// 68 | public SelectElement Owner 69 | { 70 | get 71 | { 72 | if (this.owner == null) 73 | { 74 | var selectElement = Element.Ancestors().First(e => e.Name.LocalName.ToLower() == "select"); 75 | this.owner = OwningBrowser.CreateHtmlElement(selectElement); 76 | } 77 | 78 | return this.owner; 79 | } 80 | } 81 | 82 | /// 83 | /// Gets or sets a value indicating whether this option is selected 84 | /// 85 | public bool Selected 86 | { 87 | get 88 | { 89 | // Being selected is more complicated than it seems. If a selectbox is single-valued, 90 | // the first option is selected when none of the options has a selected-attribute. The 91 | // selected state is therefor managed at the selectbox level 92 | return this.Owner.IsSelected(this); 93 | } 94 | 95 | set 96 | { 97 | this.Owner.MakeSelected(this, value); 98 | } 99 | } 100 | 101 | /// 102 | /// Perform a click action on the option element. 103 | /// 104 | /// The of the operation. 105 | public override ClickResult Click() 106 | { 107 | if (Disabled) 108 | { 109 | return ClickResult.SucceededNoOp; 110 | } 111 | 112 | base.Click(); 113 | Selected = !Selected; 114 | return ClickResult.SucceededNoNavigation; 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/SampleDocs/HTML5Elements.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | Typical 13 |
14 |
15 |
16 |
17 |
18 | Disabled & Read Only 19 |
20 |
21 |
22 |
23 |
24 |
25 | 26 |
30 |
31 |
32 | E-mail 33 | 34 |
35 |
36 | URL 37 | 38 |
39 |
40 | Date Time 41 | 42 |
43 |
44 | Number 45 | 46 |
47 |
48 | Color 49 | 50 |
51 | 52 | 53 |
54 | 55 | 56 |
57 | 58 | 59 |
60 | 61 | 62 | 63 |
64 | 65 | 66 | 67 |
68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /SimpleBrowser/Internal/StringUtil.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser 9 | { 10 | using System; 11 | using System.Collections; 12 | using System.Collections.Generic; 13 | using System.Collections.Specialized; 14 | using System.Web; 15 | 16 | internal static class StringUtil 17 | { 18 | private static readonly Random Randomizer = new Random(Convert.ToInt32(DateTime.UtcNow.Ticks % int.MaxValue)); 19 | 20 | public static string GenerateRandomString(int chars) 21 | { 22 | string s = ""; 23 | for (int i = 0; i < chars; i++) 24 | { 25 | int x = Randomizer.Next(2); 26 | switch (x) 27 | { 28 | case 0: s += (char)(Randomizer.Next(10) + 48); break; // 0-9 29 | case 1: s += (char)(Randomizer.Next(26) + 65); break; // A-Z 30 | case 2: s += (char)(Randomizer.Next(26) + 97); break; // a-z 31 | } 32 | } 33 | return s; 34 | } 35 | 36 | public class LowerCaseComparer : IEqualityComparer 37 | { 38 | public bool Equals(string x, string y) 39 | { 40 | return string.Compare(x, y, true) == 0; 41 | } 42 | 43 | public int GetHashCode(string obj) 44 | { 45 | return obj.ToLower().GetHashCode(); 46 | } 47 | } 48 | 49 | public static string MakeQueryString(IDictionary values) 50 | { 51 | if (values == null) 52 | { 53 | return string.Empty; 54 | } 55 | 56 | List list = new List(); 57 | foreach (object key in values.Keys) 58 | { 59 | list.Add(HttpUtility.UrlEncode(key.ToString()) + "=" + HttpUtility.UrlEncode(values[key].ToString())); 60 | } 61 | 62 | return list.Concat("&"); 63 | } 64 | 65 | public static string MakeQueryString(NameValueCollection values) 66 | { 67 | if (values == null) 68 | { 69 | return string.Empty; 70 | } 71 | 72 | List list = new List(); 73 | foreach (string key in values.Keys) 74 | { 75 | foreach (string value in values.GetValues(key)) 76 | { 77 | list.Add(HttpUtility.UrlEncode(key) + "=" + HttpUtility.UrlEncode(value)); 78 | } 79 | } 80 | return list.Concat("&"); 81 | } 82 | 83 | public static string MakeQueryString(params KeyValuePair[] values) 84 | { 85 | Dictionary v = new Dictionary(); 86 | foreach (KeyValuePair kvp in values) 87 | { 88 | v[kvp.Key] = kvp.Value; 89 | } 90 | 91 | return MakeQueryString(v); 92 | } 93 | 94 | public static NameValueCollection MakeCollectionFromQueryString(string queryString) 95 | { 96 | if (queryString == null) 97 | { 98 | return new NameValueCollection(); 99 | } 100 | 101 | NameValueCollection values = new NameValueCollection(); 102 | foreach (string kvp in queryString.Split(new[] { "&" }, StringSplitOptions.RemoveEmptyEntries)) 103 | { 104 | string[] val = kvp.Split('='); 105 | if (val.Length > 1) 106 | { 107 | values.Add(val[0], val[1]); 108 | } 109 | else if (val.Length == 1) 110 | { 111 | values.Add(val[0], string.Empty); 112 | } 113 | } 114 | return values; 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /SimpleBrowser/Elements/TextAreaElement.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System.Collections.Generic; 11 | using System.Xml.Linq; 12 | 13 | /// 14 | /// Implements a text area HTML element 15 | /// 16 | internal class TextAreaElement : FormElementElement 17 | { 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The associated with this element. 22 | public TextAreaElement(XElement element) 23 | : base(element) 24 | { 25 | } 26 | 27 | /// 28 | /// Gets a value indicating whether the element is readonly. 29 | /// 30 | /// 31 | /// The element is readonly if the element has a readonly attribute set to any value other than empty string. 32 | /// 33 | public bool ReadOnly 34 | { 35 | get 36 | { 37 | return this.GetAttribute("readonly") != null; 38 | } 39 | } 40 | 41 | /// 42 | /// Gets or sets the value of the input element value attribute. 43 | /// 44 | public override string Value 45 | { 46 | get 47 | { 48 | return base.Value; 49 | } 50 | 51 | set 52 | { 53 | // Don't set the value of a read only or disabled text area 54 | if (this.ReadOnly || this.Disabled) 55 | { 56 | return; 57 | } 58 | 59 | int maxLength = int.MaxValue; 60 | if (this.Element.HasAttributeCI("maxlength")) 61 | { 62 | string maxLengthStr = this.Element.GetAttributeCI("maxlength"); 63 | 64 | int parseMaxLength; 65 | if (int.TryParse(maxLengthStr, out parseMaxLength) && parseMaxLength >= 0) 66 | { 67 | maxLength = parseMaxLength; 68 | } 69 | // Do nothing (implicitly) if the value of maxlength is negative, per the HTML5 spec. 70 | } 71 | 72 | this.Element.RemoveNodes(); 73 | 74 | // If the length of the value being assigned is too long, truncate it. 75 | if (value.Length > maxLength) 76 | { 77 | this.Element.AddFirst(value.Substring(0, maxLength)); 78 | } 79 | else 80 | { 81 | this.Element.SetAttributeValue("value", value); 82 | this.Element.AddFirst(value); 83 | } 84 | } 85 | } 86 | 87 | /// 88 | /// Gets the form values to submit for this input 89 | /// 90 | /// True, if the action to submit the form was clicking this element. Otherwise, false. 91 | /// A collection of objects. 92 | public override IEnumerable ValuesToSubmit(bool isClickedElement, bool validate) 93 | { 94 | if (!string.IsNullOrEmpty(this.Name) && !this.Disabled) 95 | { 96 | if (validate) 97 | { 98 | try 99 | { 100 | this.ValidateMinimumLength(); 101 | } 102 | catch 103 | { 104 | throw; 105 | } 106 | } 107 | 108 | yield return new UserVariableEntry() { Name = Name, Value = Value }; 109 | 110 | XAttribute dirNameAttribute = this.GetAttribute("dirname"); 111 | if (dirNameAttribute != null) 112 | { 113 | yield return new UserVariableEntry() { Name = dirNameAttribute.Value, Value = this.OwningBrowser.Culture.TextInfo.IsRightToLeft ? "rtl" : "ltr" }; 114 | } 115 | } 116 | 117 | yield break; 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | SimpleBrowser 2 | ============= 3 | SimpleBrowser is a lightweight, yet highly capable browser automation engine designed for automation and testing scenarios. 4 | It provides an intuitive API that makes it simple to quickly extract specific elements of a page using a variety of matching 5 | techniques, and then interact with those elements with methods such as `Click()`, `SubmitForm()` and many more. SimpleBrowser 6 | does not support JavaScript, but allows for manual manipulation of the user agent, referrer, request headers, form values and 7 | other values before submission or navigation. 8 | 9 | Requirements 10 | ------------ 11 | * .NET Standard 2.0 compatible runtime 12 | 13 | Features 14 | -------- 15 | * Multiple ways of locating and interacting with page elements. The browser session is completely scriptable. 16 | * A highly permissive HTML parser that converts any HTML, no matter how badly formed, to a valid XDocument object 17 | * Automatic cookie/session management 18 | * Extensive logging support with attractive and comprehensive html log file output to make it easy to identify problems loading and automating browsing sessions 19 | 20 | Example 21 | ------- 22 | 23 | ``` c# 24 | class Program 25 | { 26 | static void Main(string[] args) 27 | { 28 | var browser = new Browser(); 29 | try 30 | { 31 | // log the browser request/response data to files so we can interrogate them in case of an issue with our scraping 32 | browser.RequestLogged += OnBrowserRequestLogged; 33 | browser.MessageLogged += new Action(OnBrowserMessageLogged); 34 | 35 | // we'll fake the user agent for websites that alter their content for unrecognised browsers 36 | browser.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.224 Safari/534.10"; 37 | 38 | // browse to GitHub 39 | browser.Navigate("http://github.com/"); 40 | if(LastRequestFailed(browser)) return; // always check the last request in case the page failed to load 41 | 42 | // click the login link and click it 43 | browser.Log("First we need to log in, so browse to the login page, fill in the login details and submit the form."); 44 | var loginLink = browser.Find("a", FindBy.Text, "Login"); 45 | if(!loginLink.Exists) 46 | browser.Log("Can't find the login link! Perhaps the site is down for maintenance?"); 47 | else 48 | { 49 | loginLink.Click(); 50 | if(LastRequestFailed(browser)) return; 51 | 52 | // fill in the form and click the login button - the fields are easy to locate because they have ID attributes 53 | browser.Find("login_field").Value = "youremail@domain.com"; 54 | browser.Find("password").Value = "yourpassword"; 55 | browser.Find(ElementType.Button, "name", "commit").Click(); 56 | if(LastRequestFailed(browser)) return; 57 | 58 | // see if the login succeeded - ContainsText() is very forgiving, so don't worry about whitespace, casing, html tags separating the text, etc. 59 | if(browser.ContainsText("Incorrect login or password")) 60 | { 61 | browser.Log("Login failed!", LogMessageType.Error); 62 | } 63 | else 64 | { 65 | // After logging in, we should check that the page contains elements that we recognise 66 | if(!browser.ContainsText("Your Repositories")) 67 | browser.Log("There wasn't the usual login failure message, but the text we normally expect isn't present on the page"); 68 | else 69 | { 70 | browser.Log("Your News Feed:"); 71 | // we can use simple jquery selectors, though advanced selectors are yet to be implemented 72 | foreach(var item in browser.Select("div.news .title")) 73 | browser.Log("* " + item.Value); 74 | } 75 | } 76 | } 77 | } 78 | catch(Exception ex) 79 | { 80 | browser.Log(ex.Message, LogMessageType.Error); 81 | browser.Log(ex.StackTrace, LogMessageType.StackTrace); 82 | } 83 | finally 84 | { 85 | var path = WriteFile("log-" + DateTime.UtcNow.Ticks + ".html", browser.RenderHtmlLogFile("SimpleBrowser Sample - Request Log")); 86 | Process.Start(path); 87 | } 88 | } 89 | 90 | static bool LastRequestFailed(Browser browser) 91 | { 92 | if(browser.LastWebException != null) 93 | { 94 | browser.Log("There was an error loading the page: " + browser.LastWebException.Message); 95 | return true; 96 | } 97 | return false; 98 | } 99 | 100 | static void OnBrowserMessageLogged(Browser browser, string log) 101 | { 102 | Console.WriteLine(log); 103 | } 104 | 105 | static void OnBrowserRequestLogged(Browser req, HttpRequestLog log) 106 | { 107 | Console.WriteLine(" -> " + log.Method + " request to " + log.Url); 108 | Console.WriteLine(" <- Response status code: " + log.ResponseCode); 109 | } 110 | 111 | static string WriteFile(string filename, string text) 112 | { 113 | var dir = new DirectoryInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs")); 114 | if(!dir.Exists) dir.Create(); 115 | var path = Path.Combine(dir.FullName, filename); 116 | File.WriteAllText(path, text); 117 | return path; 118 | } 119 | } 120 | ``` 121 | -------------------------------------------------------------------------------- /SimpleBrowser.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34316.72 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleBrowser", "SimpleBrowser\SimpleBrowser.csproj", "{041EB5E9-DE14-41BA-B59D-F77612578CD6}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "Sample\Sample.csproj", "{5A003D0D-9F0F-470E-A75C-C61033008A96}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleBrowser.UnitTests", "SimpleBrowser.UnitTests\SimpleBrowser.UnitTests.csproj", "{C0A2F5F6-088D-44E8-BBE3-400A8F84C0D3}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleBrowser.RazorSessionLogger", "SimpleBrowser.RazorSessionLogger\SimpleBrowser.RazorSessionLogger.csproj", "{7C433A0D-DB7E-4296-954E-4643ADA88C00}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Debug|Mixed Platforms = Debug|Mixed Platforms 18 | Debug|x86 = Debug|x86 19 | Release|Any CPU = Release|Any CPU 20 | Release|Mixed Platforms = Release|Mixed Platforms 21 | Release|x86 = Release|x86 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {041EB5E9-DE14-41BA-B59D-F77612578CD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {041EB5E9-DE14-41BA-B59D-F77612578CD6}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {041EB5E9-DE14-41BA-B59D-F77612578CD6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 27 | {041EB5E9-DE14-41BA-B59D-F77612578CD6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 28 | {041EB5E9-DE14-41BA-B59D-F77612578CD6}.Debug|x86.ActiveCfg = Debug|Any CPU 29 | {041EB5E9-DE14-41BA-B59D-F77612578CD6}.Debug|x86.Build.0 = Debug|Any CPU 30 | {041EB5E9-DE14-41BA-B59D-F77612578CD6}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {041EB5E9-DE14-41BA-B59D-F77612578CD6}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {041EB5E9-DE14-41BA-B59D-F77612578CD6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 33 | {041EB5E9-DE14-41BA-B59D-F77612578CD6}.Release|Mixed Platforms.Build.0 = Release|Any CPU 34 | {041EB5E9-DE14-41BA-B59D-F77612578CD6}.Release|x86.ActiveCfg = Release|Any CPU 35 | {041EB5E9-DE14-41BA-B59D-F77612578CD6}.Release|x86.Build.0 = Release|Any CPU 36 | {5A003D0D-9F0F-470E-A75C-C61033008A96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {5A003D0D-9F0F-470E-A75C-C61033008A96}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {5A003D0D-9F0F-470E-A75C-C61033008A96}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 39 | {5A003D0D-9F0F-470E-A75C-C61033008A96}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 40 | {5A003D0D-9F0F-470E-A75C-C61033008A96}.Debug|x86.ActiveCfg = Debug|Any CPU 41 | {5A003D0D-9F0F-470E-A75C-C61033008A96}.Debug|x86.Build.0 = Debug|Any CPU 42 | {5A003D0D-9F0F-470E-A75C-C61033008A96}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {5A003D0D-9F0F-470E-A75C-C61033008A96}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {5A003D0D-9F0F-470E-A75C-C61033008A96}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 45 | {5A003D0D-9F0F-470E-A75C-C61033008A96}.Release|Mixed Platforms.Build.0 = Release|Any CPU 46 | {5A003D0D-9F0F-470E-A75C-C61033008A96}.Release|x86.ActiveCfg = Release|Any CPU 47 | {5A003D0D-9F0F-470E-A75C-C61033008A96}.Release|x86.Build.0 = Release|Any CPU 48 | {C0A2F5F6-088D-44E8-BBE3-400A8F84C0D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {C0A2F5F6-088D-44E8-BBE3-400A8F84C0D3}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {C0A2F5F6-088D-44E8-BBE3-400A8F84C0D3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 51 | {C0A2F5F6-088D-44E8-BBE3-400A8F84C0D3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 52 | {C0A2F5F6-088D-44E8-BBE3-400A8F84C0D3}.Debug|x86.ActiveCfg = Debug|Any CPU 53 | {C0A2F5F6-088D-44E8-BBE3-400A8F84C0D3}.Debug|x86.Build.0 = Debug|Any CPU 54 | {C0A2F5F6-088D-44E8-BBE3-400A8F84C0D3}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {C0A2F5F6-088D-44E8-BBE3-400A8F84C0D3}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {C0A2F5F6-088D-44E8-BBE3-400A8F84C0D3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 57 | {C0A2F5F6-088D-44E8-BBE3-400A8F84C0D3}.Release|Mixed Platforms.Build.0 = Release|Any CPU 58 | {C0A2F5F6-088D-44E8-BBE3-400A8F84C0D3}.Release|x86.ActiveCfg = Release|Any CPU 59 | {C0A2F5F6-088D-44E8-BBE3-400A8F84C0D3}.Release|x86.Build.0 = Release|Any CPU 60 | {7C433A0D-DB7E-4296-954E-4643ADA88C00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {7C433A0D-DB7E-4296-954E-4643ADA88C00}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {7C433A0D-DB7E-4296-954E-4643ADA88C00}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 63 | {7C433A0D-DB7E-4296-954E-4643ADA88C00}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 64 | {7C433A0D-DB7E-4296-954E-4643ADA88C00}.Debug|x86.ActiveCfg = Debug|Any CPU 65 | {7C433A0D-DB7E-4296-954E-4643ADA88C00}.Debug|x86.Build.0 = Debug|Any CPU 66 | {7C433A0D-DB7E-4296-954E-4643ADA88C00}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {7C433A0D-DB7E-4296-954E-4643ADA88C00}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {7C433A0D-DB7E-4296-954E-4643ADA88C00}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 69 | {7C433A0D-DB7E-4296-954E-4643ADA88C00}.Release|Mixed Platforms.Build.0 = Release|Any CPU 70 | {7C433A0D-DB7E-4296-954E-4643ADA88C00}.Release|x86.ActiveCfg = Release|Any CPU 71 | {7C433A0D-DB7E-4296-954E-4643ADA88C00}.Release|x86.Build.0 = Release|Any CPU 72 | EndGlobalSection 73 | GlobalSection(SolutionProperties) = preSolution 74 | HideSolutionNode = FALSE 75 | EndGlobalSection 76 | GlobalSection(ExtensibilityGlobals) = postSolution 77 | SolutionGuid = {ACD5B0D0-2747-4E72-85BB-F5B386289A84} 78 | EndGlobalSection 79 | EndGlobal 80 | -------------------------------------------------------------------------------- /SimpleBrowser/Elements/AnchorElement.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System.Text.RegularExpressions; 11 | using System.Xml.Linq; 12 | 13 | /// 14 | /// Implements an anchor (hyperlink) HTML element 15 | /// 16 | internal class AnchorElement : HtmlElement 17 | { 18 | /// 19 | /// A regular expression used to recognize JavaScript post back URLs. 20 | /// 21 | private static Regex postbackRecognizer = new Regex(@"javascript\:__doPostBack\('([^\']*)\'", RegexOptions.Compiled); 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | /// The associated with this element. 27 | public AnchorElement(XElement element) 28 | : base(element) 29 | { 30 | } 31 | 32 | /// 33 | /// Gets the value of the $href$ attribute 34 | /// 35 | public string Href 36 | { 37 | get 38 | { 39 | return Element.GetAttributeCI("href"); 40 | } 41 | } 42 | 43 | /// 44 | /// Gets the value of the target attribute 45 | /// 46 | public string Target 47 | { 48 | get 49 | { 50 | return Element.GetAttributeCI("target"); 51 | } 52 | } 53 | 54 | /// 55 | /// Gets the value of the $rel$ attribute 56 | /// 57 | public string Rel 58 | { 59 | get 60 | { 61 | return Element.GetAttributeCI("rel"); 62 | } 63 | } 64 | 65 | /// 66 | /// Perform a click action on the anchor element. 67 | /// 68 | /// The of the operation. 69 | public override ClickResult Click() 70 | { 71 | base.Click(); 72 | var match = postbackRecognizer.Match(Href); 73 | if (match.Success) 74 | { 75 | var name = match.Groups[1].Value; 76 | var eventTarget = OwningBrowser.Select("input[name=__EVENTTARGET]"); 77 | 78 | // IIS does browser sniffing. If using the default SimpleBrowser user agent string, 79 | // IIS will not render the hidden __EVENTTARGET input. If, for whatever reason, 80 | // the __EVENTTARGET input is not present, create it. 81 | if (!eventTarget.Exists) 82 | { 83 | var elt = new XElement("input"); 84 | elt.SetAttributeCI("type", "hidden"); 85 | elt.SetAttributeCI("name", "__EVENTTARGET"); 86 | elt.SetAttributeCI("id", "__EVENTTARGET"); 87 | elt.SetAttributeCI("value", name); 88 | 89 | XElement.AddBeforeSelf(elt); 90 | eventTarget = OwningBrowser.Select("input[name=__EVENTTARGET]"); 91 | } 92 | 93 | if (!eventTarget.Exists) 94 | { 95 | // If the element is still not found abort. 96 | return ClickResult.Failed; 97 | } 98 | 99 | eventTarget.Value = name; 100 | 101 | if (SubmitForm()) 102 | { 103 | return ClickResult.SucceededNavigationComplete; 104 | } 105 | else 106 | { 107 | return ClickResult.Failed; 108 | } 109 | } 110 | 111 | string url = Href; 112 | string target = Target; 113 | string queryStringValues = null; 114 | 115 | if ((OwningBrowser.KeyState & (KeyStateOption.Ctrl | KeyStateOption.Shift)) != KeyStateOption.None) 116 | { 117 | target = Browser.TARGET_BLANK; 118 | } 119 | 120 | if (url != null) 121 | { 122 | string[] querystring = url.Split(new[] { '?' }); 123 | if (querystring.Length > 1) 124 | { 125 | queryStringValues = querystring[1]; 126 | } 127 | } 128 | 129 | var navArgs = new NavigationArgs() 130 | { 131 | Uri = url, 132 | Target = target, 133 | UserVariables = StringUtil.MakeCollectionFromQueryString(queryStringValues) 134 | }; 135 | 136 | if (Rel == "noreferrer") 137 | { 138 | navArgs.NavigationAttributes.Add("rel", "noreferrer"); 139 | } 140 | 141 | if (RequestNavigation(navArgs)) 142 | { 143 | return ClickResult.SucceededNavigationComplete; 144 | } 145 | else 146 | { 147 | return ClickResult.SucceededNavigationError; 148 | } 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /SimpleBrowser/Internal/CollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser 9 | { 10 | using System.Collections.Generic; 11 | using System.Collections.Specialized; 12 | using System.Linq; 13 | using System.Text; 14 | using System.Xml.Linq; 15 | 16 | internal static class CollectionExtensions 17 | { 18 | public static string Concat(this IEnumerable values, string delimiter) 19 | { 20 | StringBuilder sb = new StringBuilder(); 21 | int c = 0; 22 | if (values == null) 23 | { 24 | values = new T[0]; 25 | } 26 | 27 | foreach (T k in values) 28 | { 29 | if (c++ > 0) 30 | { 31 | sb.Append(delimiter); 32 | } 33 | 34 | sb.Append(k); 35 | } 36 | return sb.ToString(); 37 | } 38 | 39 | public delegate string StringEncodeHandler(T input); 40 | 41 | public static string Concat(this IEnumerable values, StringEncodeHandler encodeValue) 42 | { 43 | return values.Concat("", encodeValue); 44 | } 45 | 46 | public static string Concat(this IEnumerable values, string delimiter, StringEncodeHandler encodeValue) 47 | { 48 | StringBuilder sb = new StringBuilder(); 49 | int c = 0; 50 | if (values == null) 51 | { 52 | values = new T[0]; 53 | } 54 | 55 | foreach (T k in values) 56 | { 57 | if (c++ > 0) 58 | { 59 | sb.Append(delimiter); 60 | } 61 | 62 | sb.Append(encodeValue(k)); 63 | } 64 | return sb.ToString(); 65 | } 66 | 67 | public static string FriendlyConcat(this IEnumerable values) 68 | { 69 | return values.FriendlyConcat(h => "" + h); // can't call ToString() on a null value 70 | } 71 | 72 | public static string FriendlyConcat(this IEnumerable values, StringEncodeHandler encodeValue) 73 | { 74 | StringBuilder sb = new StringBuilder(); 75 | int len = values.Count(); 76 | int i = 0; 77 | foreach (T v in values) 78 | { 79 | if (i > 0 && i < len - 1) 80 | { 81 | sb.Append(", "); 82 | } 83 | else if (i == len - 1 && len > 1) 84 | { 85 | sb.Append(" and "); 86 | } 87 | 88 | sb.Append(encodeValue(v)); 89 | i++; 90 | } 91 | return sb.ToString(); 92 | } 93 | 94 | public delegate string StringEncodeHandler(string input); 95 | 96 | public static string Concat(this IList values, string delimiter, StringEncodeHandler encodeValue) 97 | { 98 | if (values.Count == 0) 99 | { 100 | return string.Empty; 101 | } 102 | 103 | StringBuilder sb = new StringBuilder(encodeValue(values[0])); 104 | for (int i = 1; i < values.Count; i++) 105 | { 106 | sb.Append(delimiter).Append(encodeValue(values[i])); 107 | } 108 | 109 | return sb.ToString(); 110 | } 111 | 112 | public static string ToHexString(this byte[] bytes) 113 | { 114 | StringBuilder sb = new StringBuilder(); 115 | foreach (byte b in bytes) 116 | { 117 | string s = b.ToString("X"); 118 | if (s.Length == 1) 119 | { 120 | sb.Append("0"); 121 | } 122 | 123 | sb.Append(s); 124 | } 125 | return sb.ToString(); 126 | } 127 | 128 | public static string ToQueryString(this NameValueCollection nvc) 129 | { 130 | return StringUtil.MakeQueryString(nvc); 131 | } 132 | 133 | public static XElement ToXElement(this NameValueCollection nvc, string name) 134 | { 135 | XElement e = new XElement(name); 136 | foreach (string key in nvc.AllKeys) 137 | { 138 | string[] vals = nvc.GetValues(key); 139 | switch (vals.Length) 140 | { 141 | case 0: 142 | e.Add(new XElement("Value", new XAttribute("Name", key))); 143 | break; 144 | 145 | case 1: 146 | e.Add(new XElement("Value", new XAttribute("Name", key), vals[0])); 147 | break; 148 | 149 | default: 150 | { 151 | foreach (string val in vals) 152 | { 153 | e.Add(new XElement("Value", new XAttribute("Name", key), val)); 154 | } 155 | 156 | break; 157 | } 158 | } 159 | } 160 | return e; 161 | } 162 | } 163 | } -------------------------------------------------------------------------------- /SimpleBrowser.UnitTests/OfflineTests/Namespace.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.UnitTests.OfflineTests 9 | { 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Xml.Linq; 13 | using NUnit.Framework; 14 | 15 | /// 16 | /// A unit test for the html tags with attributes, and specifically namespace attributes. 17 | /// 18 | [TestFixture] 19 | public class Namespace 20 | { 21 | /// 22 | /// The number of valid (not ignored/dropped) namespaces in the html tag of $CommentElements.htm$. 23 | /// 24 | private static int namespaceCount = 9; 25 | 26 | /// 27 | /// Tests that the html element may have attributes and that namespace attributes are parsed correctly. 28 | /// 29 | [Test] 30 | public void HtmlElement_Attributes() 31 | { 32 | Browser b = new Browser(); 33 | b.SetContent(Helper.GetFromResources("SimpleBrowser.UnitTests.SampleDocs.CommentElements.htm")); 34 | HtmlResult result = b.Find("html1"); 35 | 36 | Assert.IsTrue(result.Exists); 37 | Assert.AreEqual(namespaceCount, result.XElement.Attributes().Count()); 38 | 39 | List attributes = new List(); 40 | attributes.AddRange(result.XElement.Attributes()); 41 | 42 | for (int index = 0; index < namespaceCount; index++) 43 | { 44 | XAttribute attribute = attributes[index]; 45 | switch (index) 46 | { 47 | case 0: 48 | { 49 | Assert.AreEqual(attribute.Name.LocalName, "xmlns_"); 50 | Assert.AreEqual(attribute.Value, "http://www.w3.org/1999/xhtml"); 51 | break; 52 | } 53 | 54 | case 1: 55 | { 56 | Assert.IsFalse(attribute.IsNamespaceDeclaration); 57 | Assert.AreEqual(attribute.Name.LocalName, "lang"); 58 | Assert.AreEqual(attribute.Value, "en"); 59 | break; 60 | } 61 | 62 | case 2: 63 | { 64 | Assert.IsFalse(attribute.IsNamespaceDeclaration); 65 | Assert.AreEqual(attribute.Name.LocalName, "xml_lang"); 66 | Assert.AreEqual(attribute.Value, "en"); 67 | break; 68 | } 69 | 70 | case 3: 71 | { 72 | Assert.IsFalse(attribute.IsNamespaceDeclaration); 73 | Assert.AreEqual(attribute.Name.LocalName, "id"); 74 | Assert.AreEqual(attribute.Value, "html1"); 75 | break; 76 | } 77 | 78 | case 4: 79 | { 80 | Assert.IsFalse(attribute.IsNamespaceDeclaration); 81 | Assert.AreEqual(attribute.Name.LocalName, "class"); 82 | Assert.AreEqual(attribute.Value, "cookieBar"); 83 | break; 84 | } 85 | 86 | case 5: 87 | { 88 | Assert.AreEqual(attribute.Name.LocalName, "xmlns_fb"); 89 | Assert.AreEqual(attribute.Value, "http://www.facebook.com/2008/fbml"); 90 | break; 91 | } 92 | 93 | case 6: 94 | { 95 | Assert.AreEqual(attribute.Name.LocalName, "xmlns_xsi"); 96 | Assert.AreEqual(attribute.Value, "http://www.w3.org/2001/XMLSchema-instance"); 97 | break; 98 | } 99 | 100 | case 7: 101 | { 102 | Assert.AreEqual(attribute.Name.LocalName, "xsi_schemalocation"); 103 | Assert.AreEqual(attribute.Value, "http://namespaces.ordnancesurvey.co.uk/cmd/local/v1.1 http://www.ordnancesurvey.co.uk/oswebsite/xml/cmdschema/local/V1.1/CMDFeatures.xsd"); 104 | 105 | XAttribute parent_attribute = result.XElement.Attributes().FirstOrDefault(element => element.Name == "xmlns_xsi"); 106 | Assert.IsNotNull(parent_attribute); 107 | Assert.AreEqual(parent_attribute.Value, "http://www.w3.org/2001/XMLSchema-instance"); 108 | 109 | break; 110 | } 111 | case 8: 112 | { 113 | Assert.IsFalse(attribute.IsNamespaceDeclaration); 114 | Assert.AreEqual(attribute.Name.LocalName, "xsl_badattribute"); 115 | Assert.AreEqual(attribute.Value, "http://www.w3.org"); 116 | Assert.That(attribute.Name.Namespace == ""); 117 | break; 118 | } 119 | } 120 | } 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /SimpleBrowser/Elements/InputElement.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser.Elements 9 | { 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Xml.Linq; 13 | 14 | /// 15 | /// Implements an input of types text, hidden, password, of null (unspecified) type, or an unknown or unimplemented type. 16 | /// 17 | /// Per the HTML specification, any input of unknown or unspecified type is a considered a text input. 18 | internal class InputElement : FormElementElement 19 | { 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The associated with this element. 24 | public InputElement(XElement element) 25 | : base(element) 26 | { 27 | } 28 | 29 | /// 30 | /// Gets a value indicating whether the element is readonly. 31 | /// 32 | /// 33 | /// The element is readonly if the element has a readonly attribute. 34 | /// 35 | public bool ReadOnly 36 | { 37 | get 38 | { 39 | return this.GetAttribute("readonly") != null; 40 | } 41 | } 42 | 43 | /// 44 | /// Gets a value indicating whether the element is required. 45 | /// 46 | /// 47 | /// The element is required if the element has a required attribute. 48 | /// 49 | public bool Required 50 | { 51 | get 52 | { 53 | return this.GetAttribute("required") != null; 54 | } 55 | } 56 | 57 | /// 58 | /// Gets or sets the value of the input element value attribute. 59 | /// 60 | public override string Value 61 | { 62 | get 63 | { 64 | XAttribute attribute = this.GetAttribute("value"); 65 | if (attribute == null) 66 | { 67 | return string.Empty; // no value attribute means empty string 68 | } 69 | 70 | return attribute.Value; 71 | } 72 | 73 | set 74 | { 75 | // Don't set the value of a read only or disabled input 76 | if (this.ReadOnly || this.Disabled) 77 | { 78 | return; 79 | } 80 | 81 | int? maxLength = base.ParseNonNegativeIntegerAttribute("maxlength", int.MaxValue); 82 | 83 | // Apply maximum length validation 84 | // If the length of the value being assigned is too long, truncate it. 85 | if (value.Length > maxLength) 86 | { 87 | this.Element.SetAttributeValue("value", value.Substring(0, maxLength.Value)); 88 | } 89 | else 90 | { 91 | this.Element.SetAttributeValue("value", value); 92 | } 93 | } 94 | } 95 | 96 | /// 97 | /// Gets the value of the input element type attribute. 98 | /// 99 | public string InputType 100 | { 101 | get 102 | { 103 | return this.GetAttributeValue("type"); 104 | } 105 | } 106 | 107 | /// 108 | /// Gets the form values to submit for this input 109 | /// 110 | /// True, if the action to submit the form was clicking this element. Otherwise, false. 111 | /// A collection of objects. 112 | public override IEnumerable ValuesToSubmit(bool isClickedElement, bool validate) 113 | { 114 | if (!string.IsNullOrEmpty(this.Name) && !this.Disabled) 115 | { 116 | if (validate) 117 | { 118 | try 119 | { 120 | this.ValidateMinimumLength(); 121 | this.ValidatePattern(); 122 | } 123 | catch 124 | { 125 | throw; 126 | } 127 | } 128 | 129 | if (this.Name.Equals("_charset_") && string.IsNullOrEmpty(this.Value) && this.InputType.Equals("hidden", StringComparison.OrdinalIgnoreCase)) 130 | { 131 | yield return new UserVariableEntry() { Name = Name, Value = "iso-8859-1" }; 132 | } 133 | else 134 | { 135 | yield return new UserVariableEntry() { Name = Name, Value = Value }; 136 | 137 | XAttribute dirNameAttribute = this.GetAttribute("dirname"); 138 | if (dirNameAttribute != null && this.OwningBrowser.Culture != null && this.OwningBrowser.Culture.TextInfo != null) 139 | { 140 | yield return new UserVariableEntry() { Name = dirNameAttribute.Value, Value = this.OwningBrowser.Culture.TextInfo.IsRightToLeft ? "rtl" : "ltr" }; 141 | } 142 | } 143 | } 144 | 145 | yield break; 146 | } 147 | } 148 | } -------------------------------------------------------------------------------- /SimpleBrowser/Internal/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright © 2010 - 2019, Nathan Ridley and the SimpleBrowser contributors. 4 | // See https://github.com/SimpleBrowserDotNet/SimpleBrowser/blob/master/readme.md 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | namespace SimpleBrowser 9 | { 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | 13 | internal static class StringExtensions 14 | { 15 | public static bool MatchesAny(this string source, params string[] comparisons) 16 | { 17 | return comparisons.Any(s => s == source); 18 | } 19 | 20 | public static bool CaseInsensitiveCompare(this string str1, string str2) 21 | { 22 | return string.Compare(str1, str2, true) == 0; 23 | } 24 | 25 | public static bool ToBool(this string value) 26 | { 27 | if (value == null) 28 | { 29 | return false; 30 | } 31 | 32 | return !MatchesAny(value.ToLower(), "", "no", "false", "off", "0", null); 33 | } 34 | 35 | public static int ToInt(this string s) 36 | { 37 | int n; 38 | if (!int.TryParse(s, out n)) 39 | { 40 | return 0; 41 | } 42 | 43 | return n; 44 | } 45 | 46 | public static long ToLong(this string s) 47 | { 48 | long n; 49 | if (!long.TryParse(s, out n)) 50 | { 51 | return 0; 52 | } 53 | 54 | return n; 55 | } 56 | 57 | public static double ToDouble(this string s) 58 | { 59 | double n; 60 | if (!double.TryParse(s, out n)) 61 | { 62 | return 0; 63 | } 64 | 65 | return n; 66 | } 67 | 68 | public static double ToDecimal(this string s) 69 | { 70 | double n; 71 | if (!double.TryParse(s, out n)) 72 | { 73 | return 0; 74 | } 75 | 76 | return n; 77 | } 78 | 79 | public static string ShortenTo(this string str, int length) 80 | { 81 | return ShortenTo(str, length, false); 82 | } 83 | 84 | public static string ShortenTo(this string str, int length, bool ellipsis) 85 | { 86 | if (str.Length > length) 87 | { 88 | str = str.Substring(0, length); 89 | if (ellipsis) 90 | { 91 | str += "…"; 92 | } 93 | } 94 | return str; 95 | } 96 | 97 | public static List Split(this string delimitedList, char delimiter, bool trimValues, bool stripDuplicates, bool caseSensitiveDuplicateMatch) 98 | { 99 | if (delimitedList == null) 100 | { 101 | return new List(); 102 | } 103 | 104 | StringUtil.LowerCaseComparer lcc = new StringUtil.LowerCaseComparer(); 105 | List list = new List(); 106 | string[] arr = delimitedList.Split(delimiter); 107 | for (int i = 0; i < arr.Length; i++) 108 | { 109 | string val = trimValues ? arr[i].Trim() : arr[i]; 110 | if (val.Length > 0) 111 | { 112 | if (stripDuplicates) 113 | { 114 | if (caseSensitiveDuplicateMatch) 115 | { 116 | if (!list.Contains(val)) 117 | { 118 | list.Add(val); 119 | } 120 | } 121 | else if (!list.Contains(val, lcc)) 122 | { 123 | list.Add(val); 124 | } 125 | } 126 | else 127 | { 128 | list.Add(val); 129 | } 130 | } 131 | } 132 | return list; 133 | } 134 | 135 | public static List SplitLines(this string listWithOnePerLine, bool trimValues, bool stripDuplicates, bool caseSensitiveDuplicateMatch) 136 | { 137 | if (listWithOnePerLine == null) 138 | { 139 | return new List(); 140 | } 141 | 142 | StringUtil.LowerCaseComparer lcc = new StringUtil.LowerCaseComparer(); 143 | List list = new List(); 144 | using (System.IO.StringReader reader = new System.IO.StringReader(listWithOnePerLine)) 145 | { 146 | string val = reader.ReadLine(); 147 | while (val != null) 148 | { 149 | if (trimValues) 150 | { 151 | val = val.Trim(); 152 | } 153 | 154 | if (val.Length > 0 || !trimValues) 155 | { 156 | if (stripDuplicates) 157 | { 158 | if (caseSensitiveDuplicateMatch) 159 | { 160 | if (!list.Contains(val)) 161 | { 162 | list.Add(val); 163 | } 164 | } 165 | else if (!list.Contains(val, lcc)) 166 | { 167 | list.Add(val); 168 | } 169 | } 170 | else 171 | { 172 | list.Add(val); 173 | } 174 | } 175 | val = reader.ReadLine(); 176 | } 177 | } 178 | return list; 179 | } 180 | } 181 | } -------------------------------------------------------------------------------- /SimpleBrowser.RazorSessionLogger/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Resources\HtmlLogTemplate.cshtml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 123 | 124 | --------------------------------------------------------------------------------