Skip to content

Commit

Permalink
Implement Browser tests (#220)
Browse files Browse the repository at this point in the history
  • Loading branch information
Meir017 authored and kblok committed May 15, 2018
1 parent 396d973 commit e242e76
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 43 deletions.
35 changes: 35 additions & 0 deletions lib/PuppeteerSharp.Tests/BrowserTests/Events/DisconnectedTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Threading.Tasks;
using Xunit;

namespace PuppeteerSharp.Tests.BrowserTests.Events
{
[Collection("PuppeteerLoaderFixture collection")]
public class DisconnectedTests : PuppeteerBaseTest
{
[Fact]
public async Task ShouldEmittedWhenBrowserGetsClosedDisconnectedOrUnderlyingWebsocketGetsClosed()
{
var originalBrowser = await PuppeteerSharp.Puppeteer.LaunchAsync(TestConstants.DefaultBrowserOptions(), TestConstants.ChromiumRevision);
var connectOptions = new ConnectOptions { BrowserWSEndpoint = originalBrowser.WebSocketEndpoint };
var remoteBrowser1 = await PuppeteerSharp.Puppeteer.ConnectAsync(connectOptions);
var remoteBrowser2 = await PuppeteerSharp.Puppeteer.ConnectAsync(connectOptions);

var disconnectedOriginal = 0;
var disconnectedRemote1 = 0;
var disconnectedRemote2 = 0;
originalBrowser.Disconnected += (sender, e) => ++disconnectedOriginal;
remoteBrowser1.Disconnected += (sender, e) => ++disconnectedRemote1;
remoteBrowser2.Disconnected += (sender, e) => ++disconnectedRemote2;

remoteBrowser2.Disconnect();
Assert.Equal(0, disconnectedOriginal);
Assert.Equal(0, disconnectedRemote1);
Assert.Equal(1, disconnectedRemote2);

await originalBrowser.CloseAsync();
Assert.Equal(1, disconnectedOriginal);
Assert.Equal(1, disconnectedRemote1);
Assert.Equal(1, disconnectedRemote2);
}
}
}
21 changes: 21 additions & 0 deletions lib/PuppeteerSharp.Tests/BrowserTests/ProcessTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Threading.Tasks;
using Xunit;

namespace PuppeteerSharp.Tests.BrowserTests
{
[Collection("PuppeteerLoaderFixture collection")]
public class ProcessTests : PuppeteerBaseTest
{
[Fact]
public async Task ShouldReturnProcessInstance()
{
var process = Browser.Process;
Assert.True(process.Id > 0);
var browserWSEndpoint = Browser.WebSocketEndpoint;
var remoteBrowser = await PuppeteerSharp.Puppeteer.ConnectAsync(
new ConnectOptions { BrowserWSEndpoint = browserWSEndpoint });
Assert.Null(remoteBrowser.Process);
remoteBrowser.Disconnect();
}
}
}
17 changes: 17 additions & 0 deletions lib/PuppeteerSharp.Tests/BrowserTests/UserAgentTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Threading.Tasks;
using Xunit;

namespace PuppeteerSharp.Tests.BrowserTests
{
[Collection("PuppeteerLoaderFixture collection")]
public class UserAgentTests : PuppeteerBaseTest
{
[Fact]
public async Task ShouldIncludeWebKit()
{
var userAgent = await Browser.GetUserAgentAsync();
Assert.NotEmpty(userAgent);
Assert.Contains("WebKit", userAgent);
}
}
}
17 changes: 17 additions & 0 deletions lib/PuppeteerSharp.Tests/BrowserTests/VersionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Threading.Tasks;
using Xunit;

namespace PuppeteerSharp.Tests.BrowserTests
{
[Collection("PuppeteerLoaderFixture collection")]
public class VersionTests : PuppeteerBaseTest
{
[Fact]
public async Task ShouldReturnWhetherWeAreInHeadless()
{
var version = await Browser.GetVersionAsync();
Assert.NotEmpty(version);
Assert.Equal(TestConstants.DefaultBrowserOptions().Headless, version.StartsWith("Headless"));
}
}
}
67 changes: 54 additions & 13 deletions lib/PuppeteerSharp/Browser.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.Linq;
using System.Threading.Tasks;
Expand All @@ -9,8 +10,9 @@ namespace PuppeteerSharp
{
public class Browser : IDisposable
{
public Browser(Connection connection, IBrowserOptions options, Func<Task> closeCallBack)
public Browser(Connection connection, IBrowserOptions options, Process process, Func<Task> closeCallBack)
{
Process = process;
Connection = connection;
IgnoreHTTPSErrors = options.IgnoreHTTPSErrors;
AppMode = options.AppMode;
Expand All @@ -25,29 +27,41 @@ public Browser(Connection connection, IBrowserOptions options, Func<Task> closeC

#region Private members
private Dictionary<string, Target> _targets;

#endregion

#region Properties
public Connection Connection { get; set; }

public event EventHandler Closed;
public event EventHandler Disconnected;
public event EventHandler<TargetChangedArgs> TargetChanged;
public event EventHandler<TargetChangedArgs> TargetCreated;
public event EventHandler<TargetChangedArgs> TargetDestroyed;
private event Func<Task> _closeCallBack;

public string WebSocketEndpoint
{
get
{
return Connection.Url;
}
}
/// <summary>
/// Gets the Browser websocket url
/// </summary>
/// <remarks>
/// Browser websocket endpoint which can be used as an argument to <see cref="Puppeteer.ConnectAsync(ConnectOptions)"/>.
/// The format is <c>ws://${host}:${port}/devtools/browser/<id></c>
/// You can find the <c>webSocketDebuggerUrl</c> from <c>http://${host}:${port}/json/version</c>.
/// Learn more about the devtools protocol <see cref="https://chromedevtools.github.io/devtools-protocol">
/// and the browser endpoint <see cref="https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target"/>
/// </remarks>
public string WebSocketEndpoint => Connection.Url;

/// <summary>
/// Gets the spawned browser process. Returns <c>null</c> if the browser instance was created with <see cref="Puppeteer.ConnectAsync(ConnectOptions)"/> method.
/// </summary>
public Process Process { get; }
public bool IgnoreHTTPSErrors { get; set; }
public bool AppMode { get; set; }
internal TaskQueue ScreenshotTaskQueue { get; set; }
public bool IsClosed { get; internal set; }

internal TaskQueue ScreenshotTaskQueue { get; set; }
internal Connection Connection { get; }

#endregion

#region Public Methods
Expand Down Expand Up @@ -91,14 +105,41 @@ internal void ChangeTarget(Target target)
});
}

/// <summary>
/// Gets the browser's version
/// </summary>
/// <returns>For headless Chromium, this is similar to <c>HeadlessChrome/61.0.3153.0</c>. For non-headless, this is similar to <c>Chrome/61.0.3153.0</c></returns>
/// <remarks>
/// the format of <see cref="GetVersionAsync"/> might change with future releases of Chromium
/// </remarks>
public async Task<string> GetVersionAsync()
{
dynamic version = await Connection.SendAsync("Browser.getVersion");
return version.product.ToString();
}

/// <summary>
/// Gets the browser's original user agent
/// </summary>
/// <returns>Task which resolves to the browser's original user agent</returns>
/// <remarks>
/// Pages can override browser user agent with <see cref="Page.SetUserAgentAsync(string)"/>
/// </remarks>
public async Task<string> GetUserAgentAsync()
{
dynamic version = await Connection.SendAsync("Browser.getVersion");
return version.userAgent.ToString();
}

/// <summary>
/// Disconnects Puppeteer from the browser, but leaves the Chromium process running. After calling <see cref="Disconnect"/>, the browser object is considered disposed and cannot be used anymore
/// </summary>
public void Disconnect() => Connection.Dispose();

/// <summary>
/// Closes Chromium and all of its pages (if any were opened). The browser object itself is considered disposed and cannot be used anymore
/// </summary>
/// <returns>Task</returns>
public async Task CloseAsync()
{
if (IsClosed)
Expand Down Expand Up @@ -188,10 +229,10 @@ private async Task CreateTarget(MessageEventArgs args)
}
}

internal static async Task<Browser> CreateAsync(Connection connection, IBrowserOptions options,
Func<Task> closeCallBack)
internal static async Task<Browser> CreateAsync(
Connection connection, IBrowserOptions options, Process process, Func<Task> closeCallBack)
{
var browser = new Browser(connection, options, closeCallBack);
var browser = new Browser(connection, options, process, closeCallBack);
await connection.SendAsync("Target.setDiscoverTargets", new
{
discover = true
Expand Down
27 changes: 10 additions & 17 deletions lib/PuppeteerSharp/Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ public Connection(string url, int delay, ClientWebSocket ws)
private Dictionary<int, TaskCompletionSource<dynamic>> _responses;
private Dictionary<string, Session> _sessions;
private TaskCompletionSource<bool> _connectionCloseTask;

private bool _closeMessageSent;
private const string CloseMessage = "Browser.close";
private TaskQueue _socketQueue;
#endregion
Expand Down Expand Up @@ -67,13 +65,6 @@ public async Task<dynamic> SendAsync(string method, dynamic args = null)

await _socketQueue.Enqueue(() => WebSocket.SendAsync(buffer, WebSocketMessageType.Text, true, default(CancellationToken)));

//I don't know if this will be the final solution.
//For now this will prevent the WebSocket from failing after the process is killed by the close method.
if (method == CloseMessage)
{
_closeMessageSent = true;
}

return await _responses[id].Task;
}

Expand All @@ -95,15 +86,17 @@ public async Task<Session> CreateSession(string targetId)

private void OnClose()
{
if (IsClosed)
if (!IsClosed)
{
return;
Closed?.Invoke(this, new EventArgs());
_connectionCloseTask.SetResult(true);
IsClosed = true;
}

IsClosed = true;
_connectionCloseTask.SetResult(true);

Closed?.Invoke(this, new EventArgs());
foreach (var session in _sessions.Values)
{
session.OnClosed();
}

_responses.Clear();
_sessions.Clear();
Expand Down Expand Up @@ -149,7 +142,7 @@ await Task.WhenAny(
{
result = socketTask.Result;
}
catch (AggregateException) when (_closeMessageSent)
catch
{
if (!IsClosed)
{
Expand Down Expand Up @@ -211,7 +204,7 @@ private void ProcessResponse(string response)
var session = _sessions.GetValueOrDefault(objAsJObject["params"]["sessionId"].ToString());
if (session != null)
{
session.Close();
session.OnClosed();
}

_sessions.Remove(objAsJObject["params"]["sessionId"].ToString());
Expand Down
8 changes: 2 additions & 6 deletions lib/PuppeteerSharp/Launcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,13 @@ public async Task<Browser> LaunchAsync(LaunchOptions options, int chromiumRevisi
Console.WriteLine($"PROCESS COUNT: {Interlocked.Increment(ref _processCount)}");
}

return await Browser.CreateAsync(_connection, options, KillChrome);
return await Browser.CreateAsync(_connection, options, _chromeProcess, KillChrome);
}
catch (Exception ex)
{
ForceKillChrome();
throw new ChromeProcessException("Failed to create connection", ex);
}

}

/// <summary>
Expand All @@ -195,7 +194,7 @@ public async Task<Browser> ConnectAsync(ConnectOptions options)

_connection = await Connection.Create(options.BrowserWSEndpoint, connectionDelay, keepAliveInterval);

return await Browser.CreateAsync(_connection, options, () =>
return await Browser.CreateAsync(_connection, options, null, () =>
{
var closeTask = _connection.SendAsync("Browser.close", null);
return null;
Expand Down Expand Up @@ -322,7 +321,6 @@ private Task<string> WaitForEndpoint(Process chromeProcess, int timeout, bool du
new ChromeProcessException($"Timed out after {timeout} ms while trying to connect to Chrome! "));
_timer.Dispose();
}, null, timeout, 0);

}

chromeProcess.Start();
Expand Down Expand Up @@ -360,7 +358,6 @@ private async Task AfterProcessExit()
{
_waitForChromeToClose.SetResult(true);
}

}

private async Task KillChrome()
Expand Down Expand Up @@ -411,6 +408,5 @@ private static void SetEnvVariables(IDictionary<string, string> environment, IDi
}

#endregion

}
}
10 changes: 3 additions & 7 deletions lib/PuppeteerSharp/Session.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Net.WebSockets;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Newtonsoft.Json.Linq;
using System.IO;

namespace PuppeteerSharp
{
Expand All @@ -21,7 +17,7 @@ public Session(Connection connection, string targetId, string sessionId)
_callbacks = new Dictionary<int, MessageTask>();
}

#region Private Memebers
#region Private Members
private int _lastId = 0;
private Dictionary<int, MessageTask> _callbacks;
#endregion
Expand Down Expand Up @@ -98,7 +94,7 @@ public Task DetachAsync()

#endregion

#region Private Mathods
#region Private Methods

public void Dispose()
{
Expand Down Expand Up @@ -152,7 +148,7 @@ internal void OnMessage(string message)
}
}

internal void Close()
internal void OnClosed()
{
foreach (var callback in _callbacks.Values)
{
Expand Down

0 comments on commit e242e76

Please sign in to comment.