Skip to content

Commit 28f7e8c

Browse files
authored
Download browser (#60)
* Downloader feature complete
1 parent c26bcad commit 28f7e8c

File tree

8 files changed

+129
-44
lines changed

8 files changed

+129
-44
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading.Tasks;
4+
using Xunit;
5+
6+
namespace PuppeteerSharp.Tests.Puppeteer
7+
{
8+
public class DownloaderTests
9+
{
10+
[Fact(Skip = "Long run")]
11+
public async Task ShouldDownloadChromium()
12+
{
13+
var downloadsFolder = Path.Combine(Directory.GetCurrentDirectory(), ".test-chromium");
14+
var dirInfo = new DirectoryInfo(downloadsFolder);
15+
var downloader = new Downloader(downloadsFolder);
16+
17+
if (dirInfo.Exists)
18+
{
19+
dirInfo.Delete(true);
20+
}
21+
22+
await downloader.DownloadRevisionAsync(PuppeteerLaunchTests.ChromiumRevision);
23+
Assert.True(new FileInfo(downloader.GetExecutablePath(PuppeteerLaunchTests.ChromiumRevision)).Exists);
24+
}
25+
}
26+
}

lib/PuppeteerSharp.Tests/Puppeteer/PuppeteerLaunch.cs renamed to lib/PuppeteerSharp.Tests/Puppeteer/PuppeteerLaunchTests.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,32 @@
88

99
namespace PuppeteerSharp.Tests.Puppeteer
1010
{
11-
public class PuppeteerLaunch
11+
public class PuppeteerLaunchTests
1212
{
1313
private const int HttpsPort = 8908;
1414
private const string HttpsPrefix = "https://localhost:8908";
15+
public const int ChromiumRevision = 526987;
1516

1617
private Dictionary<string, object> _defaultBrowserOptions = new Dictionary<string, object>()
1718
{
18-
{ "executablePath", Environment.GetEnvironmentVariable("CHROME") },
1919
{ "slowMo", Convert.ToInt32(Environment.GetEnvironmentVariable("SLOW_MO") ?? "0") },
2020
{ "headless", Convert.ToBoolean(Environment.GetEnvironmentVariable("HEADLESS") ?? "true") },
2121
{ "args", new[] { "--no-sandbox" }},
2222
{ "timeout", 0}
2323
};
2424

25+
public PuppeteerLaunchTests()
26+
{
27+
Downloader.CreateDefault().DownloadRevisionAsync(ChromiumRevision).GetAwaiter().GetResult();
28+
}
29+
2530
[Fact]
26-
public async Task should_support_ignoreHTTPSErrors_option()
31+
public async Task ShouldSupportIgnoreHTTPSErrorsOption()
2732
{
2833
var options = _defaultBrowserOptions.Clone();
2934
options.Add("ignoreHTTPSErrors", true);
3035

31-
var puppeteerOptions = new PuppeteerOptions()
32-
{
33-
34-
ChromiumRevision = "526987"
35-
};
36-
37-
var browser = await PuppeteerSharp.Puppeteer.LaunchAsync(options, puppeteerOptions);
36+
var browser = await PuppeteerSharp.Puppeteer.LaunchAsync(options, ChromiumRevision);
3837
var page = await browser.NewPageAsync();
3938

4039
var response = await page.GoToAsync(HttpsPrefix + "/empty.html");

lib/PuppeteerSharp/Downloader.cs

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
24
using System.IO;
3-
using System.Reflection;
5+
using System.IO.Compression;
6+
using System.Net;
47
using System.Runtime.InteropServices;
8+
using System.Threading.Tasks;
59

610
namespace PuppeteerSharp
711
{
812
public class Downloader
913
{
10-
private string _downloadsFolder;
14+
private readonly string _downloadsFolder;
1115
private const string DefaultDownloadHost = "https://storage.googleapis.com";
16+
private static readonly Dictionary<Platform, string> _downloadUrls = new Dictionary<Platform, string> {
17+
{Platform.Linux, "{0}/chromium-browser-snapshots/Linux_x64/{1}/chrome-linux.zip"},
18+
{Platform.MacOS, "{0}/chromium-browser-snapshots/Mac/{1}/chrome-mac.zip"},
19+
{Platform.Win32, "{0}/chromium-browser-snapshots/Win/{1}/chrome-win32.zip"},
20+
{Platform.Win64, "{0}/chromium-browser-snapshots/Win_x64/{1}/chrome-win32.zip"}
21+
};
1222
private string _downloadHost;
1323

1424
public Downloader(string downloadsFolder)
@@ -17,24 +27,29 @@ public Downloader(string downloadsFolder)
1727
_downloadHost = DefaultDownloadHost;
1828
}
1929

30+
#region Public Methods
31+
2032
public static Downloader CreateDefault()
2133
{
2234
var downloadsFolder = Path.Combine(Directory.GetCurrentDirectory(), ".local-chromium");
2335
return new Downloader(downloadsFolder);
2436
}
2537

26-
internal static Platform CurrentPlatform()
38+
internal static Platform CurrentPlatform
2739
{
28-
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
29-
return Platform.MacOS;
30-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
31-
return Platform.Linux;
32-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
33-
return IntPtr.Size == 8 ? Platform.Win64 : Platform.Win32;
34-
return Platform.Unknown;
40+
get
41+
{
42+
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
43+
return Platform.MacOS;
44+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
45+
return Platform.Linux;
46+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
47+
return IntPtr.Size == 8 ? Platform.Win64 : Platform.Win32;
48+
return Platform.Unknown;
49+
}
3550
}
3651

37-
internal RevisionInfo RevisionInfo(Platform platform, string revision)
52+
internal RevisionInfo RevisionInfo(Platform platform, int revision)
3853
{
3954
var result = new RevisionInfo
4055
{
@@ -45,7 +60,47 @@ internal RevisionInfo RevisionInfo(Platform platform, string revision)
4560
return result;
4661
}
4762

48-
private static string GetExecutablePath(Platform platform, string folderPath)
63+
public async Task DownloadRevisionAsync(int revision)
64+
{
65+
var url = string.Format(_downloadUrls[CurrentPlatform], _downloadHost, revision);
66+
var zipPath = Path.Combine(_downloadsFolder, $"download-{CurrentPlatform.ToString()}-{revision}.zip");
67+
var folderPath = GetFolderPath(CurrentPlatform, revision);
68+
69+
if (new DirectoryInfo(folderPath).Exists)
70+
{
71+
return;
72+
}
73+
74+
var downloadFolder = new DirectoryInfo(_downloadsFolder);
75+
if (!downloadFolder.Exists)
76+
{
77+
downloadFolder.Create();
78+
}
79+
80+
await new WebClient().DownloadFileTaskAsync(new Uri(url), zipPath);
81+
82+
if (CurrentPlatform == Platform.MacOS)
83+
{
84+
//ZipFile and many others unzip libraries have issues extracting .app files
85+
//Until we have a clear solution we'll call the native unzip tool
86+
//https://github.com/dotnet/corefx/issues/15516
87+
NativeExtractToDirectory(zipPath, folderPath);
88+
}
89+
else
90+
{
91+
ZipFile.ExtractToDirectory(zipPath, folderPath);
92+
}
93+
94+
new FileInfo(zipPath).Delete();
95+
96+
}
97+
98+
public string GetExecutablePath(int revision)
99+
{
100+
return GetExecutablePath(CurrentPlatform, GetFolderPath(CurrentPlatform, revision));
101+
}
102+
103+
public static string GetExecutablePath(Platform platform, string folderPath)
49104
{
50105
switch (platform)
51106
{
@@ -62,9 +117,24 @@ private static string GetExecutablePath(Platform platform, string folderPath)
62117
}
63118
}
64119

65-
private string GetFolderPath(Platform platform, string revision)
120+
#endregion
121+
122+
#region Private Methods
123+
124+
private string GetFolderPath(Platform platform, int revision)
66125
{
67126
return Path.Combine(_downloadsFolder, $"{platform.ToString()}-{revision}");
68127
}
128+
129+
private void NativeExtractToDirectory(string zipPath, string folderPath)
130+
{
131+
var process = new Process();
132+
process.StartInfo.FileName = "unzip";
133+
process.StartInfo.Arguments = $"{zipPath} -d {folderPath}";
134+
process.Start();
135+
process.WaitForExit();
136+
}
137+
138+
#endregion
69139
}
70140
}

lib/PuppeteerSharp/Launcher.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public Launcher()
4646
{
4747
}
4848

49-
internal static async Task<Browser> LaunchAsync(Dictionary<string, object> options, PuppeteerOptions puppeteerOptions)
49+
internal static async Task<Browser> LaunchAsync(Dictionary<string, object> options, int chromiumRevision)
5050
{
5151
var chromeArguments = new List<string>(_defaultArgs);
5252

@@ -94,8 +94,7 @@ internal static async Task<Browser> LaunchAsync(Dictionary<string, object> optio
9494
if (string.IsNullOrEmpty(chromeExecutable))
9595
{
9696
var downloader = Downloader.CreateDefault();
97-
var revisionInfo = downloader.RevisionInfo(Downloader.CurrentPlatform(),
98-
puppeteerOptions.ChromiumRevision);
97+
var revisionInfo = downloader.RevisionInfo(Downloader.CurrentPlatform, chromiumRevision);
9998
chromeExecutable = revisionInfo.ExecutablePath;
10099
}
101100

lib/PuppeteerSharp/NetworkManager.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Diagnostics.Contracts;
43
using System.Linq;
54
using System.Net;
65
using System.Threading.Tasks;
@@ -135,12 +134,15 @@ private void OnLoadingFailed(MessageEventArgs e)
135134
{
136135
var request = _requestIdToRequest[e.MessageData.requestId.ToString()];
137136

138-
request.Failure = e.MessageData.errorText;
137+
request.Failure = e.MessageData.errorText.ToString();
139138
request.CompleteTaskWrapper.SetResult(true);
140139
_requestIdToRequest.Remove(request.RequestId);
141-
_interceptionIdToRequest.Remove(request.InterceptionId);
142-
_attemptedAuthentications.Remove(request.InterceptionId);
143140

141+
if (request.InterceptionId != null)
142+
{
143+
_interceptionIdToRequest.Remove(request.InterceptionId);
144+
_attemptedAuthentications.Remove(request.InterceptionId);
145+
}
144146
RequestFailed(this, new RequestEventArgs()
145147
{
146148
Request = request
@@ -224,9 +226,6 @@ private async Task OnRequestInterceptedAsync(MessageEventArgs e)
224226

225227
if (!string.IsNullOrEmpty(e.MessageData.redirectUrl))
226228
{
227-
Contract.Ensures(_interceptionIdToRequest.ContainsKey(e.MessageData.interceptionId),
228-
"INTERNAL ERROR: failed to find request for interception redirect.");
229-
230229
var request = _interceptionIdToRequest[e.MessageData.interceptionId];
231230

232231
HandleRequestRedirect(request, e.MessageData.responseStatusCode, e.MessageData.responseHeaders);

lib/PuppeteerSharp/Puppeteer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ public Puppeteer()
1010
{
1111
}
1212

13-
public static async Task<Browser> LaunchAsync(Dictionary<string, object> options, PuppeteerOptions puppeteerOptions)
13+
public static async Task<Browser> LaunchAsync(Dictionary<string, object> options, int chromiumRevision)
1414
{
15-
return await Launcher.LaunchAsync(options, puppeteerOptions);
15+
return await Launcher.LaunchAsync(options, chromiumRevision);
1616
}
1717
}
1818
}

lib/PuppeteerSharp/PuppeteerOptions.cs

Lines changed: 0 additions & 8 deletions
This file was deleted.

lib/PuppeteerSharp/RevisionInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace PuppeteerSharp
55
{
66
public struct RevisionInfo
77
{
8-
public string Revision { get; set; }
8+
public int Revision { get; set; }
99
public string FolderPath { get; set; }
1010
public string ExecutablePath { get; set; }
1111
public bool Downloaded => Directory.Exists(FolderPath);

0 commit comments

Comments
 (0)