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
42 changes: 42 additions & 0 deletions lib/PuppeteerSharp.TestServer/wwwroot/credit-card.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>

<body>
<form id="testform" method="post">
<table>
<tbody>
<tr>
<td>
<label for="name">Name on Card</label>
</td>
<td>
<input size="40" id="name" />
</td>
</tr>
<tr>
<td>
<label for="number">Card Number</label>
</td>
<td>
<input size="40" id="number" name="card_number" />
</td>
</tr>
<tr>
<td>
<label>Expiration Date</label>
</td>
<td>
<input size="2" id="expiration_month" name="ccmonth"> <input size="4" id="expiration_year"
name="ccyear" />
</td>
</tr>
</tbody>
</table>
<input type="submit" value="Submit">
</form>
</body>
</html>
36 changes: 36 additions & 0 deletions lib/PuppeteerSharp.Tests/AutofillTests/AutofillTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Threading.Tasks;
using NUnit.Framework;
using PuppeteerSharp.Nunit;

namespace PuppeteerSharp.Tests.AutofillTests
{
public class AutofillTests : PuppeteerPageBaseTest
{
[Test, PuppeteerTest("autofill.spec", "ElementHandle.autofill", "should fill out a credit card")]
public async Task ShouldFillOutACreditCard()
{
await Page.GoToAsync(TestConstants.ServerUrl + "/credit-card.html");
var name = await Page.WaitForSelectorAsync("#name");
await name.AutofillAsync(new AutofillData
{
CreditCard = new CreditCardData
{
Number = "4444444444444444",
Name = "John Smith",
ExpiryMonth = "01",
ExpiryYear = "2030",
Cvc = "123",
},
});

var result = await Page.EvaluateFunctionAsync<string>(@"() => {
const result = [];
for (const el of document.querySelectorAll('input')) {
result.push(el.value);
}
return result.join(',');
}");
Assert.That(result, Is.EqualTo("John Smith,4444444444444444,01,2030,Submit"));
}
}
}
13 changes: 13 additions & 0 deletions lib/PuppeteerSharp/AutofillData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace PuppeteerSharp
{
/// <summary>
/// Data for autofilling form fields.
/// </summary>
public class AutofillData
{
/// <summary>
/// Gets or sets the credit card data.
/// </summary>
public CreditCardData CreditCard { get; set; }
}
}
3 changes: 3 additions & 0 deletions lib/PuppeteerSharp/Bidi/BidiElementHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ public override Task<int> BackendNodeIdAsync()
throw new PuppeteerException("BackendNodeId is not supported in the current configuration.");
}

public override Task AutofillAsync(AutofillData data)
=> throw new PuppeteerException("Autofill is not supported in the current configuration.");

internal async IAsyncEnumerable<IElementHandle> QueryAXTreeAsync(string name, string role)
{
var locator = new AccessibilityLocator { Name = name, Role = role };
Expand Down
23 changes: 23 additions & 0 deletions lib/PuppeteerSharp/Cdp/CdpElementHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,29 @@ public override async Task<int> BackendNodeIdAsync()
return _backendNodeId.Value;
}

/// <inheritdoc/>
public override async Task AutofillAsync(AutofillData data)
{
if (data is null)
{
throw new ArgumentNullException(nameof(data));
}

var nodeInfo = await Client
.SendAsync<DomDescribeNodeResponse>("DOM.describeNode", new DomDescribeNodeRequest { ObjectId = Id, })
.ConfigureAwait(false);
var fieldId = nodeInfo.Node.BackendNodeId.GetInt32();
var frameId = _cdpFrame.Id;
await Client.SendAsync(
"Autofill.trigger",
new AutofillTriggerRequest
{
FieldId = fieldId,
FrameId = frameId,
Card = data.CreditCard,
}).ConfigureAwait(false);
}

/// <inheritdoc />
public override string ToString() => Handle.ToString();

Expand Down
11 changes: 11 additions & 0 deletions lib/PuppeteerSharp/Cdp/Messaging/AutofillTriggerRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace PuppeteerSharp.Cdp.Messaging
{
internal class AutofillTriggerRequest
{
public int FieldId { get; set; }

public string FrameId { get; set; }

public CreditCardData Card { get; set; }
}
}
34 changes: 34 additions & 0 deletions lib/PuppeteerSharp/CreditCardData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace PuppeteerSharp
{
/// <summary>
/// Credit card data for autofilling.
/// See https://chromedevtools.github.io/devtools-protocol/tot/Autofill/#type-CreditCard.
/// </summary>
public class CreditCardData
{
/// <summary>
/// Gets or sets the credit card number.
/// </summary>
public string Number { get; set; }

/// <summary>
/// Gets or sets the name on the credit card.
/// </summary>
public string Name { get; set; }

/// <summary>
/// Gets or sets the expiry month.
/// </summary>
public string ExpiryMonth { get; set; }

/// <summary>
/// Gets or sets the expiry year.
/// </summary>
public string ExpiryYear { get; set; }

/// <summary>
/// Gets or sets the CVC.
/// </summary>
public string Cvc { get; set; }
}
}
3 changes: 3 additions & 0 deletions lib/PuppeteerSharp/ElementHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,9 @@ public override async ValueTask DisposeAsync()
/// <inheritdoc/>
public abstract Task<int> BackendNodeIdAsync();

/// <inheritdoc/>
public abstract Task AutofillAsync(AutofillData data);

/// <inheritdoc/>
public virtual Task ScrollIntoViewAsync()
=> BindIsolatedHandleAsync<JsonElement?, ElementHandle>(handle
Expand Down
13 changes: 13 additions & 0 deletions lib/PuppeteerSharp/IElementHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -342,5 +342,18 @@ public interface IElementHandle : IJSHandle
/// </summary>
/// <returns>A task that resolves to the backend node ID.</returns>
Task<int> BackendNodeIdAsync();

/// <summary>
/// If the element is a form input, you can use <see cref="AutofillAsync(AutofillData)"/>
/// to test if the form is compatible with the browser's autofill implementation.
/// Throws an error if the form cannot be autofilled.
/// </summary>
/// <param name="data">The autofill data.</param>
/// <returns>A task that resolves when the autofill is complete.</returns>
/// <remarks>
/// Currently, Puppeteer supports auto-filling credit card information only and
/// in Chrome in the new headless and headful modes only.
/// </remarks>
Task AutofillAsync(AutofillData data);
}
}
Loading