Skip to content

Commit 2b8f41b

Browse files
kblokclaude
andcommitted
merge: resolve conflicts with master (issues support + extension realms)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2 parents 487e3cd + 478991d commit 2b8f41b

27 files changed

Lines changed: 643 additions & 26 deletions

lib/PuppeteerSharp.Nunit/TestExpectations/TestExpectations.upstream.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,6 +1367,21 @@
13671367
],
13681368
"comment": "Flaky on Mac"
13691369
},
1370+
{
1371+
"testIdPattern": "[page.spec] Page Page.Events.Issue *",
1372+
"platforms": [
1373+
"darwin",
1374+
"linux",
1375+
"win32"
1376+
],
1377+
"parameters": [
1378+
"webDriverBiDi"
1379+
],
1380+
"expectations": [
1381+
"SKIP"
1382+
],
1383+
"comment": "BiDi does not support issue events"
1384+
},
13701385
{
13711386
"testIdPattern": "[page.spec] Page Page.Events.error should throw when page crashes",
13721387
"platforms": [
@@ -3933,6 +3948,13 @@
39333948
],
39343949
"comment": "TODO: add a comment explaining why this expectation is required (include links to issues)"
39353950
},
3951+
{
3952+
"testIdPattern": "[worker.spec] Workers console should return remote objects",
3953+
"platforms": ["darwin", "linux", "win32"],
3954+
"parameters": ["webDriverBiDi"],
3955+
"expectations": ["FAIL"],
3956+
"comment": "BiDi does not support getting a Handle for log args"
3957+
},
39363958
{
39373959
"testIdPattern": "[worker.spec] Workers should report errors",
39383960
"platforms": [
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System.Threading.Tasks;
2+
using NUnit.Framework;
3+
using PuppeteerSharp.Nunit;
4+
5+
namespace PuppeteerSharp.Tests.PageTests;
6+
7+
public class PageEventsIssueTests : PuppeteerPageBaseTest
8+
{
9+
[Test, PuppeteerTest("page.spec", "Page Page.Events.Issue", "should emit issue event when CSP violation occurs")]
10+
public async Task ShouldEmitIssueEventWhenCspViolationOccurs()
11+
{
12+
await Page.GoToAsync(TestConstants.ServerUrl + "/csp.html");
13+
14+
var issueTask = new TaskCompletionSource<Issue>();
15+
Page.Issue += (_, e) => issueTask.TrySetResult(e.Issue);
16+
17+
await Page.AddScriptTagAsync(new AddTagOptions { Content = "console.log(\"CSP test\")" });
18+
19+
var issue = await issueTask.Task;
20+
Assert.That(issue, Is.Not.Null);
21+
Assert.That(issue.Code, Is.EqualTo("ContentSecurityPolicyIssue"));
22+
}
23+
24+
[Test, PuppeteerTest("page.spec", "Page Page.Events.Issue", "should emit issue event from cross-origin iframe")]
25+
public async Task ShouldEmitIssueEventFromCrossOriginIframe()
26+
{
27+
await Page.GoToAsync(TestConstants.EmptyPage);
28+
29+
var cspIssueTask = new TaskCompletionSource<Issue>();
30+
Page.Issue += (_, e) =>
31+
{
32+
if (e.Issue.Code == "ContentSecurityPolicyIssue")
33+
{
34+
cspIssueTask.TrySetResult(e.Issue);
35+
}
36+
};
37+
38+
var crossOriginUrl = TestConstants.CrossProcessUrl + "/csp.html";
39+
await Page.SetContentAsync($"<iframe src=\"{crossOriginUrl}\"></iframe>");
40+
41+
var frame = await Page.WaitForFrameAsync(crossOriginUrl);
42+
Assert.That(frame, Is.Not.Null);
43+
44+
await frame.AddScriptTagAsync(new AddTagOptions { Content = "console.log(\"CSP test in iframe\")" });
45+
46+
var issue = await cspIssueTask.Task;
47+
Assert.That(issue, Is.Not.Null);
48+
Assert.That(issue.Code, Is.EqualTo("ContentSecurityPolicyIssue"));
49+
}
50+
}
51+
52+
public class PageEventsIssueDisabledTests : PuppeteerPageBaseTest
53+
{
54+
public PageEventsIssueDisabledTests()
55+
{
56+
DefaultOptions = TestConstants.DefaultBrowserOptions();
57+
DefaultOptions.IssuesEnabled = false;
58+
}
59+
60+
[Test, PuppeteerTest("page.spec", "Page Page.Events.Issue when issues are disabled", "should be able to connect and disable issues")]
61+
public async Task ShouldBeAbleToConnectAndDisableIssues()
62+
{
63+
var issueEmitted = false;
64+
Page.Issue += (_, _) => issueEmitted = true;
65+
66+
await Page.GoToAsync(TestConstants.ServerUrl + "/csp.html");
67+
68+
Assert.That(issueEmitted, Is.False);
69+
}
70+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using NUnit.Framework;
4+
using PuppeteerSharp.Helpers;
5+
6+
namespace PuppeteerSharp.Tests.UtilitiesTests
7+
{
8+
public class ScreenshotMutexTests
9+
{
10+
[Test]
11+
public async Task ShouldLockAndRelease()
12+
{
13+
var mutex = new ScreenshotMutex();
14+
var guard = await mutex.AcquireAsync();
15+
Assert.That(guard, Is.Not.Null);
16+
guard.Dispose();
17+
}
18+
19+
[Test]
20+
public async Task ShouldWorkSequentially()
21+
{
22+
var mutex = new ScreenshotMutex();
23+
var results = new System.Collections.Generic.List<int>();
24+
var first = await mutex.AcquireAsync();
25+
var secondTask = mutex.AcquireAsync();
26+
27+
_ = Task.Delay(10).ContinueWith(_ =>
28+
{
29+
results.Add(1);
30+
first.Dispose();
31+
});
32+
33+
var second = await secondTask;
34+
results.Add(2);
35+
second.Dispose();
36+
37+
Assert.That(results, Is.EqualTo(new[] { 1, 2 }));
38+
}
39+
40+
[Test]
41+
public async Task ShouldCallOnReleaseWhenDisposed()
42+
{
43+
var mutex = new ScreenshotMutex();
44+
var onReleaseCalled = false;
45+
var guard = await mutex.AcquireAsync(() => onReleaseCalled = true);
46+
guard.Dispose();
47+
Assert.That(onReleaseCalled, Is.True);
48+
}
49+
50+
[Test]
51+
public async Task ShouldCallOnReleaseWhenDisposedForQueuedAcquirers()
52+
{
53+
var mutex = new ScreenshotMutex();
54+
var first = await mutex.AcquireAsync();
55+
56+
var onReleaseCalled = false;
57+
var secondTask = mutex.AcquireAsync(() => onReleaseCalled = true);
58+
59+
first.Dispose();
60+
var second = await secondTask;
61+
62+
second.Dispose();
63+
Assert.That(onReleaseCalled, Is.True);
64+
}
65+
}
66+
}

lib/PuppeteerSharp.Tests/WorkerTests/PageWorkerTests.cs

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Generic;
12
using System.Threading.Tasks;
23
using NUnit.Framework;
34
using PuppeteerSharp.Helpers;
@@ -11,6 +12,14 @@ public PageWorkerTests() : base()
1112
{
1213
}
1314

15+
private async Task<WebWorker> CreateWorkerAsync()
16+
{
17+
var workerCreatedTcs = new TaskCompletionSource<WebWorker>();
18+
Page.WorkerCreated += (_, e) => workerCreatedTcs.TrySetResult(e.Worker);
19+
await Page.EvaluateFunctionAsync("() => new Worker('data:text/javascript,1')");
20+
return await workerCreatedTcs.Task;
21+
}
22+
1423
[Test, PuppeteerTest("worker.spec", "Workers", "Page.workers")]
1524
[Ignore("TODO: Fix me. Too flaky")]
1625
public async Task PageWorkers()
@@ -134,6 +143,174 @@ public async Task CanBeClosed()
134143
Assert.That(await workerClosedTcs.Task, Is.SameAs(worker));
135144
}
136145

146+
[Test, PuppeteerTest("worker.spec", "Workers console", "should work")]
147+
public async Task ConsoleShouldWork()
148+
{
149+
var worker = await CreateWorkerAsync();
150+
var consoleTcs = new TaskCompletionSource<ConsoleMessage>();
151+
worker.Console += (_, e) => consoleTcs.TrySetResult(e.Message);
152+
153+
await worker.EvaluateFunctionAsync("() => console.log('hello', 5, {foo: 'bar'})");
154+
155+
var message = await consoleTcs.Task.WithTimeout();
156+
Assert.That(message.Text, Does.Contain("hello").And.Contain("5"));
157+
Assert.That(message.Type, Is.EqualTo(ConsoleType.Log));
158+
Assert.That(message.Args, Has.Count.EqualTo(3));
159+
Assert.That(await message.Args[0].JsonValueAsync<string>(), Is.EqualTo("hello"));
160+
Assert.That(await message.Args[1].JsonValueAsync<int>(), Is.EqualTo(5));
161+
}
162+
163+
[Test, PuppeteerTest("worker.spec", "Workers console", "should work for Error instances")]
164+
public async Task ConsoleShouldWorkForErrorInstances()
165+
{
166+
var worker = await CreateWorkerAsync();
167+
var consoleTcs = new TaskCompletionSource<ConsoleMessage>();
168+
worker.Console += (_, e) => consoleTcs.TrySetResult(e.Message);
169+
170+
await worker.EvaluateFunctionAsync("() => console.log(new Error('test error'))");
171+
172+
var message = await consoleTcs.Task.WithTimeout();
173+
Assert.That(message.Text, Does.Contain("test error").Or.EqualTo("JSHandle@error"));
174+
Assert.That(message.Type, Is.EqualTo(ConsoleType.Log));
175+
Assert.That(message.Args, Has.Count.EqualTo(1));
176+
}
177+
178+
[Test, PuppeteerTest("worker.spec", "Workers console", "should return the first line of the error message in text()")]
179+
public async Task ConsoleShouldReturnFirstLineOfErrorMessageInText()
180+
{
181+
var worker = await CreateWorkerAsync();
182+
var consoleTcs = new TaskCompletionSource<ConsoleMessage>();
183+
worker.Console += (_, e) => consoleTcs.TrySetResult(e.Message);
184+
185+
await worker.EvaluateFunctionAsync("() => console.log(new Error('test error\\nsecond line'))");
186+
187+
var message = await consoleTcs.Task.WithTimeout();
188+
Assert.That(message.Text, Does.Contain("test error").Or.EqualTo("JSHandle@error"));
189+
Assert.That(message.Type, Is.EqualTo(ConsoleType.Log));
190+
}
191+
192+
[Test, PuppeteerTest("worker.spec", "Workers console", "should work for console.trace")]
193+
public async Task ConsoleShouldWorkForConsoleTrace()
194+
{
195+
var worker = await CreateWorkerAsync();
196+
var consoleTcs = new TaskCompletionSource<ConsoleMessage>();
197+
worker.Console += (_, e) => consoleTcs.TrySetResult(e.Message);
198+
199+
await worker.EvaluateFunctionAsync("() => console.trace('calling console.trace')");
200+
201+
var message = await consoleTcs.Task.WithTimeout();
202+
Assert.That(message.Type, Is.EqualTo(ConsoleType.Trace));
203+
Assert.That(message.Text, Is.EqualTo("calling console.trace"));
204+
}
205+
206+
[Test, PuppeteerTest("worker.spec", "Workers console", "should work for console.dir")]
207+
public async Task ConsoleShouldWorkForConsoleDir()
208+
{
209+
var worker = await CreateWorkerAsync();
210+
var consoleTcs = new TaskCompletionSource<ConsoleMessage>();
211+
worker.Console += (_, e) => consoleTcs.TrySetResult(e.Message);
212+
213+
await worker.EvaluateFunctionAsync("() => console.dir('calling console.dir')");
214+
215+
var message = await consoleTcs.Task.WithTimeout();
216+
Assert.That(message.Type, Is.EqualTo(ConsoleType.Dir));
217+
Assert.That(message.Text, Is.EqualTo("calling console.dir"));
218+
}
219+
220+
[Test, PuppeteerTest("worker.spec", "Workers console", "should work for console.warn")]
221+
public async Task ConsoleShouldWorkForConsoleWarn()
222+
{
223+
var worker = await CreateWorkerAsync();
224+
var consoleTcs = new TaskCompletionSource<ConsoleMessage>();
225+
worker.Console += (_, e) => consoleTcs.TrySetResult(e.Message);
226+
227+
await worker.EvaluateFunctionAsync("() => console.warn('calling console.warn')");
228+
229+
var message = await consoleTcs.Task.WithTimeout();
230+
Assert.That(message.Type, Is.EqualTo(ConsoleType.Warning));
231+
Assert.That(message.Text, Is.EqualTo("calling console.warn"));
232+
}
233+
234+
[Test, PuppeteerTest("worker.spec", "Workers console", "should work for console.error")]
235+
public async Task ConsoleShouldWorkForConsoleError()
236+
{
237+
var worker = await CreateWorkerAsync();
238+
var consoleTcs = new TaskCompletionSource<ConsoleMessage>();
239+
worker.Console += (_, e) => consoleTcs.TrySetResult(e.Message);
240+
241+
await worker.EvaluateFunctionAsync("() => console.error('calling console.error')");
242+
243+
var message = await consoleTcs.Task.WithTimeout();
244+
Assert.That(message.Type, Is.EqualTo(ConsoleType.Error));
245+
Assert.That(message.Text, Is.EqualTo("calling console.error"));
246+
}
247+
248+
[Test, PuppeteerTest("worker.spec", "Workers console", "should work for console.log with promise")]
249+
public async Task ConsoleShouldWorkForConsoleLogWithPromise()
250+
{
251+
var worker = await CreateWorkerAsync();
252+
var consoleTcs = new TaskCompletionSource<ConsoleMessage>();
253+
worker.Console += (_, e) => consoleTcs.TrySetResult(e.Message);
254+
255+
await worker.EvaluateFunctionAsync("() => console.log(Promise.resolve('should not wait until resolved!'))");
256+
257+
var message = await consoleTcs.Task.WithTimeout();
258+
Assert.That(message.Type, Is.EqualTo(ConsoleType.Log));
259+
Assert.That(message.Text, Does.Contain("promise").Or.Contain("Promise"));
260+
}
261+
262+
[Test, PuppeteerTest("worker.spec", "Workers console", "should work for different console API calls with timing functions")]
263+
public async Task ConsoleShouldWorkForTimingFunctions()
264+
{
265+
var worker = await CreateWorkerAsync();
266+
var messages = new List<ConsoleMessage>();
267+
worker.Console += (_, e) => messages.Add(e.Message);
268+
269+
await worker.EvaluateFunctionAsync(@"() => {
270+
console.time('calling console.time');
271+
console.timeEnd('calling console.time');
272+
}");
273+
274+
Assert.That(messages, Has.Count.EqualTo(1));
275+
Assert.That(messages[0].Type, Is.EqualTo(ConsoleType.TimeEnd));
276+
Assert.That(messages[0].Text, Does.Contain("calling console.time"));
277+
}
278+
279+
[Test, PuppeteerTest("worker.spec", "Workers console", "should work for different console API calls with group functions")]
280+
public async Task ConsoleShouldWorkForGroupFunctions()
281+
{
282+
var worker = await CreateWorkerAsync();
283+
var messages = new List<ConsoleMessage>();
284+
worker.Console += (_, e) => messages.Add(e.Message);
285+
286+
await worker.EvaluateFunctionAsync(@"() => {
287+
console.group('calling console.group');
288+
console.groupEnd();
289+
}");
290+
291+
Assert.That(messages, Has.Count.EqualTo(2));
292+
Assert.That(messages[0].Type, Is.EqualTo(ConsoleType.StartGroup));
293+
Assert.That(messages[1].Type, Is.EqualTo(ConsoleType.EndGroup));
294+
Assert.That(messages[0].Text, Does.Contain("calling console.group"));
295+
}
296+
297+
[Test, PuppeteerTest("worker.spec", "Workers console", "should return remote objects")]
298+
public async Task ConsoleShouldReturnRemoteObjects()
299+
{
300+
var worker = await CreateWorkerAsync();
301+
var consoleTcs = new TaskCompletionSource<ConsoleMessage>();
302+
worker.Console += (_, e) => consoleTcs.TrySetResult(e.Message);
303+
304+
await worker.EvaluateFunctionAsync(@"() => {
305+
globalThis.test = 1;
306+
console.log(1, 2, 3, globalThis);
307+
}");
308+
309+
var message = await consoleTcs.Task.WithTimeout();
310+
Assert.That(message.Text, Does.Contain("1 2 3"));
311+
Assert.That(message.Args, Has.Count.EqualTo(4));
312+
}
313+
137314
[Test, PuppeteerTest("worker.spec", "Workers", "should work with waitForNetworkIdle")]
138315
public async Task ShouldWorkWithWaitForNetworkIdle()
139316
{

lib/PuppeteerSharp/Bidi/BidiBrowser.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,15 @@ public class BidiBrowser : Browser
4747
private readonly BidiBrowserTarget _target;
4848
private readonly string _webSocketEndpoint;
4949
private readonly bool _networkEnabled;
50+
private readonly bool _issuesEnabled;
5051
private bool _isClosed;
5152

5253
private BidiBrowser(Core.Browser browserCore, IBrowserOptions options, ILoggerFactory loggerFactory, string webSocketEndpoint)
5354
{
5455
_target = new BidiBrowserTarget(this);
5556
_options = options;
5657
_networkEnabled = options.NetworkEnabled;
58+
_issuesEnabled = options.IssuesEnabled;
5759
BrowserCore = browserCore;
5860
_webSocketEndpoint = webSocketEndpoint;
5961
_logger = loggerFactory.CreateLogger<BidiBrowser>();
@@ -386,6 +388,8 @@ await driver.Network.AddDataCollectorAsync(
386388

387389
internal override bool IsNetworkEnabled() => _networkEnabled;
388390

391+
internal override bool IsIssuesEnabled() => _issuesEnabled;
392+
389393
private static WindowState MapFromClientWindowState(WebDriverBiDi.Browser.ClientWindowState state) => state switch
390394
{
391395
WebDriverBiDi.Browser.ClientWindowState.Normal => WindowState.Normal,

0 commit comments

Comments
 (0)