Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .azure-pipelines/publish-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ extends:
- bash: ./build.sh --build-driver
displayName: "Build the driver"

- bash: echo "Node.js version of the driver: $(sed 's/.* node //' src/Playwright/.drivers/.stamp)"
displayName: "Print the Node.js version of the driver"

- task: AzureCLI@2
displayName: "Login to ACR via OIDC"
inputs:
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Before building the solution, build the driver:
./build.sh --build-driver
```

The driver comes from [microsoft/playwright](https://github.com/microsoft/playwright), at the commit in `DriverVersion` of `src/Common/Version.props`. The script clones that repository into `.upstream/` (gitignored), checks out the commit and builds the driver from it into `src/Playwright/.drivers`. Run it again when `DriverVersion` changes, for example after a pull. Do not commit, branch or edit in `.upstream/`.
The driver comes from [microsoft/playwright](https://github.com/microsoft/playwright), at the commit in `DriverVersion` of `src/Common/Version.props`. The script clones that repository into `.upstream/` (gitignored), checks out the commit and builds the driver from it into `src/Playwright/.drivers`, together with the latest Node.js release of `DriverNodeMajorVersion`. Run it again when `DriverVersion` changes, for example after a pull. Do not commit, branch or edit in `.upstream/`.

#### Dotnet Format

Expand Down
2 changes: 1 addition & 1 deletion ROLLING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
./build.sh --roll <ref>
```

This writes the commit and the latest Node.js LTS version to `src/Common/Version.props`, checks the commit out in `.upstream/`, builds the driver from it, generates the API and the transport channels again, and updates the browser versions in the README. The driver reports the version of `packages/playwright-core/package.json` at that commit: `1.66.0` on a release tag, `1.67.0-next` on `main`.
This writes the commit and the major version of the latest Node.js LTS to `src/Common/Version.props`, checks the commit out in `.upstream/`, builds the driver from it, generates the API and the transport channels again, and updates the browser versions in the README. Every driver build, also the ones for a release and for the Docker images, bundles the latest Node.js release of that major version. The driver reports the version of `packages/playwright-core/package.json` at that commit: `1.66.0` on a release tag, `1.67.0-next` on `main`.

2. **Port** what changed upstream into `src/Playwright/` and `src/Playwright.Tests/`.

Expand Down
13 changes: 7 additions & 6 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -112,17 +112,18 @@ function roll() {
local old_driver_version=$(read_driver_version < "${version_props_path}")
local new_driver_version
new_driver_version=$(resolve_ref "$1")
local new_node_version=$(curl -fsSL "https://nodejs.org/dist/index.json" \
| node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>console.log(JSON.parse(s).find(r=>r.lts).version.slice(1)))")
if [[ -z "${new_node_version}" ]]; then
echo "Failed to determine the Node.js version for the driver"
# The driver build bundles the latest release of this major version.
local new_node_major_version=$(curl -fsSL "https://nodejs.org/dist/index.json" \
| node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>console.log(JSON.parse(s).find(r=>r.lts).version.slice(1).split('.')[0]))")
if [[ -z "${new_node_major_version}" ]]; then
echo "Failed to determine the Node.js major version for the driver"
exit 1
fi
echo "Rolling from microsoft/playwright ${old_driver_version} to ${new_driver_version} (Node.js ${new_node_version})..."
echo "Rolling from microsoft/playwright ${old_driver_version} to ${new_driver_version} (Node.js ${new_node_major_version})..."

local xml_file_contents=$(cat "${version_props_path}")
xml_file_contents=$(echo "${xml_file_contents}" | sed "s|<DriverVersion>.*</DriverVersion>|<DriverVersion>${new_driver_version}</DriverVersion>|")
xml_file_contents=$(echo "${xml_file_contents}" | sed "s|<DriverNodeVersion>.*</DriverNodeVersion>|<DriverNodeVersion>${new_node_version}</DriverNodeVersion>|")
xml_file_contents=$(echo "${xml_file_contents}" | sed "s|<DriverNodeMajorVersion>.*</DriverNodeMajorVersion>|<DriverNodeMajorVersion>${new_node_major_version}</DriverNodeMajorVersion>|")
echo "${xml_file_contents}" > "${version_props_path}"

# The channels generator needs the dependencies that the driver build installs.
Expand Down
7 changes: 4 additions & 3 deletions src/Common/Version.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
<AssemblyVersion>1.65.0</AssemblyVersion>
<PackageVersion>$(AssemblyVersion)</PackageVersion>
<DriverVersion>5ccfbd85521679ebf5ab4d836852bf1c544aa748</DriverVersion>
<!-- The Node.js version bundled with the driver. Set by the roll script to the latest
Node.js LTS, matching upstream's utils/build/update-playwright-node.mjs. -->
<DriverNodeVersion>26.13.0</DriverNodeVersion>
<!-- The Node.js major version bundled with the driver. Each driver build bundles the
latest release of it. Set by the roll script to the latest Node.js LTS, matching
upstream's utils/build/update-playwright-node.mjs. -->
<DriverNodeMajorVersion>26</DriverNodeMajorVersion>
<ReleaseVersion>$(AssemblyVersion)</ReleaseVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<NoDefaultExcludes>true</NoDefaultExcludes>
Expand Down
82 changes: 62 additions & 20 deletions src/tools/Playwright.Tooling/DriverBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
Expand All @@ -40,8 +42,8 @@ namespace Playwright.Tooling;
/// <summary>
/// Builds the Playwright driver into src/Playwright/.drivers. The driver is the
/// playwright-core package of microsoft/playwright at the commit in DriverVersion
/// (src/Common/Version.props), plus the official Node.js binaries of
/// DriverNodeVersion. This:
/// (src/Common/Version.props), plus the official Node.js binaries of the latest
/// release of DriverNodeMajorVersion. This:
/// 1. clones microsoft/playwright into .upstream/ (gitignored) the first time,
/// and checks out the commit;
/// 2. builds it with `npm ci` and `npm run build` (once per commit);
Expand All @@ -52,33 +54,41 @@ namespace Playwright.Tooling;
/// .drivers/package/** - playwright-core npm package contents
/// .drivers/node/LICENSE - Node.js license
/// .drivers/node/&lt;platform&gt;/node[.exe] - per-platform Node.js binary.
/// A driver that is built is not built again until DriverVersion or
/// DriverNodeVersion changes. Do not commit, branch or edit in .upstream/: the
/// driver is built only from a checkout with no local changes.
/// A driver that is built is not built again until DriverVersion changes or
/// Node.js DriverNodeMajorVersion gets a new release. Do not commit, branch or
/// edit in .upstream/: the driver is built only from a checkout with no local changes.
/// </summary>
internal class DriverBuilder
{
private const string UpstreamUrl = "https://github.com/microsoft/playwright.git";

private const string NodeIndexUrl = "https://nodejs.org/dist/index.json";

private static readonly Regex _commitRegex = new("^[0-9a-f]{40}$", RegexOptions.Compiled);

private static readonly Regex _nodeReleaseRegex = new(@"^v([0-9]+\.[0-9]+\.[0-9]+)$", RegexOptions.Compiled);

// Directory names under .drivers/node/ are the platform ids that Driver.cs
// resolves at runtime; the suffixes name the Node.js download for that platform.
private static readonly (string PlatformId, string NodeSuffix)[] _platforms = new[]
// resolves at runtime; the suffixes name the Node.js download for that platform,
// and the index files name it in the "files" of a release in the Node.js index.
private static readonly (string PlatformId, string NodeSuffix, string NodeIndexFile)[] _platforms = new[]
{
("darwin-x64", "darwin-x64"),
("darwin-arm64", "darwin-arm64"),
("linux-x64", "linux-x64"),
("linux-arm64", "linux-arm64"),
("win32_x64", "win-x64"),
("darwin-x64", "darwin-x64", "osx-x64-tar"),
("darwin-arm64", "darwin-arm64", "osx-arm64-tar"),
("linux-x64", "linux-x64", "linux-x64"),
("linux-arm64", "linux-arm64", "linux-arm64"),
("win32_x64", "win-x64", "win-x64-zip"),
};

public string BasePath { get; set; }

/// <summary>The microsoft/playwright commit that the driver is built from.</summary>
public string DriverVersion { get; set; }

public string NodeVersion { get; set; }
/// <summary>The Node.js major version that the driver bundles the latest release of.</summary>
public int NodeMajorVersion { get; set; }

private string NodeVersion { get; set; }

private string UpstreamPath => Path.Combine(BasePath, ".upstream");

Expand All @@ -88,17 +98,21 @@ internal static Task RunAsync(BuildDriverOptions o)
var props = new XmlDocument();
props.Load(propsPath);
string driverVersion = props.DocumentElement.SelectSingleNode("/Project/PropertyGroup/DriverVersion").FirstChild.Value.Trim();
string nodeVersion = props.DocumentElement.SelectSingleNode("/Project/PropertyGroup/DriverNodeVersion").FirstChild.Value.Trim();
string nodeMajorVersion = props.DocumentElement.SelectSingleNode("/Project/PropertyGroup/DriverNodeMajorVersion").FirstChild.Value.Trim();
if (!_commitRegex.IsMatch(driverVersion))
{
throw new Exception($"DriverVersion in {propsPath} must be a microsoft/playwright commit (40 hex digits), got \"{driverVersion}\"");
}
if (!int.TryParse(nodeMajorVersion, NumberStyles.None, CultureInfo.InvariantCulture, out int nodeMajor))
{
throw new Exception($"DriverNodeMajorVersion in {propsPath} must be a Node.js major version such as 26, got \"{nodeMajorVersion}\"");
}

return new DriverBuilder()
{
BasePath = Path.GetFullPath(o.BasePath),
DriverVersion = driverVersion,
NodeVersion = nodeVersion,
NodeMajorVersion = nodeMajor,
}.ExecuteAsync();
}

Expand Down Expand Up @@ -259,6 +273,12 @@ private async Task<bool> ExecuteAsync()
{
await CheckoutUpstreamAsync().ConfigureAwait(false);

using var client = new HttpClient();
client.Timeout = TimeSpan.FromMinutes(5);
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36");

NodeVersion = await ResolveNodeVersionAsync(client).ConfigureAwait(false);

var driversDirectory = new DirectoryInfo(Path.Combine(BasePath, "src", "Playwright", ".drivers"));
string stamp = $"driver {DriverVersion} node {NodeVersion}";
string stampFile = Path.Combine(driversDirectory.FullName, ".stamp");
Expand All @@ -276,15 +296,11 @@ private async Task<bool> ExecuteAsync()

driversDirectory.Create();

using var client = new HttpClient();
client.Timeout = TimeSpan.FromMinutes(5);
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36");

var tasks = new List<Task>
{
BuildPlaywrightPackageAsync(driversDirectory.FullName),
};
foreach (var (platformId, nodeSuffix) in _platforms)
foreach (var (platformId, nodeSuffix, _) in _platforms)
{
tasks.Add(DownloadNodeAsync(client, driversDirectory.FullName, platformId, nodeSuffix));
}
Expand Down Expand Up @@ -403,6 +419,32 @@ private async Task BuildPlaywrightPackageAsync(string driversDirectory)
Console.WriteLine($"Built playwright-core at {DriverVersion}");
}

/// <summary>
/// Finds the latest release of Node.js NodeMajorVersion that has the binaries of all platforms.
/// </summary>
private async Task<string> ResolveNodeVersionAsync(HttpClient client)
{
Version latest = null;
await WithRetriesAsync(NodeIndexUrl, async () =>
{
using var stream = await GetStreamAsync(client, NodeIndexUrl).ConfigureAwait(false);
using var index = await JsonDocument.ParseAsync(stream).ConfigureAwait(false);
latest = index.RootElement.EnumerateArray()
.Where(release => _platforms.All(platform => release.GetProperty("files").EnumerateArray().Any(file => file.GetString() == platform.NodeIndexFile)))
.Select(release => _nodeReleaseRegex.Match(release.GetProperty("version").GetString()))
.Where(match => match.Success)
.Select(match => Version.Parse(match.Groups[1].Value))
.Where(version => version.Major == NodeMajorVersion)
.Max();
}).ConfigureAwait(false);
if (latest == null)
{
throw new Exception($"{NodeIndexUrl} has no Node.js {NodeMajorVersion} release with the binaries of all platforms");
}
Console.WriteLine($"The latest Node.js {NodeMajorVersion} release is {latest}");
return latest.ToString();
}

private async Task DownloadNodeAsync(HttpClient client, string driversDirectory, string platformId, string nodeSuffix)
{
bool isWindows = nodeSuffix.StartsWith("win-", StringComparison.Ordinal);
Expand Down
Loading