Skip to content

Commit 3c2533f

Browse files
authored
Download chrome-headless-shell by default and use it for the old headless mode (#2495)
* Download chrome-headless-shell by default and use it for the old headless mode * Without the combo
1 parent b87a9d7 commit 3c2533f

13 files changed

+120
-63
lines changed

.github/workflows/dotnet.yml

-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ jobs:
2525
strategy:
2626
fail-fast: false
2727
matrix:
28-
os: [ubuntu-latest, windows-latest]
29-
browser: [CHROME, FIREFOX]
30-
mode: [headless, headful, headless-shell]
3128
include:
3229
- os: ubuntu-latest
3330
browser: CHROME

lib/PuppeteerSharp.Tests/ChromeLauncherTests/GetFeaturesTests.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,35 @@ public class GetFeaturesTests : PuppeteerPageBaseTest
1010
[Test, Retry(2), PuppeteerTest("ChromeLauncher.test.ts", "getFeatures", "returns an empty array when no options are provided")]
1111
public void ReturnsAnEmptyArrayWhenNoOptionsAreProvided()
1212
{
13-
var result = ChromiumLauncher.GetFeatures("--foo", Array.Empty<string>());
13+
var result = ChromeLauncher.GetFeatures("--foo", Array.Empty<string>());
1414
Assert.IsEmpty(result);
1515
}
1616

1717
[Test, Retry(2), PuppeteerTest("ChromeLauncher.test.ts", "getFeatures", "returns an empty array when no options match the flag")]
1818
public void ReturnsAnEmptyArrayWhenNoOptionsMatchTheFlag()
1919
{
20-
var result = ChromiumLauncher.GetFeatures("--foo", new[] { "--bar", "--baz" });
20+
var result = ChromeLauncher.GetFeatures("--foo", new[] { "--bar", "--baz" });
2121
Assert.IsEmpty(result);
2222
}
2323

2424
[Test, Retry(2), PuppeteerTest("ChromeLauncher.test.ts", "getFeatures", "returns an array of values when options match the flag")]
2525
public void ReturnsAnArrayOfValuesWhenOptionsMatchTheFlag()
2626
{
27-
var result = ChromiumLauncher.GetFeatures("--foo", new[] { "--foo=bar", "--foo=baz" });
27+
var result = ChromeLauncher.GetFeatures("--foo", new[] { "--foo=bar", "--foo=baz" });
2828
Assert.AreEqual(new[] { "bar", "baz" }, result);
2929
}
3030

3131
[Test, Retry(2), PuppeteerTest("ChromeLauncher.test.ts", "getFeatures", "does not handle whitespace")]
3232
public void DoesNotHandleWhitespace()
3333
{
34-
var result = ChromiumLauncher.GetFeatures("--foo", new[] { "--foo bar", "--foo baz " });
34+
var result = ChromeLauncher.GetFeatures("--foo", new[] { "--foo bar", "--foo baz " });
3535
Assert.IsEmpty(result);
3636
}
3737

3838
[Test, Retry(2), PuppeteerTest("ChromeLauncher.test.ts", "getFeatures", "handles equals sign around the flag and value")]
3939
public void HandlesEqualsSignAroundTheFlagAndValue()
4040
{
41-
var result = ChromiumLauncher.GetFeatures("--foo", new[] { "--foo=bar", "--foo=baz" });
41+
var result = ChromeLauncher.GetFeatures("--foo", new[] { "--foo=bar", "--foo=baz" });
4242
Assert.AreEqual(new[] { "bar", "baz" }, result);
4343
}
4444
}

lib/PuppeteerSharp.Tests/ChromeLauncherTests/RemoveMatchingFlagsTests.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,31 @@ public class RemoveMatchingFlagsTests : PuppeteerPageBaseTest
1111
public void Empty()
1212
{
1313
var a = Array.Empty<string>();
14-
var result = ChromiumLauncher.RemoveMatchingFlags(a, "--foo");
14+
var result = ChromeLauncher.RemoveMatchingFlags(a, "--foo");
1515
Assert.IsEmpty(result);
1616
}
1717

1818
[Test, Retry(2), PuppeteerTest("ChromeLauncher.test.ts", "removeMatchingFlags", "with one match")]
1919
public void WithOneMatch()
2020
{
2121
var a = new[] { "--foo=1", "--bar=baz" };
22-
var result = ChromiumLauncher.RemoveMatchingFlags(a, "--foo");
22+
var result = ChromeLauncher.RemoveMatchingFlags(a, "--foo");
2323
Assert.AreEqual(new[] { "--bar=baz" }, result);
2424
}
2525

2626
[Test, Retry(2), PuppeteerTest("ChromeLauncher.test.ts", "removeMatchingFlags", "with multiple matches")]
2727
public void WithMultipleMatches()
2828
{
2929
var a = new[] { "--foo=1", "--bar=baz", "--foo=2" };
30-
var result = ChromiumLauncher.RemoveMatchingFlags(a, "--foo");
30+
var result = ChromeLauncher.RemoveMatchingFlags(a, "--foo");
3131
Assert.AreEqual(new[] { "--bar=baz" }, result);
3232
}
3333

3434
[Test, Retry(2), PuppeteerTest("ChromeLauncher.test.ts", "removeMatchingFlags", "with no matches")]
3535
public void WithNoMatches()
3636
{
3737
var a = new[] { "--foo=1", "--bar=baz" };
38-
var result = ChromiumLauncher.RemoveMatchingFlags(a, "--baz");
38+
var result = ChromeLauncher.RemoveMatchingFlags(a, "--baz");
3939
Assert.AreEqual(new[] { "--foo=1", "--bar=baz" }, result);
4040
}
4141
}

lib/PuppeteerSharp.Tests/FixturesTests/FixturesTests.cs

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using System.Diagnostics;
32
using System.IO;
43
using System.Linq;
@@ -11,8 +10,6 @@ namespace PuppeteerSharp.Tests.FixturesTests
1110
{
1211
public class FixturesTests : PuppeteerBaseTest
1312
{
14-
public FixturesTests() : base() { }
15-
1613
[Test, Retry(2), PuppeteerTest("fixtures.spec", "Fixtures", "should dump browser process stderr")]
1714
public void ShouldDumpBrowserProcessStderr()
1815
{
@@ -38,23 +35,23 @@ public async Task ShouldCloseTheBrowserWhenTheConnectedProcessCloses()
3835
{
3936
var browserClosedTaskWrapper = new TaskCompletionSource<bool>();
4037
using var browserFetcher = new BrowserFetcher(SupportedBrowser.Chrome);
41-
var ChromiumLauncher = new ChromiumLauncher(
38+
var chromiumLauncher = new ChromeLauncher(
4239
browserFetcher.GetInstalledBrowsers().First().GetExecutablePath(),
4340
new LaunchOptions { Headless = true });
4441

45-
await ChromiumLauncher.StartAsync().ConfigureAwait(false);
42+
await chromiumLauncher.StartAsync().ConfigureAwait(false);
4643

4744
var browser = await Puppeteer.ConnectAsync(new ConnectOptions
4845
{
49-
BrowserWSEndpoint = ChromiumLauncher.EndPoint
46+
BrowserWSEndpoint = chromiumLauncher.EndPoint
5047
});
5148

5249
browser.Disconnected += (_, _) =>
5350
{
5451
browserClosedTaskWrapper.SetResult(true);
5552
};
5653

57-
KillProcess(ChromiumLauncher.Process.Id);
54+
KillProcess(chromiumLauncher.Process.Id);
5855

5956
await browserClosedTaskWrapper.Task;
6057
Assert.True(browser.IsClosed);

lib/PuppeteerSharp/BrowserData/Chrome.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public static class Chrome
1313
/// <summary>
1414
/// Default chrome build.
1515
/// </summary>
16-
public static string DefaultBuildId => "122.0.6261.69";
16+
public static string DefaultBuildId => "122.0.6261.111";
1717

1818
internal static async Task<string> ResolveBuildIdAsync(ChromeReleaseChannel channel)
1919
=> (await GetLastKnownGoodReleaseForChannel(channel).ConfigureAwait(false)).Version;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using System.IO;
3+
4+
namespace PuppeteerSharp.BrowserData
5+
{
6+
/// <summary>
7+
/// Chrome info.
8+
/// </summary>
9+
public static class ChromeHeadlessShell
10+
{
11+
internal static string ResolveDownloadUrl(Platform platform, string buildId, string baseUrl)
12+
=> $"{baseUrl ?? "https://storage.googleapis.com/chrome-for-testing-public"}/{string.Join("/", ResolveDownloadPath(platform, buildId))}";
13+
14+
internal static string RelativeExecutablePath(Platform platform, string buildId)
15+
=> platform switch
16+
{
17+
Platform.MacOS or Platform.MacOSArm64 => Path.Combine(
18+
"chrome-headless-shell-" + GetFolder(platform),
19+
"chrome-headless-shell"),
20+
Platform.Linux => Path.Combine("chrome-headless-shell-linux64", "chrome-headless-shell"),
21+
Platform.Win32 or Platform.Win64 => Path.Combine("chrome-headless-shell-" + GetFolder(platform), "chrome-headless-shell.exe"),
22+
_ => throw new ArgumentException("Invalid platform", nameof(platform)),
23+
};
24+
25+
private static string[] ResolveDownloadPath(Platform platform, string buildId)
26+
=> new string[]
27+
{
28+
buildId,
29+
GetFolder(platform),
30+
$"chrome-headless-shell-{GetFolder(platform)}.zip",
31+
};
32+
33+
private static string GetFolder(Platform platform)
34+
=> platform switch
35+
{
36+
Platform.Linux => "linux64",
37+
Platform.MacOSArm64 => "mac-arm64",
38+
Platform.MacOS => "mac-x64",
39+
Platform.Win32 => "win32",
40+
Platform.Win64 => "win64",
41+
_ => throw new PuppeteerException($"Unknown platform: {platform}"),
42+
};
43+
}
44+
}

lib/PuppeteerSharp/BrowserData/InstalledBrowser.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ namespace PuppeteerSharp.BrowserData
99
/// </summary>
1010
public class InstalledBrowser
1111
{
12-
internal static readonly Dictionary<SupportedBrowser, Func<Platform, string, string>> _executablePathByBrowser = new()
12+
private static readonly Dictionary<SupportedBrowser, Func<Platform, string, string>> _executablePathByBrowser = new()
1313
{
1414
[SupportedBrowser.Chrome] = Chrome.RelativeExecutablePath,
15+
[SupportedBrowser.ChromeHeadlessShell] = ChromeHeadlessShell.RelativeExecutablePath,
1516
[SupportedBrowser.Chromium] = Chromium.RelativeExecutablePath,
1617
[SupportedBrowser.Firefox] = Firefox.RelativeExecutablePath,
1718
};

lib/PuppeteerSharp/BrowserFetcher.cs

+45-32
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class BrowserFetcher : IBrowserFetcher
2323
private static readonly Dictionary<SupportedBrowser, Func<Platform, string, string, string>> _downloadsUrl = new()
2424
{
2525
[SupportedBrowser.Chrome] = Chrome.ResolveDownloadUrl,
26+
[SupportedBrowser.ChromeHeadlessShell] = ChromeHeadlessShell.ResolveDownloadUrl,
2627
[SupportedBrowser.Chromium] = Chromium.ResolveDownloadUrl,
2728
[SupportedBrowser.Firefox] = Firefox.ResolveDownloadUrl,
2829
};
@@ -128,42 +129,14 @@ public void Uninstall(string buildId)
128129
/// <inheritdoc/>
129130
public async Task<InstalledBrowser> DownloadAsync(string buildId)
130131
{
131-
var url = _downloadsUrl[Browser](Platform, buildId, BaseUrl);
132-
var fileName = url.Split('/').Last();
133-
var cache = new Cache(CacheDir);
134-
var archivePath = Path.Combine(CacheDir, fileName);
135-
var downloadFolder = new DirectoryInfo(CacheDir);
136-
137-
if (!downloadFolder.Exists)
138-
{
139-
downloadFolder.Create();
140-
}
141-
142-
if (DownloadProgressChanged != null)
143-
{
144-
_webClient.DownloadProgressChanged += DownloadProgressChanged;
145-
}
132+
var installedBrowser = await DownloadAsync(Browser, buildId).ConfigureAwait(false);
146133

147-
var outputPath = cache.GetInstallationDir(Browser, Platform, buildId);
148-
149-
if (new DirectoryInfo(outputPath).Exists)
134+
if (Browser == SupportedBrowser.Chrome)
150135
{
151-
return new InstalledBrowser(cache, Browser, buildId, Platform);
136+
await DownloadAsync(SupportedBrowser.ChromeHeadlessShell, buildId).ConfigureAwait(false);
152137
}
153138

154-
try
155-
{
156-
await _customFileDownload(url, archivePath).ConfigureAwait(false);
157-
}
158-
catch (Exception ex)
159-
{
160-
throw new PuppeteerException($"Failed to download {Browser} for {Platform} from {url}", ex);
161-
}
162-
163-
await UnpackArchiveAsync(archivePath, outputPath, fileName).ConfigureAwait(false);
164-
new FileInfo(archivePath).Delete();
165-
166-
return new InstalledBrowser(cache, Browser, buildId, Platform);
139+
return installedBrowser;
167140
}
168141

169142
/// <inheritdoc/>
@@ -267,6 +240,46 @@ private static void ExtractTar(string zipPath, string folderPath)
267240
process.WaitForExit();
268241
}
269242

243+
private async Task<InstalledBrowser> DownloadAsync(SupportedBrowser browser, string buildId)
244+
{
245+
var url = _downloadsUrl[browser](Platform, buildId, BaseUrl);
246+
var fileName = url.Split('/').Last();
247+
var cache = new Cache(CacheDir);
248+
var archivePath = Path.Combine(CacheDir, fileName);
249+
var downloadFolder = new DirectoryInfo(CacheDir);
250+
251+
if (!downloadFolder.Exists)
252+
{
253+
downloadFolder.Create();
254+
}
255+
256+
if (DownloadProgressChanged != null)
257+
{
258+
_webClient.DownloadProgressChanged += DownloadProgressChanged;
259+
}
260+
261+
var outputPath = cache.GetInstallationDir(browser, Platform, buildId);
262+
263+
if (new DirectoryInfo(outputPath).Exists)
264+
{
265+
return new InstalledBrowser(cache, browser, buildId, Platform);
266+
}
267+
268+
try
269+
{
270+
await _customFileDownload(url, archivePath).ConfigureAwait(false);
271+
}
272+
catch (Exception ex)
273+
{
274+
throw new PuppeteerException($"Failed to download {browser} for {Platform} from {url}", ex);
275+
}
276+
277+
await UnpackArchiveAsync(archivePath, outputPath, fileName).ConfigureAwait(false);
278+
new FileInfo(archivePath).Delete();
279+
280+
return new InstalledBrowser(cache, browser, buildId, Platform);
281+
}
282+
270283
private Task InstallDMGAsync(string dmgPath, string folderPath)
271284
{
272285
try

lib/PuppeteerSharp/ChromiumLauncher.cs lib/PuppeteerSharp/ChromeLauncher.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ namespace PuppeteerSharp
1111
/// Represents a Chromium process and any associated temporary user data directory that have created
1212
/// by Puppeteer and therefore must be cleaned up when no longer needed.
1313
/// </summary>
14-
public class ChromiumLauncher : LauncherBase
14+
public class ChromeLauncher : LauncherBase
1515
{
1616
private const string UserDataDirArgument = "--user-data-dir";
1717

1818
/// <summary>
19-
/// Initializes a new instance of the <see cref="ChromiumLauncher"/> class.
19+
/// Initializes a new instance of the <see cref="ChromeLauncher"/> class.
2020
/// </summary>
2121
/// <param name="executable">Full path of executable.</param>
2222
/// <param name="options">Options for launching Chromium.</param>
23-
public ChromiumLauncher(string executable, LaunchOptions options)
23+
public ChromeLauncher(string executable, LaunchOptions options)
2424
: base(executable, options)
2525
{
2626
List<string> chromiumArgs;

lib/PuppeteerSharp/LaunchOptions.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,13 @@ public bool Devtools
102102
public bool LogProcess { get; set; }
103103

104104
/// <summary>
105-
/// If <c>true</c>, then do not use <see cref="ChromiumLauncher.GetDefaultArgs"/>.
105+
/// If <c>true</c>, then do not use <see cref="ChromeLauncher.GetDefaultArgs"/>.
106106
/// Dangerous option; use with care. Defaults to <c>false</c>.
107107
/// </summary>
108108
public bool IgnoreDefaultArgs { get; set; }
109109

110110
/// <summary>
111-
/// if <see cref="IgnoreDefaultArgs"/> is set to <c>false</c> this list will be used to filter <see cref="ChromiumLauncher.GetDefaultArgs"/>.
111+
/// if <see cref="IgnoreDefaultArgs"/> is set to <c>false</c> this list will be used to filter <see cref="ChromeLauncher.GetDefaultArgs"/>.
112112
/// </summary>
113113
public string[] IgnoredDefaultArgs
114114
{

lib/PuppeteerSharp/Launcher.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public async Task<IBrowser> LaunchAsync(LaunchOptions options)
5555

5656
Process = options.Browser switch
5757
{
58-
SupportedBrowser.Chrome or SupportedBrowser.Chromium => new ChromiumLauncher(executable, options),
58+
SupportedBrowser.Chrome or SupportedBrowser.Chromium => new ChromeLauncher(executable, options),
5959
SupportedBrowser.Firefox => new FirefoxLauncher(executable, options),
6060
_ => throw new ArgumentException("Invalid product"),
6161
};
@@ -187,7 +187,7 @@ private void EnsureSingleLaunchOrConnect()
187187
_processLaunched = true;
188188
}
189189

190-
private string ResolveExecutablePath(string buildId)
190+
private string ResolveExecutablePath(HeadlessMode headlessMode, string buildId)
191191
{
192192
var executablePath = Environment.GetEnvironmentVariable("PUPPETEER_EXECUTABLE_PATH");
193193

@@ -203,7 +203,7 @@ private string ResolveExecutablePath(string buildId)
203203

204204
return new InstalledBrowser(
205205
new Cache(),
206-
_browser,
206+
headlessMode == HeadlessMode.Shell && _browser == SupportedBrowser.Chrome ? SupportedBrowser.ChromeHeadlessShell : _browser,
207207
buildId,
208208
BrowserFetcher.GetCurrentPlatform()).GetExecutablePath();
209209
}
@@ -215,7 +215,7 @@ private string GetExecutablePath(LaunchOptions options, string buildId)
215215
return ComputeSystemExecutablePath(_browser, options.Channel.Value);
216216
}
217217

218-
return ResolveExecutablePath(buildId);
218+
return ResolveExecutablePath(options.HeadlessMode, buildId);
219219
}
220220

221221
private string ComputeSystemExecutablePath(SupportedBrowser browser, ChromeReleaseChannel channel)

lib/PuppeteerSharp/Puppeteer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public static class Puppeteer
6464
public static string[] GetDefaultArgs(LaunchOptions options = null)
6565
=> (options?.Browser ?? SupportedBrowser.Firefox) == SupportedBrowser.Firefox
6666
? FirefoxLauncher.GetDefaultArgs(options ?? new LaunchOptions())
67-
: ChromiumLauncher.GetDefaultArgs(options ?? new LaunchOptions());
67+
: ChromeLauncher.GetDefaultArgs(options ?? new LaunchOptions());
6868

6969
/// <summary>
7070
/// The method launches a browser instance with given arguments. The browser will be closed when the Browser is disposed.

lib/PuppeteerSharp/SupportedBrowser.cs

+5
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,10 @@ public enum SupportedBrowser
1919
/// Chromium.
2020
/// </summary>
2121
Chromium,
22+
23+
/// <summary>
24+
/// Chrome headless shell.
25+
/// </summary>
26+
ChromeHeadlessShell,
2227
}
2328
}

0 commit comments

Comments
 (0)