Skip to content
Open
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
442 changes: 442 additions & 0 deletions src/PlaywrightNative.Tests/Library/Webmcp/WebmcpTests.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) Microsoft Corporation.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using NUnit.Framework;
using PlaywrightNative.NUnit;

namespace PlaywrightNative.Tests
{
/// <summary>
/// Official <c>library/webmcp-disabled.spec.ts</c> parity. Uses the shared
/// browser, which is launched without WebMCP.
/// </summary>
[TestFixture]
[NonParallelizable]
public class WebmcpDisabledTests : PageTestEx
{
[SetUp]
public void SkipWebKit()
{
if (TestConstants.IsWebKit)
{
Assert.Ignore("WebKit does not implement WebMCP");
}
}

[PlaywrightTest("webmcp-disabled.spec.ts", "should throw when the browser was launched without WebMCP")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
public void ShouldThrowWhenTheBrowserWasLaunchedWithoutWebMCP()
{
Exception error = Assert.CatchAsync(() => Page.Webmcp().ToolsAsync());
Assert.That(error.Message, Does.Contain("WebMCP is not enabled."));
Assert.That(error.Message, Does.Contain(TestConstants.IsFirefox ? "dom.modelcontext.enabled" : "--enable-features=WebMCP"));
}
}
}
6 changes: 6 additions & 0 deletions src/PlaywrightNative/Chromium/ChromiumBrowser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ public IReadOnlyList<IBrowserContext> Contexts
/// </summary>
internal Proxy LaunchProxy { get; set; }

/// <summary>
/// Gets the <c>args</c> the browser was launched with, or <see langword="null"/>
/// when Playwright did not launch it (for example <c>connectOverCDP</c>).
/// </summary>
internal IReadOnlyList<string> LaunchArgs { get; init; }

/// <inheritdoc/>
public async Task CloseAsync(string reason = default)
{
Expand Down
16 changes: 16 additions & 0 deletions src/PlaywrightNative/Chromium/ChromiumFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,22 @@ public Task<IJSHandle> WaitForFunctionAsync(string expression, object arg = defa
public Task WaitForTimeoutAsync(float timeout)
=> Task.Delay((int)timeout);

/// <summary>
/// Calls <paramref name="functionDeclaration"/> in the frame utility world and
/// returns its result by value.
/// </summary>
/// <typeparam name="T">The type to deserialize the result to.</typeparam>
/// <param name="functionDeclaration">The JavaScript function to call.</param>
/// <param name="args">Arguments passed by value.</param>
/// <returns>The deserialized result.</returns>
internal async Task<T> EvaluateInUtilityWorldAsync<T>(string functionDeclaration, params object[] args)
{
EvaluateWithArg.ThrowIfDetached(this);
CRExecutionContext context = await ((Page)_page).CrPage.GetUtilityWorldAsync(_crFrame).ConfigureAwait(false)
?? throw new PlaywrightException("Frame utility world is not available.");
return await context.EvaluateFunctionAsync<T>(functionDeclaration, args).ConfigureAwait(false);
}

private async Task<T> EvaluatePreparedInFrameAsync<T>(string handleFn, object[] handleArgs)
{
object[] args = EvaluateHandleArg.AsCallFunctionArguments(handleArgs);
Expand Down
54 changes: 54 additions & 0 deletions src/PlaywrightNative/Contracts/IWebMCP.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) Microsoft Corporation.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;

namespace PlaywrightNative
{
/// <summary>
/// Lists and calls the WebMCP tools a frame registers through
/// <c>document.modelContext.registerTool</c>. Chromium needs the
/// <c>--enable-features=WebMCP</c> launch argument. WebKit does not
/// implement WebMCP.
/// </summary>
public interface IWebMCP
{
/// <summary>
/// Returns the tools currently registered by the frame. Throws if the
/// browser was launched without WebMCP support. The page object covers
/// the main frame only; child frames list their tools through their own
/// <c>frame.Webmcp()</c>.
/// </summary>
/// <param name="timeout">Maximum time in milliseconds. Defaults to the page default timeout. Pass <c>0</c> to disable it.</param>
/// <returns>The registered tools.</returns>
Task<IReadOnlyList<WebMCPTool>> ToolsAsync(float? timeout = default);

/// <summary>
/// Calls a tool registered by the frame and returns its result. The
/// result is whatever the tool's <c>execute</c> function resolved to,
/// typically an object with a <c>content</c> array. A result with
/// <c>isError: true</c> is returned as is. Throws when the tool is not
/// registered or its <c>execute</c> function throws.
/// </summary>
/// <param name="name">Name of the tool, as reported by <see cref="ToolsAsync"/>.</param>
/// <param name="input">Input for the tool, matching its <c>inputSchema</c>. Defaults to an empty object.</param>
/// <param name="timeout">Maximum time in milliseconds. Defaults to the page default timeout. Pass <c>0</c> to disable it.</param>
/// <returns>The tool result, or <see langword="null"/> when the tool returned nothing.</returns>
Task<JsonElement?> CallToolAsync(string name, object input = default, float? timeout = default);
}
}
51 changes: 51 additions & 0 deletions src/PlaywrightNative/Contracts/Models/WebMCPTool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (c) Microsoft Corporation.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Text.Json;
using System.Text.Json.Serialization;

namespace PlaywrightNative
{
/// <summary>
/// A WebMCP tool registered by a frame.
/// </summary>
public sealed class WebMCPTool
{
/// <summary>
/// Tool name, unique within the frame.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; }

/// <summary>
/// Tool description.
/// </summary>
[JsonPropertyName("description")]
public string Description { get; set; }

/// <summary>
/// JSON Schema of the tool input, when the page provides one.
/// </summary>
[JsonPropertyName("inputSchema")]
public JsonElement? InputSchema { get; set; }

/// <summary>
/// Hints the page provides about the tool, or <see langword="null"/> when it provides none.
/// </summary>
[JsonPropertyName("annotations")]
public WebMCPToolAnnotations Annotations { get; set; }
}
}
45 changes: 45 additions & 0 deletions src/PlaywrightNative/Contracts/Models/WebMCPToolAnnotations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) Microsoft Corporation.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Text.Json.Serialization;

namespace PlaywrightNative
{
/// <summary>
/// Hints a page provides about a <see cref="WebMCPTool"/>. Only hints the
/// page set to <see langword="true"/> are reported.
/// </summary>
public sealed class WebMCPToolAnnotations
{
/// <summary>
/// The tool does not modify any state.
/// </summary>
[JsonPropertyName("readOnly")]
public bool? ReadOnly { get; set; }

/// <summary>
/// The tool output may contain third-party content.
/// </summary>
[JsonPropertyName("untrustedContent")]
public bool? UntrustedContent { get; set; }

/// <summary>
/// The tool takes a consequential action, such as placing an order.
/// </summary>
[JsonPropertyName("consequential")]
public bool? Consequential { get; set; }
}
}
6 changes: 6 additions & 0 deletions src/PlaywrightNative/FrameCompatExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,12 @@ public static Task<IResponse> WaitForNavigationAsync(
WaitUntil = options?.WaitUntil,
});

/// <summary>WebMCP tools registered by this frame.</summary>
/// <param name="frame">The frame.</param>
/// <returns>The frame WebMCP API.</returns>
public static IWebMCP Webmcp(this IFrame frame)
=> new WebMCP(frame);

/// <summary>Any-frame search from this frame.</summary>
public static IFrameLocator FrameLocator(this IFrame frame)
=> new FrameLocator(frame);
Expand Down
Loading
Loading