feat: add autofill support (#10565)

This commit is contained in:
Alex Rudenko 2023-07-19 19:42:31 +02:00 committed by GitHub
parent c14f9b64a7
commit 6c9306a72e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 241 additions and 0 deletions

View File

@ -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. |

View File

@ -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; } | | |

View File

@ -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',
},
});
```

View File

@ -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. |

View File

@ -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;
};
}

View File

@ -17,6 +17,7 @@
import {Protocol} from 'devtools-protocol';
import {
AutofillData,
BoundingBox,
BoxModel,
ClickOptions,
@ -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 {

View File

@ -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';
@ -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 //
// ///////////////////

View File

@ -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"],
@ -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"],

View File

@ -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
test/src/autofill.spec.ts Normal file
View File

@ -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');
});
});
});