Skip to content

Commit

Permalink
feat: add autofill support (#10565)
Browse files Browse the repository at this point in the history
  • Loading branch information
OrKoN authored Jul 19, 2023
1 parent c14f9b6 commit 6c9306a
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ sidebar_label: API
| Interface | Description |
| --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [ActionOptions](./puppeteer.actionoptions.md) | |
| [AutofillData](./puppeteer.autofilldata.md) | |
| [BoundingBox](./puppeteer.boundingbox.md) | |
| [BoxModel](./puppeteer.boxmodel.md) | |
| [BrowserConnectOptions](./puppeteer.browserconnectoptions.md) | Generic browser options that can be passed when launching any browser or when connecting to an existing browser instance. |
Expand Down
17 changes: 17 additions & 0 deletions docs/api/puppeteer.autofilldata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
sidebar_label: AutofillData
---

# AutofillData interface

#### Signature:

```typescript
export interface AutofillData
```

## Properties

| Property | Modifiers | Type | Description | Default |
| ---------- | --------- | --------------------------------------------------------------------------------------- | ----------- | ------- |
| creditCard | | { number: string; name: string; expiryMonth: string; expiryYear: string; cvc: string; } | | |
44 changes: 44 additions & 0 deletions docs/api/puppeteer.elementhandle.autofill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
sidebar_label: ElementHandle.autofill
---

# ElementHandle.autofill() method

If the element is a form input, you can use [ElementHandle.autofill()](./puppeteer.elementhandle.autofill.md) to test if the form is compatible with the browser's autofill implementation. Throws an error if the form cannot be autofilled.

#### Signature:

```typescript
class ElementHandle {
autofill(data: AutofillData): Promise<void>;
}
```

## Parameters

| Parameter | Type | Description |
| --------- | ------------------------------------------- | ----------- |
| data | [AutofillData](./puppeteer.autofilldata.md) | |

**Returns:**

Promise&lt;void&gt;

## Remarks

Currently, Puppeteer supports auto-filling credit card information only and in Chrome in the new headless and headful modes only.

```ts
// Select an input on the credit card form.
const name = await page.waitForSelector('form #name');
// Trigger autofill with the desired data.
await name.autofill({
creditCard: {
number: '4444444444444444',
name: 'John Smith',
expiryMonth: '01',
expiryYear: '2030',
cvc: '123',
},
});
```
1 change: 1 addition & 0 deletions docs/api/puppeteer.elementhandle.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ The constructor for this class is marked as internal. Third-party code should no
| [$eval(selector, pageFunction, args)](./puppeteer.elementhandle._eval.md) | | <p>Runs the given function on the first element matching the given selector in the current element.</p><p>If the given function returns a promise, then this method will wait till the promise resolves.</p> |
| [$x(expression)](./puppeteer.elementhandle._x.md) | | |
| [asElement()](./puppeteer.elementhandle.aselement.md) | | |
| [autofill(data)](./puppeteer.elementhandle.autofill.md) | | If the element is a form input, you can use [ElementHandle.autofill()](./puppeteer.elementhandle.autofill.md) to test if the form is compatible with the browser's autofill implementation. Throws an error if the form cannot be autofilled. |
| [boundingBox()](./puppeteer.elementhandle.boundingbox.md) | | This method returns the bounding box of the element (relative to the main frame), or <code>null</code> if the element is not visible. |
| [boxModel()](./puppeteer.elementhandle.boxmodel.md) | | This method returns boxes of the element, or <code>null</code> if the element is not visible. |
| [click(this, options)](./puppeteer.elementhandle.click.md) | | This method scrolls element into view if needed, and then uses [Page.mouse](./puppeteer.page.md) to click in the center of the element. If the element is detached from DOM, the method throws an error. |
Expand Down
41 changes: 41 additions & 0 deletions packages/puppeteer-core/src/api/ElementHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1042,4 +1042,45 @@ export class ElementHandle<
assertElementHasWorld(): asserts this {
assert(this.executionContext()._world);
}

/**
* If the element is a form input, you can use {@link ElementHandle.autofill}
* to test if the form is compatible with the browser's autofill
* implementation. Throws an error if the form cannot be autofilled.
*
* @remarks
*
* Currently, Puppeteer supports auto-filling credit card information only and
* in Chrome in the new headless and headful modes only.
*
* ```ts
* // Select an input on the credit card form.
* const name = await page.waitForSelector('form #name');
* // Trigger autofill with the desired data.
* await name.autofill({
* creditCard: {
* number: '4444444444444444',
* name: 'John Smith',
* expiryMonth: '01',
* expiryYear: '2030',
* cvc: '123',
* },
* });
* ```
*/
autofill(data: AutofillData): Promise<void>;
autofill(): Promise<void> {
throw new Error('Not implemented');
}
}

export interface AutofillData {
creditCard: {
// See https://chromedevtools.github.io/devtools-protocol/tot/Autofill/#type-CreditCard.
number: string;
name: string;
expiryMonth: string;
expiryYear: string;
cvc: string;
};
}
14 changes: 14 additions & 0 deletions packages/puppeteer-core/src/common/ElementHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import {Protocol} from 'devtools-protocol';

import {
AutofillData,
BoundingBox,
BoxModel,
ClickOptions,
Expand Down Expand Up @@ -571,6 +572,19 @@ export class CDPElementHandle<

return imageData;
}

override async autofill(data: AutofillData): Promise<void> {
const nodeInfo = await this.client.send('DOM.describeNode', {
objectId: this.handle.id,
});
const fieldId = nodeInfo.node.backendNodeId;
const frameId = this.#frame._id;
await this.client.send('Autofill.trigger', {
fieldId,
frameId,
card: data.creditCard,
});
}
}

function computeQuadArea(quad: Point[]): number {
Expand Down
15 changes: 15 additions & 0 deletions packages/puppeteer-core/src/common/bidi/ElementHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';

import {
AutofillData,
ElementHandle as BaseElementHandle,
ClickOptions,
} from '../../api/ElementHandle.js';
Expand Down Expand Up @@ -70,6 +71,20 @@ export class ElementHandle<
return;
}

override async autofill(data: AutofillData): Promise<void> {
const client = this.#frame.context().cdpSession;
const nodeInfo = await client.send('DOM.describeNode', {
objectId: this.handle.id,
});
const fieldId = nodeInfo.node.backendNodeId;
const frameId = this.#frame._id;
await client.send('Autofill.trigger', {
fieldId,
frameId,
card: data.creditCard,
});
}

// ///////////////////
// // Input methods //
// ///////////////////
Expand Down
18 changes: 18 additions & 0 deletions test/TestExpectations.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP", "TIMEOUT"]
},
{
"testIdPattern": "[autofill.spec] *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[autofill.spec] *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[chromiumonly.spec] Chromium-Specific Launcher tests *",
"platforms": ["darwin", "linux", "win32"],
Expand Down Expand Up @@ -299,6 +311,12 @@
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[autofill.spec] *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "headless"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[browser.spec] Browser specs Browser.isConnected should set the browser connected state",
"platforms": ["darwin", "linux", "win32"],
Expand Down
42 changes: 42 additions & 0 deletions test/assets/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>
48 changes: 48 additions & 0 deletions test/src/autofill.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* 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.
*/

import expect from 'expect';

import {getTestState, setupTestBrowserHooks} from './mocha-utils.js';

describe('Autofill', function () {
setupTestBrowserHooks();
describe('ElementHandle.autofill', () => {
it('should fill out a credit card', async () => {
const {page, server} = await getTestState();
await page.goto(server.PREFIX + '/credit-card.html');
const name = await page.waitForSelector('#name');
await name!.autofill({
creditCard: {
number: '4444444444444444',
name: 'John Smith',
expiryMonth: '01',
expiryYear: '2030',
cvc: '123',
},
});
expect(
await page.evaluate(() => {
const result = [];
for (const el of document.querySelectorAll('input')) {
result.push(el.value);
}
return result.join(',');
})
).toBe('John Smith,4444444444444444,01,2030,Submit');
});
});
});

0 comments on commit 6c9306a

Please sign in to comment.