├── 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 | 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 |
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 |
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 |
8 |
9 | 0
10 | 1
11 | 2
12 | 3
13 |
14 | A
15 | B
16 | C
17 |
18 |
19 | 4
20 | 5
21 |
22 |
23 | V
24 | W
25 | X
26 | Y
27 | Z
28 |
29 |
30 |
31 |
32 | first
33 | second
34 |
35 |
36 |
37 | first
38 | second
39 |
40 |
41 |
42 | This is a full text part
43 | with several line
44 | breaks in it.
45 |
46 |
47 |
48 | opt1
49 | opt2
50 | opt3
51 | opt4
52 |
53 |
54 | opt5
55 | opt6
56 | opt7
57 | opt8
58 |
59 |
60 |
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 |
19 |
20 |
25 |
26 |
27 |
86 |
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 | Text Input
14 | Text Area Input Text Area Input
15 | Checkbox Input
16 |
17 |
18 | Disabled & Read Only
19 | Read only text
20 | Disabled text
21 | Read only text area readme textarea
22 | Disabled text area disableme textarea
23 | Disabled check box
24 | Disabled radio
25 | Disabled select
26 |
27 | 1
28 | 2
29 |
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 |
--------------------------------------------------------------------------------