Skip to content

Commit b7b3675

Browse files
authored
chore: roll driver to 1.62.0-alpha-1781285727000 (#3323)
1 parent f845b4a commit b7b3675

51 files changed

Lines changed: 1484 additions & 106 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
| | Linux | macOS | Windows |
55
| :--- | :---: | :---: | :---: |
6-
| Chromium <!-- GEN:chromium-version -->148.0.7778.96<!-- GEN:stop --> ||||
7-
| WebKit <!-- GEN:webkit-version -->26.4<!-- GEN:stop --> ||||
8-
| Firefox <!-- GEN:firefox-version -->150.0.2<!-- GEN:stop --> ||||
6+
| Chromium <!-- GEN:chromium-version -->149.0.7827.55<!-- GEN:stop --> ||||
7+
| WebKit <!-- GEN:webkit-version -->26.5<!-- GEN:stop --> ||||
8+
| Firefox <!-- GEN:firefox-version -->151.0<!-- GEN:stop --> ||||
99

1010
Playwright for .NET is the official language port of [Playwright](https://playwright.dev), the library to automate [Chromium](https://www.chromium.org/Home), [Firefox](https://www.mozilla.org/en-US/firefox/new/) and [WebKit](https://webkit.org/) with a single API. Playwright is built to enable cross-browser web automation that is **ever-green**, **capable**, **reliable** and **fast**.
1111

src/Common/Version.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
<PropertyGroup>
33
<AssemblyVersion>1.60.0</AssemblyVersion>
44
<PackageVersion>$(AssemblyVersion)</PackageVersion>
5-
<DriverVersion>1.60.0</DriverVersion>
5+
<DriverVersion>1.62.0-alpha-1781285727000</DriverVersion>
66
<!-- The Node.js version bundled with the driver. Kept in sync with NODE_VERSION
77
in upstream's utils/build/build-playwright-driver.sh by the roll script. -->
8-
<DriverNodeVersion>24.15.0</DriverNodeVersion>
8+
<DriverNodeVersion>24.16.0</DriverNodeVersion>
99
<ReleaseVersion>$(AssemblyVersion)</ReleaseVersion>
1010
<FileVersion>$(AssemblyVersion)</FileVersion>
1111
<NoDefaultExcludes>true</NoDefaultExcludes>

src/Playwright.TestingHarnessTest/package-lock.json

Lines changed: 23 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Playwright.TestingHarnessTest/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "playwright.testingharnesstest",
33
"private": true,
44
"devDependencies": {
5-
"@playwright/test": "1.60.0",
5+
"@playwright/test": "1.62.0-alpha-1781285727000",
66
"@types/node": "^22.12.0",
77
"fast-xml-parser": "^4.5.0"
88
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) Microsoft Corporation.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
using System.Text.Json;
26+
27+
namespace Microsoft.Playwright.Tests;
28+
29+
///<playwright-file>browsercontext-webauthn.spec.ts</playwright-file>
30+
public class BrowserContextWebAuthnTests : BrowserTestEx
31+
{
32+
// Server.Prefix is http://localhost:<port>.
33+
private const string RpId = "localhost";
34+
35+
[PlaywrightTest("browsercontext-webauthn.spec.ts", "should not intercept navigator.credentials without install()")]
36+
public async Task ShouldNotInterceptNavigatorCredentialsWithoutInstall()
37+
{
38+
await using var context = await Browser.NewContextAsync();
39+
// Seed a credential, but do not install the interceptor.
40+
await context.Credentials.CreateAsync(RpId);
41+
var page = await context.NewPageAsync();
42+
await page.GotoAsync(Server.EmptyPage);
43+
44+
Assert.IsFalse(await page.EvaluateAsync<bool>("() => globalThis.__pwWebAuthnInstalled === true"));
45+
}
46+
47+
[PlaywrightTest("browsercontext-webauthn.spec.ts", "should seed a known credential and authenticate")]
48+
public async Task ShouldSeedAKnownCredentialAndAuthenticate()
49+
{
50+
// This is the easiest way to create credentials. In practice, this
51+
// probably comes from environment.
52+
await using var source = await Browser.NewContextAsync();
53+
var known = await source.Credentials.CreateAsync(RpId);
54+
55+
// A fresh context imports the known credential and signs in with it.
56+
await using var context = await Browser.NewContextAsync();
57+
await context.Credentials.CreateAsync(known.RpId, new()
58+
{
59+
Id = known.Id,
60+
UserHandle = known.UserHandle,
61+
PrivateKey = known.PrivateKey,
62+
PublicKey = known.PublicKey,
63+
});
64+
await context.Credentials.InstallAsync();
65+
var page = await context.NewPageAsync();
66+
await page.GotoAsync(Server.EmptyPage);
67+
68+
var result = await page.EvaluateAsync<JsonElement>(@"async ({ rpId, credentialId }) => {
69+
const b64UrlToBytes = (s) => {
70+
let str = s.replace(/-/g, '+').replace(/_/g, '/');
71+
while (str.length % 4)
72+
str += '=';
73+
const bin = atob(str);
74+
const u8 = new Uint8Array(bin.length);
75+
for (let i = 0; i < bin.length; i++)
76+
u8[i] = bin.charCodeAt(i);
77+
return u8;
78+
};
79+
const challenge = crypto.getRandomValues(new Uint8Array(32));
80+
const cred = await navigator.credentials.get({
81+
publicKey: {
82+
challenge,
83+
rpId,
84+
allowCredentials: [{ type: 'public-key', id: b64UrlToBytes(credentialId) }],
85+
userVerification: 'preferred',
86+
},
87+
});
88+
const resp = cred.response;
89+
return {
90+
id: cred.id,
91+
type: cred.type,
92+
hasClientData: resp.clientDataJSON.byteLength > 0,
93+
hasAuthData: resp.authenticatorData.byteLength > 0,
94+
hasSignature: resp.signature.byteLength > 0,
95+
authDataFlags: new Uint8Array(resp.authenticatorData)[32],
96+
};
97+
}", new { rpId = RpId, credentialId = known.Id });
98+
99+
Assert.AreEqual(known.Id, result.GetProperty("id").GetString());
100+
Assert.AreEqual("public-key", result.GetProperty("type").GetString());
101+
Assert.IsTrue(result.GetProperty("hasClientData").GetBoolean());
102+
Assert.IsTrue(result.GetProperty("hasAuthData").GetBoolean());
103+
Assert.IsTrue(result.GetProperty("hasSignature").GetBoolean());
104+
// UP (0x01) | UV (0x04) = 0x05
105+
Assert.AreEqual(0x05, result.GetProperty("authDataFlags").GetInt32() & 0x05);
106+
107+
// After the credential is deleted, the page can no longer authenticate with it.
108+
await context.Credentials.DeleteAsync(known.Id);
109+
Assert.IsEmpty(await context.Credentials.GetAsync());
110+
111+
var error = await page.EvaluateAsync<string>(@"async ({ rpId, credentialId }) => {
112+
const b64UrlToBytes = (s) => {
113+
let str = s.replace(/-/g, '+').replace(/_/g, '/');
114+
while (str.length % 4)
115+
str += '=';
116+
const bin = atob(str);
117+
const u8 = new Uint8Array(bin.length);
118+
for (let i = 0; i < bin.length; i++)
119+
u8[i] = bin.charCodeAt(i);
120+
return u8;
121+
};
122+
const challenge = crypto.getRandomValues(new Uint8Array(32));
123+
try {
124+
await navigator.credentials.get({
125+
publicKey: {
126+
challenge,
127+
rpId,
128+
allowCredentials: [{ type: 'public-key', id: b64UrlToBytes(credentialId) }],
129+
},
130+
});
131+
return 'no-error';
132+
} catch (e) {
133+
return e.name;
134+
}
135+
}", new { rpId = RpId, credentialId = known.Id });
136+
Assert.AreEqual("NotAllowedError", error);
137+
}
138+
139+
[PlaywrightTest("browsercontext-webauthn.spec.ts", "should capture a page-created credential and reuse it in another context")]
140+
public async Task ShouldCaptureAPageCreatedCredentialAndReuseItInAnotherContext()
141+
{
142+
// Setup context: the app registers a passkey via navigator.credentials.create().
143+
await using var setupContext = await Browser.NewContextAsync();
144+
await setupContext.Credentials.InstallAsync();
145+
var setupPage = await setupContext.NewPageAsync();
146+
await setupPage.GotoAsync(Server.EmptyPage);
147+
148+
var createdId = await setupPage.EvaluateAsync<string>(@"async ({ rpId }) => {
149+
const challenge = crypto.getRandomValues(new Uint8Array(32));
150+
const created = await navigator.credentials.create({
151+
publicKey: {
152+
challenge,
153+
rp: { id: rpId, name: 'Test RP' },
154+
user: { id: new Uint8Array([1, 2, 3, 4]), name: 'u', displayName: 'User' },
155+
pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
156+
authenticatorSelection: { residentKey: 'required', userVerification: 'preferred' },
157+
},
158+
});
159+
return created.id;
160+
}", new { rpId = RpId });
161+
162+
var credentials = await setupContext.Credentials.GetAsync(new() { RpId = RpId });
163+
Assert.AreEqual(1, credentials.Count);
164+
var captured = credentials[0];
165+
Assert.AreEqual(createdId, captured.Id);
166+
StringAssert.IsMatch("^[A-Za-z0-9_-]+$", captured.PrivateKey);
167+
StringAssert.IsMatch("^[A-Za-z0-9_-]+$", captured.PublicKey);
168+
169+
// Reuse the captured passkey in a fresh context and sign in with it.
170+
await using var context = await Browser.NewContextAsync();
171+
await context.Credentials.CreateAsync(captured.RpId, new()
172+
{
173+
Id = captured.Id,
174+
UserHandle = captured.UserHandle,
175+
PrivateKey = captured.PrivateKey,
176+
PublicKey = captured.PublicKey,
177+
});
178+
await context.Credentials.InstallAsync();
179+
var page = await context.NewPageAsync();
180+
await page.GotoAsync(Server.EmptyPage);
181+
182+
var gotId = await page.EvaluateAsync<string>(@"async ({ rpId }) => {
183+
const challenge = crypto.getRandomValues(new Uint8Array(32));
184+
// No allowCredentials — relies on the re-seeded credential being discoverable.
185+
const cred = await navigator.credentials.get({
186+
publicKey: { challenge, rpId, userVerification: 'preferred' },
187+
});
188+
return cred.id;
189+
}", new { rpId = RpId });
190+
191+
Assert.AreEqual(createdId, gotId);
192+
}
193+
}

src/Playwright.Tests/GlobalFetchTests.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,48 @@ public async Task ShouldPropagateIgnoreHTTPSErrorsOnRedirect()
235235
await request.DisposeAsync();
236236
}
237237

238+
[PlaywrightTest("global-fetch.spec.ts", "should return server address from response")]
239+
public async Task ShouldReturnServerAddressFromResponse()
240+
{
241+
var request = await Playwright.APIRequest.NewContextAsync();
242+
// The second request reuses the keep-alive socket and should report the address as well.
243+
for (int i = 0; i < 2; i++)
244+
{
245+
var response = await request.GetAsync(Server.EmptyPage);
246+
var addr = await response.ServerAddrAsync();
247+
Assert.IsNotNull(addr);
248+
StringAssert.IsMatch("^(127\\.0\\.0\\.1|::1)$", addr.IpAddress);
249+
Assert.AreEqual(Server.Port, addr.Port);
250+
}
251+
await request.DisposeAsync();
252+
}
253+
254+
[PlaywrightTest("global-fetch.spec.ts", "should return security details from response")]
255+
public async Task ShouldReturnSecurityDetailsFromResponse()
256+
{
257+
var request = await Playwright.APIRequest.NewContextAsync(new() { IgnoreHTTPSErrors = true });
258+
// The second request reuses the keep-alive socket and should report the details as well.
259+
for (int i = 0; i < 2; i++)
260+
{
261+
var response = await request.GetAsync(HttpsServer.EmptyPage);
262+
var details = await response.SecurityDetailsAsync();
263+
Assert.IsNotNull(details);
264+
Assert.AreEqual("puppeteer-tests", details.SubjectName);
265+
Assert.AreEqual("puppeteer-tests", details.Issuer);
266+
StringAssert.Contains("TLS", details.Protocol);
267+
}
268+
await request.DisposeAsync();
269+
}
270+
271+
[PlaywrightTest("global-fetch.spec.ts", "should return null security details for http response")]
272+
public async Task ShouldReturnNullSecurityDetailsForHttpResponse()
273+
{
274+
var request = await Playwright.APIRequest.NewContextAsync();
275+
var response = await request.GetAsync(Server.EmptyPage);
276+
Assert.IsNull(await response.SecurityDetailsAsync());
277+
await request.DisposeAsync();
278+
}
279+
238280
[PlaywrightTest("global-fetch.spec.ts", "should resolve url relative to global baseURL option")]
239281
public async Task ShouldResolveUrlRelativeToGlobalBaseURLOption()
240282
{

0 commit comments

Comments
 (0)