Skip to content

Commit

Permalink
added puppeteer.
Browse files Browse the repository at this point in the history
  • Loading branch information
Dummy committed Feb 5, 2019
1 parent 4c93f02 commit 404dfd5
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 44 deletions.
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
# FluffySpoon.Automation
A set of automation tools.
An abstraction that makes Selenium or Puppeteer testing fun, stable and fast.

## Selectors
A selector determines how to select elements. Right now, only jQuery selectors are supported.

### jQuery
```
install-package FluffySpoon.Automation.JQuery
```

## Automation frameworks
An automation framework decides how the automation is done. Can use either Selenium or Puppeteer currently.

### Selenium
```
install-package FluffySpoon.Automation.Selenium
```

### Puppeteer
```
install-package FluffySpoon.Automation.Puppeteer
```

## Example
The following test searches for something in Google and asserts that the results are present in both Chrome and Firefox.
The following test searches for something in Google and asserts that the results are present in both Chrome, Edge and Firefox on Selenium, and Chromium on Puppeteer.

```csharp
var serviceCollection = new ServiceCollection();
serviceCollection.UseJQueryDomSelector();

//use 3 different browsers via Selenium
serviceCollection.AddSeleniumWebAutomationFrameworkInstance(GetEdgeDriver);
serviceCollection.AddSeleniumWebAutomationFrameworkInstance(GetFirefoxDriver);
serviceCollection.AddSeleniumWebAutomationFrameworkInstance(GetChromeDriver);

//also use Chromium via Puppeteer
serviceCollection.AddPuppeteerWebAutomationFrameworkInstance(GetPuppeteerDriverAsync);

var serviceProvider = serviceCollection.BuildServiceProvider();

using (var automationEngine = serviceProvider.GetRequiredService<IWebAutomationEngine>())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluffySpoon.Http" Version="1.0.9" />
<PackageReference Include="FluffySpoon.Http" Version="1.0.21" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public string GetJavaScriptForRetrievingDomElements(string selector)

public async Task InitializeAsync()
{
var jQueryScriptContents = await _webClient.GetAsync(new Uri("https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"));
var jQueryScriptContents = await _webClient.GetAsync<string>(new Uri("https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"));
DomSelectorLibraryJavaScript = _uniqueJQueryInstanceReference + @"=(function() {" + jQueryScriptContents + @"})()||jQuery.noConflict()";
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using FluffySpoon.Automation.Web.Dom;
using Microsoft.Extensions.DependencyInjection;
using PuppeteerSharp;

Expand All @@ -11,7 +12,8 @@ public static void AddPuppeteerWebAutomationFrameworkInstance(this ServiceCollec
{
RegistrationExtensions.AddWebAutomationFrameworkInstance(provider =>
new PuppeteerWebAutomationFrameworkInstance(
driverConstructor));
driverConstructor,
provider.GetRequiredService<IDomTunnel>()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ public async Task ClickAsync(IReadOnlyList<IDomElement> elements, int offsetX, i
foreach (var element in elements)
{
await _page.Mouse.ClickAsync(
element.ClientLeft + offsetX,
element.ClientTop + offsetY);
(int)Math.Ceiling(element.BoundingClientRectangle.Left) + offsetX,
(int)Math.Ceiling(element.BoundingClientRectangle.Top) + offsetY);
}
}

Expand All @@ -60,8 +60,8 @@ public async Task DoubleClickAsync(IReadOnlyList<IDomElement> elements, int offs
foreach (var element in elements)
{
await _page.Mouse.ClickAsync(
element.ClientLeft + offsetX,
element.ClientTop + offsetY,
(int)Math.Ceiling(element.BoundingClientRectangle.Left) + offsetX,
(int)Math.Ceiling(element.BoundingClientRectangle.Top) + offsetY,
new ClickOptions() {
ClickCount = 2
});
Expand All @@ -71,21 +71,22 @@ await _page.Mouse.ClickAsync(
public async Task DragDropAsync(IDomElement from, int fromOffsetX, int fromOffsetY, IDomElement to, int toOffsetX, int toOffsetY)
{
await _page.Mouse.MoveAsync(
from.ClientLeft + fromOffsetX,
from.ClientTop + fromOffsetY);
(int)Math.Ceiling(from.BoundingClientRectangle.Left) + fromOffsetX,
(int)Math.Ceiling(from.BoundingClientRectangle.Top) + fromOffsetY);
await _page.Mouse.DownAsync();

await _page.Mouse.MoveAsync(
to.ClientLeft + toOffsetX,
to.ClientTop + toOffsetY);
(int)Math.Ceiling(to.BoundingClientRectangle.Left) + toOffsetX,
(int)Math.Ceiling(to.BoundingClientRectangle.Top) + toOffsetY);
await _page.Mouse.UpAsync();
}

public async Task EnterTextInAsync(IReadOnlyList<IDomElement> elements, string text)
{
var handles = await GetElementHandlesFromDomElementsAsync(elements);
foreach(var handle in handles)
foreach(var handle in handles) {
await handle.TypeAsync(text);
}
}

public async Task<string> EvaluateJavaScriptAsync(string code)
Expand Down Expand Up @@ -116,28 +117,31 @@ public async Task HoverAsync(IDomElement domElement, int offsetX, int offsetY)
{
var handle = await GetElementHandleFromDomElementAsync(domElement);
await _page.Mouse.MoveAsync(
domElement.ClientLeft + offsetX,
domElement.ClientTop + offsetY);
(int)Math.Ceiling(domElement.BoundingClientRectangle.Left) + offsetX,
(int)Math.Ceiling(domElement.BoundingClientRectangle.Top) + offsetY);
}

public async Task InitializeAsync()
{
_browser = await _driverConstructor();
_page = await _browser.NewPageAsync();

var pages = await _browser.PagesAsync();
_page = pages.Single();
}

public Task OpenAsync(string uri)
public async Task OpenAsync(string uri)
{
return _page.GoToAsync(uri);
await _page.GoToAsync(uri);
await _page.WaitForExpressionAsync("document.readyState === 'complete'");
}

public async Task RightClickAsync(IReadOnlyList<IDomElement> elements, int offsetX, int offsetY)
{
foreach (var element in elements)
{
await _page.Mouse.ClickAsync(
element.ClientLeft + offsetX,
element.ClientTop + offsetY,
(int)Math.Ceiling(element.BoundingClientRectangle.Left) + offsetX,
(int)Math.Ceiling(element.BoundingClientRectangle.Top) + offsetY,
new ClickOptions() {
Button = MouseButton.Right
});
Expand All @@ -152,29 +156,56 @@ public async Task SelectByIndicesAsync(IReadOnlyList<IDomElement> elements, int[
.Select(x => $"{element.CssSelector} > option:nth-child({x+1})")
.Aggregate(string.Empty, (a, b) => $"{a}, {b}");
var handles = await _page.QuerySelectorAllAsync(selector);
//var values = handles.Select(x => x.)
//await _page.SelectAsync(handle)
var valueTasks = handles.Select(x => _page.EvaluateFunctionAsync("x => x.value", x));
var valueTokens = await Task.WhenAll(valueTasks);
var values = valueTokens.Cast<string>();
await _page.SelectAsync(element.CssSelector, values.ToArray());
}
}

public Task SelectByTextsAsync(IReadOnlyList<IDomElement> elements, string[] byTexts)
public async Task SelectByTextsAsync(IReadOnlyList<IDomElement> elements, string[] byTexts)
{
throw new NotImplementedException();
var trimmedByTexts = byTexts
.Select(x => x.Trim())
.ToArray();
foreach (var element in elements)
{
var selector = byTexts
.Select(x => $"{element.CssSelector} > option")
.Aggregate(string.Empty, (a, b) => $"{a}, {b}");
var handles = await _page.QuerySelectorAllAsync(selector);
var tasks = handles.Select(x => _page.EvaluateFunctionAsync("x => { return { value: x.value, textContent: x.textContent } }", x));
var tokens = await Task.WhenAll(tasks);
var values = tokens
.Select(x => new {
Value = x.Value<string>("value"),
TextContent = x.Value<string>("textContent")
})
.Where(x => trimmedByTexts.Contains(x.TextContent?.Trim()))
.Select(x => x.Value);
await _page.SelectAsync(element.CssSelector, values.ToArray());
}
}

public Task SelectByValuesAsync(IReadOnlyList<IDomElement> elements, string[] byValues)
public async Task SelectByValuesAsync(IReadOnlyList<IDomElement> elements, string[] byValues)
{
throw new NotImplementedException();
var selector = elements
.Select(x => x.CssSelector)
.Aggregate(string.Empty, (a, b) => $"{a}, {b}");
await _page.SelectAsync(selector, byValues);
}

public Task<SKBitmap> TakeScreenshotAsync()
public async Task<SKBitmap> TakeScreenshotAsync()
{
throw new NotImplementedException();
var bytes = await _page.ScreenshotDataAsync();
return SKBitmap.Decode(bytes);
}

public Task<IReadOnlyList<IDomElement>> FindDomElementsByCssSelectorsAsync(int methodChainOffset, string[] selectors)
public async Task<IReadOnlyList<IDomElement>> FindDomElementsByCssSelectorsAsync(int methodChainOffset, string[] selectors)
{
throw new NotImplementedException();
return await FindDomElementsBySelectorAsync(
methodChainOffset,
selectors.Aggregate(string.Empty, (a, b) => $"{a}, {b}"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\FluffySpoon.Automation.Web.JQuery\FluffySpoon.Automation.Web.JQuery.csproj" />
<ProjectReference Include="..\FluffySpoon.Automation.Web.Puppeteer\FluffySpoon.Automation.Web.Puppeteer.csproj" />
<ProjectReference Include="..\FluffySpoon.Automation.Web.Selenium\FluffySpoon.Automation.Web.Selenium.csproj" />
<ProjectReference Include="..\FluffySpoon.Automation.Web\FluffySpoon.Automation.Web.csproj" />
</ItemGroup>
Expand Down
21 changes: 19 additions & 2 deletions src/FluffySpoon.Automation.Web.Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using FluffySpoon.Automation.Web.Selenium;
using OpenQA.Selenium.Edge;
using OpenQA.Selenium.Firefox;
using PuppeteerSharp;

namespace FluffySpoon.Automation.Web.Sample
{
Expand All @@ -18,10 +19,13 @@ static async Task Main(string[] args)
{
var serviceCollection = new ServiceCollection();
serviceCollection.UseJQueryDomSelector();

serviceCollection.AddSeleniumWebAutomationFrameworkInstance(GetFirefoxDriverAsync);
serviceCollection.AddSeleniumWebAutomationFrameworkInstance(GetChromeDriverAsync);
serviceCollection.AddSeleniumWebAutomationFrameworkInstance(GetEdgeDriverAsync);

serviceCollection.AddPuppeteerWebAutomationFrameworkInstance(GetPuppeteerDriverAsync);

var serviceProvider = serviceCollection.BuildServiceProvider();

using (var automationEngine = serviceProvider.GetRequiredService<IWebAutomationEngine>())
Expand All @@ -34,10 +38,10 @@ await automationEngine
await automationEngine
.Enter("this is a very long test that works").In("input[type=text]:visible")
.Wait(until =>
until.Exists("input[type=submit]:visible"));
until.Exists("input[type=submit][name=btnK]:visible"));

var elements = await automationEngine
.Click.On("input[type=submit]:visible:first")
.Click.On("input[type=submit][name=btnK]:visible")
.Wait(until =>
until.Exists("#rso .g:visible"))
.Expect
Expand All @@ -53,6 +57,19 @@ await automationEngine
}
}

private static async Task<Browser> GetPuppeteerDriverAsync()
{
await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision);
return await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = false,
DefaultViewport = new ViewPortOptions() {
Width = 1100,
Height = 500
}
});
}

private static async Task<IWebDriver> GetEdgeDriverAsync()
{
var options = new EdgeOptions() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="selenium.support" Version="3.8.0" />
<PackageReference Include="selenium.webdriver" Version="3.8.0" />
<PackageReference Include="selenium.support" Version="3.141.0" />
<PackageReference Include="selenium.webdriver" Version="3.141.0" />
</ItemGroup>

<ItemGroup>
Expand Down
10 changes: 5 additions & 5 deletions src/FluffySpoon.Automation.Web/Dom/DomTunnel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ public async Task<IReadOnlyList<IDomElement>> GetDomElementsFromJavaScriptCode(
{
var elementFetchJavaScript = WrapJavaScriptInIsolatedFunction(
scriptToExecute);

var resultJsonBlobs = await automationFrameworkInstance.EvaluateJavaScriptAsync(@"
return " + WrapJavaScriptInIsolatedFunction(@"
var resultJsonBlobs = await automationFrameworkInstance.EvaluateJavaScriptAsync(
WrapJavaScriptInIsolatedFunction(@"
var elements = " + elementFetchJavaScript + @";
var returnValues = [];
Expand Down Expand Up @@ -74,7 +74,7 @@ public async Task<IReadOnlyList<IDomElement>> GetDomElementsFromJavaScriptCode(
var boundingClientRectangle = element.getBoundingClientRect();
returnValues.push(JSON.stringify({
returnValues.push({
tag: tag,
attributes: attributes,
computedStyle: computedStyleProperties,
Expand All @@ -90,7 +90,7 @@ public async Task<IReadOnlyList<IDomElement>> GetDomElementsFromJavaScriptCode(
top: boundingClientRectangle.top,
bottom: boundingClientRectangle.bottom
}
}));
});
}
return JSON.stringify(returnValues);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="SkiaSharp" Version="1.59.3" />
<PackageReference Include="SkiaSharp" Version="1.68.0" />
</ItemGroup>

</Project>
4 changes: 3 additions & 1 deletion src/FluffySpoon.Automation.Web/RegistrationExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using FluffySpoon.Automation.Web.Dom;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -23,6 +24,7 @@ public static void AddWebAutomationFrameworkInstance(
public static void UseFluffySpoonAutomationWeb(
this ServiceCollection services)
{
services.AddTransient<IDomTunnel, DomTunnel>();
services.AddTransient<IWebAutomationEngine, WebAutomationEngine>();
services.AddTransient(provider => webAutomationFrameworkInstanceConstructors
.Select(constructor => constructor(provider))
Expand Down

0 comments on commit 404dfd5

Please sign in to comment.