Skip to content

Commit 0714c7c

Browse files
cdroulerskblok
authored andcommitted
Implement Puppeteer.ConnectAsync (#137)
1 parent 53e9640 commit 0714c7c

File tree

11 files changed

+243
-22
lines changed

11 files changed

+243
-22
lines changed

lib/PuppeteerSharp.Tests/FrameUtils.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Text.RegularExpressions;
23
using System.Threading.Tasks;
34

45
namespace PuppeteerSharp.Tests
@@ -15,5 +16,16 @@ await page.EvaluateFunctionAsync(@"(frameId, url) => {
1516
return new Promise(x => frame.onload = x);
1617
}", frameId, url);
1718
}
19+
20+
public static string DumpFrames(Frame frame, string indentation = "")
21+
{
22+
var result = indentation + Regex.Replace(frame.Url, @":\d{4}", ":<PORT>");
23+
foreach (var child in frame.ChildFrames)
24+
{
25+
result += Environment.NewLine + DumpFrames(child, " " + indentation);
26+
}
27+
28+
return result;
29+
}
1830
}
1931
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System.Linq;
2+
using System.Threading.Tasks;
3+
using Xunit;
4+
5+
namespace PuppeteerSharp.Tests.Puppeteer
6+
{
7+
[Collection("PuppeteerLoaderFixture collection")]
8+
public class PuppeteerConnectTests : PuppeteerBaseTest
9+
{
10+
[Fact]
11+
public async Task ShouldBeAbleToConnectMultipleTimesToSameBrowser()
12+
{
13+
var options = new ConnectOptions()
14+
{
15+
BrowserWSEndpoint = Browser.WebSocketEndpoint
16+
};
17+
var browser = await PuppeteerSharp.Puppeteer.ConnectAsync(options);
18+
using (var page = await browser.NewPageAsync())
19+
{
20+
var response = await page.EvaluateExpressionAsync<int>("7 * 8");
21+
Assert.Equal(response, 56);
22+
}
23+
24+
using (var originalPage = await Browser.NewPageAsync())
25+
{
26+
var response = await originalPage.EvaluateExpressionAsync<int>("7 * 6");
27+
Assert.Equal(response, 42);
28+
}
29+
}
30+
31+
[Fact]
32+
public async Task ShouldBeAbleToReconnectToADisconnectedBrowser()
33+
{
34+
var options = new ConnectOptions()
35+
{
36+
BrowserWSEndpoint = Browser.WebSocketEndpoint
37+
};
38+
39+
var url = TestConstants.CrossProcessHttpPrefix + "/frames/frame.html";
40+
var page = await Browser.NewPageAsync();
41+
await page.GoToAsync(url);
42+
43+
Browser.Disconnect();
44+
45+
using (var browser = await PuppeteerSharp.Puppeteer.ConnectAsync(options))
46+
{
47+
var pages = (await browser.Pages()).ToList();
48+
var restoredPage = pages.FirstOrDefault(x => x.Url == url);
49+
Assert.NotNull(restoredPage);
50+
var frameDump = FrameUtils.DumpFrames(restoredPage.MainFrame);
51+
Assert.Equal(@"http://127.0.0.1:<PORT>/frames/frame.html", frameDump);
52+
var response = await restoredPage.EvaluateExpressionAsync<int>("7 * 8");
53+
Assert.Equal(response, 56);
54+
}
55+
}
56+
}
57+
}

lib/PuppeteerSharp.Tests/PuppeteerSharp.Tests.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
</ItemGroup>
1616

1717
<ItemGroup>
18-
<Folder Include="Puppeteer\" />
1918
<Folder Include="Issues\" />
2019
</ItemGroup>
2120

lib/PuppeteerSharp/Browser.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Dynamic;
4+
using System.Linq;
45
using System.Threading.Tasks;
56

67
namespace PuppeteerSharp
78
{
89
public class Browser : IDisposable
910
{
10-
public Browser(Connection connection, LaunchOptions options, Func<Task> closeCallBack)
11+
public Browser(Connection connection, IBrowserOptions options, Func<Task> closeCallBack)
1112
{
1213
Connection = connection;
1314
IgnoreHTTPSErrors = options.IgnoreHTTPSErrors;
@@ -69,6 +70,9 @@ public async Task<Page> NewPageAsync()
6970
return await target.Page();
7071
}
7172

73+
public async Task<IEnumerable<Page>> Pages()
74+
=> (await Task.WhenAll(this._targets.Select(x => x.Value.Page()))).Where(x => x != null);
75+
7276
internal void ChangeTarget(TargetInfo targetInfo)
7377
{
7478
TargetChanged?.Invoke(this, new TargetChangedArgs()
@@ -83,6 +87,8 @@ public async Task<string> GetVersionAsync()
8387
return version.product.ToString();
8488
}
8589

90+
public void Disconnect() => Connection.Dispose();
91+
8692
public async Task CloseAsync()
8793
{
8894
if (IsClosed)
@@ -93,7 +99,7 @@ public async Task CloseAsync()
9399
IsClosed = true;
94100

95101
await _closeCallBack();
96-
Connection.Dispose();
102+
Disconnect();
97103
Closed?.Invoke(this, new EventArgs());
98104
}
99105

@@ -166,7 +172,7 @@ private async Task CreateTarget(MessageEventArgs args)
166172

167173
}
168174

169-
internal static async Task<Browser> CreateAsync(Connection connection, LaunchOptions options,
175+
internal static async Task<Browser> CreateAsync(Connection connection, IBrowserOptions options,
170176
Func<Task> closeCallBack)
171177
{
172178
var browser = new Browser(connection, options, closeCallBack);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
namespace PuppeteerSharp
2+
{
3+
/// <summary>
4+
/// Options for connecting to an existing browser.
5+
/// </summary>
6+
public class ConnectOptions : IBrowserOptions
7+
{
8+
/// <summary>
9+
/// Whether to ignore HTTPS errors during navigation. Defaults to false.
10+
/// </summary>
11+
public bool IgnoreHTTPSErrors { get; set; }
12+
13+
/// <summary>
14+
/// If set to true, sets Headless = false, otherwise, enables automation.
15+
/// </summary>
16+
public bool AppMode { get; set; }
17+
18+
/// <summary>
19+
/// A browser websocket endpoint to connect to.
20+
/// </summary>
21+
public string BrowserWSEndpoint { get; set; }
22+
23+
/// <summary>
24+
/// Slows down Puppeteer operations by the specified amount of milliseconds. Useful so that you can see what is going on.
25+
/// </summary>
26+
public int SlowMo { get; set; }
27+
28+
/// <summary>
29+
/// Keep alive value.
30+
/// </summary>
31+
public int KeepAliveInterval { get; set; } = 30;
32+
}
33+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace PuppeteerSharp
2+
{
3+
public interface IBrowserOptions
4+
{
5+
/// <summary>
6+
/// Whether to ignore HTTPS errors during navigation. Defaults to false.
7+
/// </summary>
8+
bool IgnoreHTTPSErrors { get; }
9+
10+
/// <summary>
11+
/// If set to true, sets Headless = false, otherwise, enables automation.
12+
/// </summary>
13+
bool AppMode { get; }
14+
}
15+
}

lib/PuppeteerSharp/LaunchOptions.cs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,74 @@
33

44
namespace PuppeteerSharp
55
{
6-
public class LaunchOptions
6+
/// <summary>
7+
/// Options for launching the Chrome/ium browser.
8+
/// </summary>
9+
public class LaunchOptions : IBrowserOptions
710
{
11+
/// <summary>
12+
/// Whether to ignore HTTPS errors during navigation. Defaults to false.
13+
/// </summary>
14+
public bool IgnoreHTTPSErrors { get; set; }
15+
16+
/// <summary>
17+
/// If set to true, sets Headless = false, otherwise, enables automation.
18+
/// </summary>
819
public bool AppMode { get; set; }
920

10-
public bool IgnoreHTTPSErrors { get; set; }
11-
21+
/// <summary>
22+
/// Whether to run browser in headless mode. Defaults to true unless the devtools option is true.
23+
/// </summary>
1224
public bool Headless { get; set; } = true;
1325

26+
/// <summary>
27+
/// Path to a Chromium or Chrome executable to run instead of bundled Chromium. If executablePath is a relative path, then it is resolved relative to current working directory.
28+
/// </summary>
1429
public string ExecutablePath { get; set; }
1530

31+
/// <summary>
32+
/// Slows down Puppeteer operations by the specified amount of milliseconds. Useful so that you can see what is going on.
33+
/// </summary>
1634
public int SlowMo { get; set; }
1735

36+
/// <summary>
37+
/// Additional arguments to pass to the browser instance. List of Chromium flags can be found <a href="http://peter.sh/experiments/chromium-command-line-switches/">here</a>.
38+
/// </summary>
1839
public string[] Args { get; set; } = Array.Empty<string>();
19-
40+
41+
/// <summary>
42+
/// Maximum time in milliseconds to wait for the browser instance to start. Defaults to 30000 (30 seconds). Pass 0 to disable timeout.
43+
/// </summary>
2044
public int Timeout { get; set; } = 30_000;
2145

46+
/// <summary>
47+
/// Whether to pipe browser process stdout and stderr into process.stdout and process.stderr. Defaults to false.
48+
/// </summary>
2249
public bool DumpIO { get; set; }
2350

51+
/// <summary>
52+
/// Path to a User Data Directory.
53+
/// </summary>
2454
public string UserDataDir { get; set; }
2555

56+
/// <summary>
57+
/// Specify environment variables that will be visible to browser. Defaults to Environment variables.
58+
/// </summary>
2659
public IDictionary<string, string> Env { get; } = new Dictionary<string, string>();
2760

61+
/// <summary>
62+
/// Whether to auto-open DevTools panel for each tab. If this option is true, the headless option will be set false.
63+
/// </summary>
2864
public bool Devtools { get; set; }
2965

66+
/// <summary>
67+
/// Keep alive value.
68+
/// </summary>
3069
public int KeepAliveInterval { get; set; } = 30;
3170

71+
/// <summary>
72+
/// Logs process counts after launching chrome and after exiting.
73+
/// </summary>
3274
public bool LogProcess { get; set; }
3375
}
3476
}

lib/PuppeteerSharp/Launcher.cs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,17 @@ public Launcher()
6060
}
6161

6262
#region Public methods
63+
/// <summary>
64+
/// The method launches a browser instance with given arguments. The browser will be closed when the Browser is disposed.
65+
/// </summary>
66+
/// <param name="options">Options for launching Chrome</param>
67+
/// <param name="chromiumRevision">The revision of Chrome to launch.</param>
68+
/// <returns>A connected browser.</returns>
69+
/// <remarks>
70+
/// See <a href="https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/">this article</a>
71+
/// for a description of the differences between Chromium and Chrome.
72+
/// <a href="https://chromium.googlesource.com/chromium/src/+/lkcr/docs/chromium_browser_vs_google_chrome.md">This article</a> describes some differences for Linux users.
73+
/// </remarks>
6374
public async Task<Browser> LaunchAsync(LaunchOptions options, int chromiumRevision)
6475
{
6576
var chromeArguments = new List<string>(DefaultArgs);
@@ -170,6 +181,28 @@ public async Task<Browser> LaunchAsync(LaunchOptions options, int chromiumRevisi
170181

171182
}
172183

184+
/// <summary>
185+
/// Attaches Puppeteer to an existing Chromium instance. The browser will be closed when the Browser is disposed.
186+
/// </summary>
187+
/// <param name="options">Options for connecting.</param>
188+
/// <returns>A connected browser.</returns>
189+
public async Task<Browser> ConnectAsync(ConnectOptions options)
190+
{
191+
try
192+
{
193+
var connectionDelay = options.SlowMo;
194+
var keepAliveInterval = options.KeepAliveInterval;
195+
196+
_connection = await Connection.Create(options.BrowserWSEndpoint, connectionDelay, keepAliveInterval);
197+
198+
return await Browser.CreateAsync(_connection, options, () => _connection.SendAsync("Browser.close", null));
199+
}
200+
catch (Exception ex)
201+
{
202+
throw new Exception("Failed to create connection", ex);
203+
}
204+
}
205+
173206
public async Task TryDeleteUserDataDir(int times = 10, TimeSpan? delay = null)
174207
{
175208
if (!IsChromeClosed)
@@ -338,7 +371,6 @@ private async Task KillChrome()
338371
}
339372

340373
await _waitForChromeToClose.Task;
341-
342374
}
343375

344376
private void ForceKillChrome()
@@ -357,7 +389,6 @@ private void ForceKillChrome()
357389
}
358390
}
359391

360-
361392
private static void SetEnvVariables(IDictionary<string, string> environment, IDictionary<string, string> customEnv,
362393
IDictionary realEnv)
363394
{

lib/PuppeteerSharp/Page.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
using System;
2-
using System.Linq;
32
using System.Collections.Generic;
4-
using System.Threading.Tasks;
5-
using PuppeteerSharp.Input;
6-
using PuppeteerSharp.Helpers;
7-
using System.IO;
3+
using System.Diagnostics;
4+
using System.Dynamic;
85
using System.Globalization;
6+
using System.IO;
7+
using System.Linq;
8+
using System.Threading.Tasks;
99
using Newtonsoft.Json.Linq;
10-
using System.Dynamic;
10+
using PuppeteerSharp.Helpers;
11+
using PuppeteerSharp.Input;
1112
using PuppeteerSharp.Messaging;
12-
using System.Threading;
1313

1414
namespace PuppeteerSharp
1515
{
16+
[DebuggerDisplay("Page {Url}")]
1617
public class Page : IDisposable
1718
{
1819
public int DefaultNavigationTimeout { get; set; } = 30000;

0 commit comments

Comments
 (0)