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
Original file line number Diff line number Diff line change
Expand Up @@ -800,21 +800,6 @@
"FAIL"
]
},
{
"comment": "This is part of organizing the webdriver bidi implementation, We will remove it one by one",
"testIdPattern": "[oopif.spec] *",
"platforms": [
"darwin",
"linux",
"win32"
],
"parameters": [
"webDriverBiDi"
],
"expectations": [
"FAIL"
]
},
{
"comment": "This is part of organizing the webdriver bidi implementation, We will remove it one by one",
"testIdPattern": "[page.spec] *addStyleTag*",
Expand Down Expand Up @@ -1206,5 +1191,37 @@
"expectations": [
"SKIP"
]
},
{
"comment": "FrameElementAsync times out for OOPIF frames in Firefox BiDi due to script evaluation issues",
"testIdPattern": "[oopif.spec] OOPIF should load oopif iframes with subresources and request interception",
"platforms": [
"darwin",
"linux",
"win32"
],
"parameters": [
"firefox",
"webDriverBiDi"
],
"expectations": [
"FAIL"
]
},
{
"comment": "Firefox BiDi doesn't fire navigation/historyUpdated events for same-document (hash) navigations in OOPIFs",
"testIdPattern": "[oopif.spec] OOPIF should support frames within OOP iframes",
"platforms": [
"darwin",
"linux",
"win32"
],
"parameters": [
"firefox",
"webDriverBiDi"
],
"expectations": [
"FAIL"
]
}
]
29 changes: 21 additions & 8 deletions lib/PuppeteerSharp.Tests/OOPIFTests/OOPIFTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,25 @@ public class OOPIFTests : PuppeteerPageBaseTest
public OOPIFTests()
{
DefaultOptions = TestConstants.DefaultBrowserOptions();
DefaultOptions.Args =
[
"--site-per-process",
$"--remote-debugging-port={++_port}",
"--host-rules=\"MAP * 127.0.0.1\""
];

if (TestConstants.IsChrome)
{
// Chrome-specific args for OOPIF testing
DefaultOptions.Args =
[
"--site-per-process",
$"--remote-debugging-port={++_port}",
"--host-rules=\"MAP * 127.0.0.1\""
];
}
else
{
// Firefox: use network.dns.localDomains pref to resolve test domains
DefaultOptions.ExtraPrefsFirefox = new()
{
["network.dns.localDomains"] = "mainframe,inner-frame1.test,inner-frame2.test,oopifdomain"
};
}
}

[Test, PuppeteerTest("oopif.spec", "OOPIF", "should treat OOP iframes and normal iframes the same")]
Expand Down Expand Up @@ -251,7 +264,7 @@ await Page.EvaluateFunctionAsync(@"() => {
public async Task ShouldReportOopifFrames()
{
var frameTask = Page.WaitForFrameAsync((frame) => frame.Url.EndsWith("oopif.html"));
await Page.GoToAsync($"http://mainframe:{TestConstants.Port}/dynamic-oopif.html");
await Page.GoToAsync(TestConstants.ServerUrl + "/dynamic-oopif.html");
var frame = await frameTask.WithTimeout();
Assert.That((await GetIframesAsync()), Has.Length.EqualTo(1));
Assert.That(Page.Frames, Has.Length.EqualTo(2));
Expand Down Expand Up @@ -391,7 +404,7 @@ public async Task ShouldResolveImmediatelyIfTheFrameAlreadyExists()
}


[Test, PuppeteerTest("oopif.spec", "waitForFrame", "OOPIF: should expose events within OOPIFs")]
[Test, PuppeteerTest("oopif.spec", "OOPIF", "should expose events within OOPIFs")]
public async Task OOPIFShouldExposeEventsWithinOOPIFs()
{
// Setup our session listeners to observe OOPIF activity.
Expand Down
13 changes: 7 additions & 6 deletions lib/PuppeteerSharp/Bidi/BidiElementHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,17 @@ public override async Task<IFrame> ContentFrameAsync()
return;
}").ConfigureAwait(false);

if (handle is not BidiElementHandle bidiHandle)
// Get the RemoteValue from either BidiJSHandle or BidiElementHandle (which wraps BidiJSHandle)
RemoteValue value = handle switch
{
await handle.DisposeAsync().ConfigureAwait(false);
return null;
}
BidiElementHandle bidiElement => bidiElement.BidiJSHandle.RemoteValue,
BidiJSHandle bidiJsHandle => bidiJsHandle.RemoteValue,
_ => null,
};

var value = bidiHandle.BidiJSHandle.RemoteValue;
await handle.DisposeAsync().ConfigureAwait(false);

if (value.Type == "window" && value.Value is WindowProxyProperties windowProxy)
if (value?.Type == "window" && value.Value is WindowProxyProperties windowProxy)
{
var contextId = windowProxy.Context;
return BidiFrame.BidiPage.Frames.FirstOrDefault(frame => frame.Id == contextId);
Expand Down
1 change: 0 additions & 1 deletion lib/PuppeteerSharp/Bidi/BidiFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ void OnHistoryUpdated(object sender, EventArgs args)

try
{
// Wait for either event
var completedTask = await Task.WhenAny(navigationTcs.Task, historyUpdatedTcs.Task).ConfigureAwait(false);

if (completedTask == historyUpdatedTcs.Task)
Expand Down
94 changes: 85 additions & 9 deletions lib/PuppeteerSharp/Bidi/BidiPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,20 +219,42 @@ public override async Task<IResponse> ReloadAsync(NavigationOptions options)
/// <inheritdoc />
public override async Task<IRequest> WaitForRequestAsync(Func<IRequest, bool> predicate, WaitForOptions options = null)
{
// TODO: Implement full network request monitoring for BiDi
// For now, this creates a task that will be faulted when the page closes
var timeout = options?.Timeout ?? DefaultTimeout;
var requestTcs = new TaskCompletionSource<IRequest>(TaskCreationOptions.RunContinuationsAsynchronously);

await Task.WhenAny(requestTcs.Task, ClosedTask).WithTimeout(timeout, t =>
new TimeoutException($"Timeout of {t.TotalMilliseconds} ms exceeded")).ConfigureAwait(false);

if (ClosedTask.IsFaulted)
void RequestHandler(object sender, RequestEventArgs e)
{
await ClosedTask.ConfigureAwait(false);
try
{
if (predicate(e.Request))
{
requestTcs.TrySetResult(e.Request);
}
}
catch (Exception ex)
{
requestTcs.TrySetException(ex);
}
}

return await requestTcs.Task.ConfigureAwait(false);
Request += RequestHandler;

try
{
await Task.WhenAny(requestTcs.Task, ClosedTask).WithTimeout(timeout, t =>
new TimeoutException($"Timeout of {t.TotalMilliseconds} ms exceeded")).ConfigureAwait(false);

if (ClosedTask.IsFaulted)
{
await ClosedTask.ConfigureAwait(false);
}

return await requestTcs.Task.ConfigureAwait(false);
}
finally
{
Request -= RequestHandler;
}
}

/// <inheritdoc />
Expand Down Expand Up @@ -289,7 +311,61 @@ await Task.WhenAny(responseTcs.Task, ClosedTask).WithTimeout(timeout, t =>
public override Task SetBurstModeOffAsync() => throw new NotImplementedException();

/// <inheritdoc />
public override Task<IFrame> WaitForFrameAsync(Func<IFrame, bool> predicate, WaitForOptions options = null) => throw new NotImplementedException();
public override async Task<IFrame> WaitForFrameAsync(Func<IFrame, bool> predicate, WaitForOptions options = null)
{
if (predicate == null)
{
throw new ArgumentNullException(nameof(predicate));
}

var timeout = options?.Timeout ?? DefaultTimeout;
var frameTcs = new TaskCompletionSource<IFrame>(TaskCreationOptions.RunContinuationsAsynchronously);

void FrameNavigatedEventListener(object sender, FrameNavigatedEventArgs e)
{
if (predicate(e.Frame))
{
frameTcs.TrySetResult(e.Frame);
FrameNavigated -= FrameNavigatedEventListener;
}
}

void FrameAttachedEventListener(object sender, FrameEventArgs e)
{
if (predicate(e.Frame))
{
frameTcs.TrySetResult(e.Frame);
FrameAttached -= FrameAttachedEventListener;
}
}

FrameAttached += FrameAttachedEventListener;
FrameNavigated += FrameNavigatedEventListener;

var eventRace = Task.WhenAny(frameTcs.Task, ClosedTask).WithTimeout(timeout, t =>
{
FrameAttached -= FrameAttachedEventListener;
FrameNavigated -= FrameNavigatedEventListener;
return new TimeoutException($"Timeout of {t.TotalMilliseconds} ms exceeded");
});

foreach (var frame in Frames)
{
if (predicate(frame))
{
return frame;
}
}

await eventRace.ConfigureAwait(false);

if (ClosedTask.IsFaulted)
{
await ClosedTask.ConfigureAwait(false);
}

return await frameTcs.Task.ConfigureAwait(false);
}

/// <inheritdoc />
public override async Task SetGeolocationAsync(GeolocationOption options)
Expand Down
12 changes: 12 additions & 0 deletions lib/PuppeteerSharp/Bidi/BidiRealm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,18 @@ private T DeserializeRemoteValueDictionary<T>(RemoteValueDictionary remoteValueD
{
var type = typeof(T);

// Handle nullable value types (e.g., Point?)
var underlyingType = Nullable.GetUnderlyingType(type);
if (underlyingType != null)
{
// Recursively deserialize to the underlying type and convert back to nullable
var deserializedValue = typeof(BidiRealm)
.GetMethod(nameof(DeserializeRemoteValueDictionary), BindingFlags.Instance | BindingFlags.NonPublic)
?.MakeGenericMethod(underlyingType)
.Invoke(this, [remoteValueDictionary]);
return (T)deserializedValue;
}

// Create an instance of T
var instance = Activator.CreateInstance<T>();

Expand Down
Loading