feat: add autofill support (#10565)
This commit is contained in:
parent
c14f9b64a7
commit
6c9306a72e
@ -67,6 +67,7 @@ sidebar_label: API
|
|||||||
| Interface | Description |
|
| Interface | Description |
|
||||||
| --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| [ActionOptions](./puppeteer.actionoptions.md) | |
|
| [ActionOptions](./puppeteer.actionoptions.md) | |
|
||||||
|
| [AutofillData](./puppeteer.autofilldata.md) | |
|
||||||
| [BoundingBox](./puppeteer.boundingbox.md) | |
|
| [BoundingBox](./puppeteer.boundingbox.md) | |
|
||||||
| [BoxModel](./puppeteer.boxmodel.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. |
|
| [BrowserConnectOptions](./puppeteer.browserconnectoptions.md) | Generic browser options that can be passed when launching any browser or when connecting to an existing browser instance. |
|
||||||
|
17
docs/api/puppeteer.autofilldata.md
Normal file
17
docs/api/puppeteer.autofilldata.md
Normal 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; } | | |
|
44
docs/api/puppeteer.elementhandle.autofill.md
Normal file
44
docs/api/puppeteer.elementhandle.autofill.md
Normal 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<void>
|
||||||
|
|
||||||
|
## 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',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
@ -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> |
|
| [$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) | | |
|
| [$x(expression)](./puppeteer.elementhandle._x.md) | | |
|
||||||
| [asElement()](./puppeteer.elementhandle.aselement.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. |
|
| [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. |
|
| [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. |
|
| [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. |
|
||||||
|
@ -1042,4 +1042,45 @@ export class ElementHandle<
|
|||||||
assertElementHasWorld(): asserts this {
|
assertElementHasWorld(): asserts this {
|
||||||
assert(this.executionContext()._world);
|
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;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AutofillData,
|
||||||
BoundingBox,
|
BoundingBox,
|
||||||
BoxModel,
|
BoxModel,
|
||||||
ClickOptions,
|
ClickOptions,
|
||||||
@ -571,6 +572,19 @@ export class CDPElementHandle<
|
|||||||
|
|
||||||
return imageData;
|
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 {
|
function computeQuadArea(quad: Point[]): number {
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AutofillData,
|
||||||
ElementHandle as BaseElementHandle,
|
ElementHandle as BaseElementHandle,
|
||||||
ClickOptions,
|
ClickOptions,
|
||||||
} from '../../api/ElementHandle.js';
|
} from '../../api/ElementHandle.js';
|
||||||
@ -70,6 +71,20 @@ export class ElementHandle<
|
|||||||
return;
|
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 //
|
// // Input methods //
|
||||||
// ///////////////////
|
// ///////////////////
|
||||||
|
@ -5,6 +5,18 @@
|
|||||||
"parameters": ["webDriverBiDi"],
|
"parameters": ["webDriverBiDi"],
|
||||||
"expectations": ["SKIP", "TIMEOUT"]
|
"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 *",
|
"testIdPattern": "[chromiumonly.spec] Chromium-Specific Launcher tests *",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -299,6 +311,12 @@
|
|||||||
"parameters": ["webDriverBiDi"],
|
"parameters": ["webDriverBiDi"],
|
||||||
"expectations": ["PASS"]
|
"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",
|
"testIdPattern": "[browser.spec] Browser specs Browser.isConnected should set the browser connected state",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
42
test/assets/credit-card.html
Normal file
42
test/assets/credit-card.html
Normal 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
48
test/src/autofill.spec.ts
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user