feat: add Page.waitForDevicePrompt (#9299)
Co-authored-by: Alex Rudenko <OrKoN@users.noreply.github.com> Co-authored-by: Alex Rudenko <alexrudenko@chromium.org>
This commit is contained in:
parent
d6e5aeeff5
commit
a5149d52f5
@ -90,6 +90,7 @@ console.log(text);
|
|||||||
| [title()](./puppeteer.frame.title.md) | | |
|
| [title()](./puppeteer.frame.title.md) | | |
|
||||||
| [type(selector, text, options)](./puppeteer.frame.type.md) | | Sends a <code>keydown</code>, <code>keypress</code>/<code>input</code>, and <code>keyup</code> event for each character in the text. |
|
| [type(selector, text, options)](./puppeteer.frame.type.md) | | Sends a <code>keydown</code>, <code>keypress</code>/<code>input</code>, and <code>keyup</code> event for each character in the text. |
|
||||||
| [url()](./puppeteer.frame.url.md) | | |
|
| [url()](./puppeteer.frame.url.md) | | |
|
||||||
|
| [waitForDevicePrompt(options)](./puppeteer.frame.waitfordeviceprompt.md) | | <p>This method is typically coupled with an action that triggers a device request from an api such as WebBluetooth.</p><p>:::caution</p><p>This must be called before the device request is made. It will not return a currently active device prompt.</p><p>:::</p> |
|
||||||
| [waitForFunction(pageFunction, options, args)](./puppeteer.frame.waitforfunction.md) | | |
|
| [waitForFunction(pageFunction, options, args)](./puppeteer.frame.waitforfunction.md) | | |
|
||||||
| [waitForNavigation(options)](./puppeteer.frame.waitfornavigation.md) | | <p>Waits for the frame to navigate. It is useful for when you run code which will indirectly cause the frame to navigate.</p><p>Usage of the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) to change the URL is considered a navigation.</p> |
|
| [waitForNavigation(options)](./puppeteer.frame.waitfornavigation.md) | | <p>Waits for the frame to navigate. It is useful for when you run code which will indirectly cause the frame to navigate.</p><p>Usage of the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) to change the URL is considered a navigation.</p> |
|
||||||
| [waitForSelector(selector, options)](./puppeteer.frame.waitforselector.md) | | <p>Waits for an element matching the given selector to appear in the frame.</p><p>This method works across navigations.</p> |
|
| [waitForSelector(selector, options)](./puppeteer.frame.waitforselector.md) | | <p>Waits for an element matching the given selector to appear in the frame.</p><p>This method works across navigations.</p> |
|
||||||
|
45
docs/api/puppeteer.frame.waitfordeviceprompt.md
Normal file
45
docs/api/puppeteer.frame.waitfordeviceprompt.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
sidebar_label: Frame.waitForDevicePrompt
|
||||||
|
---
|
||||||
|
|
||||||
|
# Frame.waitForDevicePrompt() method
|
||||||
|
|
||||||
|
This method is typically coupled with an action that triggers a device request from an api such as WebBluetooth.
|
||||||
|
|
||||||
|
:::caution
|
||||||
|
|
||||||
|
This must be called before the device request is made. It will not return a currently active device prompt.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
#### Signature:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class Frame {
|
||||||
|
waitForDevicePrompt(
|
||||||
|
options?: WaitTimeoutOptions
|
||||||
|
): Promise<DeviceRequestPrompt>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ------------------------------------------------------- | ------------ |
|
||||||
|
| options | [WaitTimeoutOptions](./puppeteer.waittimeoutoptions.md) | _(Optional)_ |
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
|
||||||
|
Promise<DeviceRequestPrompt>
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const [devicePrompt] = Promise.all([
|
||||||
|
frame.waitForDevicePrompt(),
|
||||||
|
frame.click('#connect-bluetooth'),
|
||||||
|
]);
|
||||||
|
await devicePrompt.select(
|
||||||
|
await devicePrompt.waitForDevice(({name}) => name.includes('My Device'))
|
||||||
|
);
|
||||||
|
```
|
@ -149,6 +149,7 @@ page.off('request', logRequest);
|
|||||||
| [type(selector, text, options)](./puppeteer.page.type.md) | | <p>Sends a <code>keydown</code>, <code>keypress/input</code>, and <code>keyup</code> event for each character in the text.</p><p>To press a special key, like <code>Control</code> or <code>ArrowDown</code>, use [Keyboard.press()](./puppeteer.keyboard.press.md).</p> |
|
| [type(selector, text, options)](./puppeteer.page.type.md) | | <p>Sends a <code>keydown</code>, <code>keypress/input</code>, and <code>keyup</code> event for each character in the text.</p><p>To press a special key, like <code>Control</code> or <code>ArrowDown</code>, use [Keyboard.press()](./puppeteer.keyboard.press.md).</p> |
|
||||||
| [url()](./puppeteer.page.url.md) | | |
|
| [url()](./puppeteer.page.url.md) | | |
|
||||||
| [viewport()](./puppeteer.page.viewport.md) | | |
|
| [viewport()](./puppeteer.page.viewport.md) | | |
|
||||||
|
| [waitForDevicePrompt(options)](./puppeteer.page.waitfordeviceprompt.md) | | <p>This method is typically coupled with an action that triggers a device request from an api such as WebBluetooth.</p><p>:::caution</p><p>This must be called before the device request is made. It will not return a currently active device prompt.</p><p>:::</p> |
|
||||||
| [waitForFileChooser(options)](./puppeteer.page.waitforfilechooser.md) | | <p>This method is typically coupled with an action that triggers file choosing.</p><p>:::caution</p><p>This must be called before the file chooser is launched. It will not return a currently active file chooser.</p><p>:::</p> |
|
| [waitForFileChooser(options)](./puppeteer.page.waitforfilechooser.md) | | <p>This method is typically coupled with an action that triggers file choosing.</p><p>:::caution</p><p>This must be called before the file chooser is launched. It will not return a currently active file chooser.</p><p>:::</p> |
|
||||||
| [waitForFrame(urlOrPredicate, options)](./puppeteer.page.waitforframe.md) | | |
|
| [waitForFrame(urlOrPredicate, options)](./puppeteer.page.waitforframe.md) | | |
|
||||||
| [waitForFunction(pageFunction, options, args)](./puppeteer.page.waitforfunction.md) | | Waits for a function to finish evaluating in the page's context. |
|
| [waitForFunction(pageFunction, options, args)](./puppeteer.page.waitforfunction.md) | | Waits for a function to finish evaluating in the page's context. |
|
||||||
|
45
docs/api/puppeteer.page.waitfordeviceprompt.md
Normal file
45
docs/api/puppeteer.page.waitfordeviceprompt.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
sidebar_label: Page.waitForDevicePrompt
|
||||||
|
---
|
||||||
|
|
||||||
|
# Page.waitForDevicePrompt() method
|
||||||
|
|
||||||
|
This method is typically coupled with an action that triggers a device request from an api such as WebBluetooth.
|
||||||
|
|
||||||
|
:::caution
|
||||||
|
|
||||||
|
This must be called before the device request is made. It will not return a currently active device prompt.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
#### Signature:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class Page {
|
||||||
|
waitForDevicePrompt(
|
||||||
|
options?: WaitTimeoutOptions
|
||||||
|
): Promise<DeviceRequestPrompt>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ------------------------------------------------------- | ------------ |
|
||||||
|
| options | [WaitTimeoutOptions](./puppeteer.waittimeoutoptions.md) | _(Optional)_ |
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
|
||||||
|
Promise<DeviceRequestPrompt>
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const [devicePrompt] = Promise.all([
|
||||||
|
page.waitForDevicePrompt(),
|
||||||
|
page.click('#connect-bluetooth'),
|
||||||
|
]);
|
||||||
|
await devicePrompt.select(
|
||||||
|
await devicePrompt.waitForDevice(({name}) => name.includes('My Device'))
|
||||||
|
);
|
||||||
|
```
|
@ -24,6 +24,7 @@ import type {Accessibility} from '../common/Accessibility.js';
|
|||||||
import type {ConsoleMessage} from '../common/ConsoleMessage.js';
|
import type {ConsoleMessage} from '../common/ConsoleMessage.js';
|
||||||
import type {Coverage} from '../common/Coverage.js';
|
import type {Coverage} from '../common/Coverage.js';
|
||||||
import {Device} from '../common/Device.js';
|
import {Device} from '../common/Device.js';
|
||||||
|
import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js';
|
||||||
import type {Dialog} from '../common/Dialog.js';
|
import type {Dialog} from '../common/Dialog.js';
|
||||||
import {EventEmitter, Handler} from '../common/EventEmitter.js';
|
import {EventEmitter, Handler} from '../common/EventEmitter.js';
|
||||||
import type {FileChooser} from '../common/FileChooser.js';
|
import type {FileChooser} from '../common/FileChooser.js';
|
||||||
@ -2558,6 +2559,36 @@ export class Page extends EventEmitter {
|
|||||||
>(): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
>(): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||||
throw new Error('Not implemented');
|
throw new Error('Not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is typically coupled with an action that triggers a device
|
||||||
|
* request from an api such as WebBluetooth.
|
||||||
|
*
|
||||||
|
* :::caution
|
||||||
|
*
|
||||||
|
* This must be called before the device request is made. It will not return a
|
||||||
|
* currently active device prompt.
|
||||||
|
*
|
||||||
|
* :::
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* const [devicePrompt] = Promise.all([
|
||||||
|
* page.waitForDevicePrompt(),
|
||||||
|
* page.click('#connect-bluetooth'),
|
||||||
|
* ]);
|
||||||
|
* await devicePrompt.select(
|
||||||
|
* await devicePrompt.waitForDevice(({name}) => name.includes('My Device'))
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
waitForDevicePrompt(
|
||||||
|
options?: WaitTimeoutOptions
|
||||||
|
): Promise<DeviceRequestPrompt>;
|
||||||
|
waitForDevicePrompt(): Promise<DeviceRequestPrompt> {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
293
packages/puppeteer-core/src/common/DeviceRequestPrompt.ts
Normal file
293
packages/puppeteer-core/src/common/DeviceRequestPrompt.ts
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2022 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 Protocol from 'devtools-protocol';
|
||||||
|
|
||||||
|
import {WaitTimeoutOptions} from '../api/Page.js';
|
||||||
|
import {assert} from '../util/assert.js';
|
||||||
|
import {
|
||||||
|
createDeferredPromise,
|
||||||
|
DeferredPromise,
|
||||||
|
} from '../util/DeferredPromise.js';
|
||||||
|
|
||||||
|
import {CDPSession} from './Connection.js';
|
||||||
|
import {TimeoutSettings} from './TimeoutSettings.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device in a request prompt.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export class DeviceRequestPromptDevice {
|
||||||
|
/**
|
||||||
|
* Device id during a prompt.
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device name as it appears in a prompt.
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
constructor(id: string, name: string) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device request prompts let you respond to the page requesting for a device
|
||||||
|
* through an API like WebBluetooth.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* `DeviceRequestPrompt` instances are returned via the
|
||||||
|
* {@link Page.waitForDevicePrompt} method.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* const [deviceRequest] = Promise.all([
|
||||||
|
* page.waitForDevicePrompt(),
|
||||||
|
* page.click('#connect-bluetooth'),
|
||||||
|
* ]);
|
||||||
|
* await devicePrompt.select(
|
||||||
|
* await devicePrompt.waitForDevice(({name}) => name.includes('My Device'))
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export class DeviceRequestPrompt {
|
||||||
|
#client: CDPSession | null;
|
||||||
|
#timeoutSettings: TimeoutSettings;
|
||||||
|
#id: string;
|
||||||
|
#handled = false;
|
||||||
|
#updateDevicesHandle = this.#updateDevices.bind(this);
|
||||||
|
#waitForDevicePromises = new Set<{
|
||||||
|
filter: (device: DeviceRequestPromptDevice) => boolean;
|
||||||
|
promise: DeferredPromise<DeviceRequestPromptDevice>;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current list of selectable devices.
|
||||||
|
*/
|
||||||
|
devices: DeviceRequestPromptDevice[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
client: CDPSession,
|
||||||
|
timeoutSettings: TimeoutSettings,
|
||||||
|
firstEvent: Protocol.DeviceAccess.DeviceRequestPromptedEvent
|
||||||
|
) {
|
||||||
|
this.#client = client;
|
||||||
|
this.#timeoutSettings = timeoutSettings;
|
||||||
|
this.#id = firstEvent.id;
|
||||||
|
|
||||||
|
this.#client.on(
|
||||||
|
'DeviceAccess.deviceRequestPrompted',
|
||||||
|
this.#updateDevicesHandle
|
||||||
|
);
|
||||||
|
this.#client.on('Target.detachedFromTarget', () => {
|
||||||
|
this.#client = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#updateDevices(firstEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
#updateDevices(event: Protocol.DeviceAccess.DeviceRequestPromptedEvent) {
|
||||||
|
if (event.id !== this.#id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const rawDevice of event.devices) {
|
||||||
|
if (
|
||||||
|
this.devices.some(device => {
|
||||||
|
return device.id === rawDevice.id;
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDevice = new DeviceRequestPromptDevice(
|
||||||
|
rawDevice.id,
|
||||||
|
rawDevice.name
|
||||||
|
);
|
||||||
|
this.devices.push(newDevice);
|
||||||
|
|
||||||
|
for (const waitForDevicePromise of this.#waitForDevicePromises) {
|
||||||
|
if (waitForDevicePromise.filter(newDevice)) {
|
||||||
|
waitForDevicePromise.promise.resolve(newDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve to the first device in the prompt matching a filter.
|
||||||
|
*/
|
||||||
|
async waitForDevice(
|
||||||
|
filter: (device: DeviceRequestPromptDevice) => boolean,
|
||||||
|
options: WaitTimeoutOptions = {}
|
||||||
|
): Promise<DeviceRequestPromptDevice> {
|
||||||
|
for (const device of this.devices) {
|
||||||
|
if (filter(device)) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {timeout = this.#timeoutSettings.timeout()} = options;
|
||||||
|
const promise = createDeferredPromise<DeviceRequestPromptDevice>({
|
||||||
|
message: `Waiting for \`DeviceRequestPromptDevice\` failed: ${timeout}ms exceeded`,
|
||||||
|
timeout,
|
||||||
|
});
|
||||||
|
const handle = {filter, promise};
|
||||||
|
this.#waitForDevicePromises.add(handle);
|
||||||
|
try {
|
||||||
|
return await promise;
|
||||||
|
} finally {
|
||||||
|
this.#waitForDevicePromises.delete(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a device in the prompt's list.
|
||||||
|
*/
|
||||||
|
async select(device: DeviceRequestPromptDevice): Promise<void> {
|
||||||
|
assert(
|
||||||
|
this.#client !== null,
|
||||||
|
'Cannot select device through detached session!'
|
||||||
|
);
|
||||||
|
assert(this.devices.includes(device), 'Cannot select unknown device!');
|
||||||
|
assert(
|
||||||
|
!this.#handled,
|
||||||
|
'Cannot select DeviceRequestPrompt which is already handled!'
|
||||||
|
);
|
||||||
|
this.#client.off(
|
||||||
|
'DeviceAccess.deviceRequestPrompted',
|
||||||
|
this.#updateDevicesHandle
|
||||||
|
);
|
||||||
|
this.#handled = true;
|
||||||
|
return this.#client.send('DeviceAccess.selectPrompt', {
|
||||||
|
id: this.#id,
|
||||||
|
deviceId: device.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the prompt.
|
||||||
|
*/
|
||||||
|
async cancel(): Promise<void> {
|
||||||
|
assert(
|
||||||
|
this.#client !== null,
|
||||||
|
'Cannot cancel prompt through detached session!'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
!this.#handled,
|
||||||
|
'Cannot cancel DeviceRequestPrompt which is already handled!'
|
||||||
|
);
|
||||||
|
this.#client.off(
|
||||||
|
'DeviceAccess.deviceRequestPrompted',
|
||||||
|
this.#updateDevicesHandle
|
||||||
|
);
|
||||||
|
this.#handled = true;
|
||||||
|
return this.#client.send('DeviceAccess.cancelPrompt', {id: this.#id});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export class DeviceRequestPromptManager {
|
||||||
|
#client: CDPSession | null;
|
||||||
|
#timeoutSettings: TimeoutSettings;
|
||||||
|
#deviceRequestPromptPromises = new Set<
|
||||||
|
DeferredPromise<DeviceRequestPrompt>
|
||||||
|
>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
constructor(client: CDPSession, timeoutSettings: TimeoutSettings) {
|
||||||
|
this.#client = client;
|
||||||
|
this.#timeoutSettings = timeoutSettings;
|
||||||
|
|
||||||
|
this.#client.on('DeviceAccess.deviceRequestPrompted', event => {
|
||||||
|
this.#onDeviceRequestPrompted(event);
|
||||||
|
});
|
||||||
|
this.#client.on('Target.detachedFromTarget', () => {
|
||||||
|
this.#client = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for device prompt created by an action like calling WebBluetooth's
|
||||||
|
* requestDevice.
|
||||||
|
*/
|
||||||
|
async waitForDevicePrompt(
|
||||||
|
options: WaitTimeoutOptions = {}
|
||||||
|
): Promise<DeviceRequestPrompt> {
|
||||||
|
assert(
|
||||||
|
this.#client !== null,
|
||||||
|
'Cannot wait for device prompt through detached session!'
|
||||||
|
);
|
||||||
|
const needsEnable = this.#deviceRequestPromptPromises.size === 0;
|
||||||
|
let enablePromise: Promise<void> | undefined;
|
||||||
|
if (needsEnable) {
|
||||||
|
enablePromise = this.#client.send('DeviceAccess.enable');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {timeout = this.#timeoutSettings.timeout()} = options;
|
||||||
|
const promise = createDeferredPromise<DeviceRequestPrompt>({
|
||||||
|
message: `Waiting for \`DeviceRequestPrompt\` failed: ${timeout}ms exceeded`,
|
||||||
|
timeout,
|
||||||
|
});
|
||||||
|
this.#deviceRequestPromptPromises.add(promise);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [result] = await Promise.all([promise, enablePromise]);
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
this.#deviceRequestPromptPromises.delete(promise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
#onDeviceRequestPrompted(
|
||||||
|
event: Protocol.DeviceAccess.DeviceRequestPromptedEvent
|
||||||
|
) {
|
||||||
|
if (!this.#deviceRequestPromptPromises.size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(this.#client !== null);
|
||||||
|
const devicePrompt = new DeviceRequestPrompt(
|
||||||
|
this.#client,
|
||||||
|
this.#timeoutSettings,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
for (const promise of this.#deviceRequestPromptPromises) {
|
||||||
|
promise.resolve(devicePrompt);
|
||||||
|
}
|
||||||
|
this.#deviceRequestPromptPromises.clear();
|
||||||
|
}
|
||||||
|
}
|
@ -18,10 +18,15 @@ import {Protocol} from 'devtools-protocol';
|
|||||||
|
|
||||||
import {ElementHandle} from '../api/ElementHandle.js';
|
import {ElementHandle} from '../api/ElementHandle.js';
|
||||||
import {HTTPResponse} from '../api/HTTPResponse.js';
|
import {HTTPResponse} from '../api/HTTPResponse.js';
|
||||||
import {Page} from '../api/Page.js';
|
import {Page, WaitTimeoutOptions} from '../api/Page.js';
|
||||||
|
import {assert} from '../util/assert.js';
|
||||||
import {isErrorLike} from '../util/ErrorLike.js';
|
import {isErrorLike} from '../util/ErrorLike.js';
|
||||||
|
|
||||||
import {CDPSession} from './Connection.js';
|
import {CDPSession} from './Connection.js';
|
||||||
|
import {
|
||||||
|
DeviceRequestPrompt,
|
||||||
|
DeviceRequestPromptManager,
|
||||||
|
} from './DeviceRequestPrompt.js';
|
||||||
import {ExecutionContext} from './ExecutionContext.js';
|
import {ExecutionContext} from './ExecutionContext.js';
|
||||||
import {FrameManager} from './FrameManager.js';
|
import {FrameManager} from './FrameManager.js';
|
||||||
import {getQueryHandlerAndSelector} from './GetQueryHandler.js';
|
import {getQueryHandlerAndSelector} from './GetQueryHandler.js';
|
||||||
@ -1083,6 +1088,47 @@ export class Frame {
|
|||||||
return this.worlds[PUPPETEER_WORLD].title();
|
return this.worlds[PUPPETEER_WORLD].title();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
_deviceRequestPromptManager(): DeviceRequestPromptManager {
|
||||||
|
if (this.isOOPFrame()) {
|
||||||
|
return this._frameManager._deviceRequestPromptManager(this.#client);
|
||||||
|
}
|
||||||
|
const parentFrame = this.parentFrame();
|
||||||
|
assert(parentFrame !== null);
|
||||||
|
return parentFrame._deviceRequestPromptManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is typically coupled with an action that triggers a device
|
||||||
|
* request from an api such as WebBluetooth.
|
||||||
|
*
|
||||||
|
* :::caution
|
||||||
|
*
|
||||||
|
* This must be called before the device request is made. It will not return a
|
||||||
|
* currently active device prompt.
|
||||||
|
*
|
||||||
|
* :::
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* const [devicePrompt] = Promise.all([
|
||||||
|
* frame.waitForDevicePrompt(),
|
||||||
|
* frame.click('#connect-bluetooth'),
|
||||||
|
* ]);
|
||||||
|
* await devicePrompt.select(
|
||||||
|
* await devicePrompt.waitForDevice(({name}) => name.includes('My Device'))
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
waitForDevicePrompt(
|
||||||
|
options: WaitTimeoutOptions = {}
|
||||||
|
): Promise<DeviceRequestPrompt> {
|
||||||
|
return this._deviceRequestPromptManager().waitForDevicePrompt(options);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
@ -21,6 +21,7 @@ import {assert} from '../util/assert.js';
|
|||||||
import {isErrorLike} from '../util/ErrorLike.js';
|
import {isErrorLike} from '../util/ErrorLike.js';
|
||||||
|
|
||||||
import {CDPSession, isTargetClosedError} from './Connection.js';
|
import {CDPSession, isTargetClosedError} from './Connection.js';
|
||||||
|
import {DeviceRequestPromptManager} from './DeviceRequestPrompt.js';
|
||||||
import {EventEmitter} from './EventEmitter.js';
|
import {EventEmitter} from './EventEmitter.js';
|
||||||
import {EVALUATION_SCRIPT_URL, ExecutionContext} from './ExecutionContext.js';
|
import {EVALUATION_SCRIPT_URL, ExecutionContext} from './ExecutionContext.js';
|
||||||
import {Frame} from './Frame.js';
|
import {Frame} from './Frame.js';
|
||||||
@ -77,6 +78,11 @@ export class FrameManager extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
#frameNavigatedReceived = new Set<string>();
|
#frameNavigatedReceived = new Set<string>();
|
||||||
|
|
||||||
|
#deviceRequestPromptManagerMap = new WeakMap<
|
||||||
|
CDPSession,
|
||||||
|
DeviceRequestPromptManager
|
||||||
|
>();
|
||||||
|
|
||||||
get timeoutSettings(): TimeoutSettings {
|
get timeoutSettings(): TimeoutSettings {
|
||||||
return this.#timeoutSettings;
|
return this.#timeoutSettings;
|
||||||
}
|
}
|
||||||
@ -219,6 +225,18 @@ export class FrameManager extends EventEmitter {
|
|||||||
this.initialize(target._session());
|
this.initialize(target._session());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
_deviceRequestPromptManager(client: CDPSession): DeviceRequestPromptManager {
|
||||||
|
let manager = this.#deviceRequestPromptManagerMap.get(client);
|
||||||
|
if (manager === undefined) {
|
||||||
|
manager = new DeviceRequestPromptManager(client, this.#timeoutSettings);
|
||||||
|
this.#deviceRequestPromptManagerMap.set(client, manager);
|
||||||
|
}
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
#onLifecycleEvent(event: Protocol.Page.LifecycleEventEvent): void {
|
#onLifecycleEvent(event: Protocol.Page.LifecycleEventEvent): void {
|
||||||
const frame = this.frame(event.frameId);
|
const frame = this.frame(event.frameId);
|
||||||
if (!frame) {
|
if (!frame) {
|
||||||
|
@ -50,6 +50,7 @@ import {
|
|||||||
} from './Connection.js';
|
} from './Connection.js';
|
||||||
import {ConsoleMessage, ConsoleMessageType} from './ConsoleMessage.js';
|
import {ConsoleMessage, ConsoleMessageType} from './ConsoleMessage.js';
|
||||||
import {Coverage} from './Coverage.js';
|
import {Coverage} from './Coverage.js';
|
||||||
|
import {DeviceRequestPrompt} from './DeviceRequestPrompt.js';
|
||||||
import {Dialog} from './Dialog.js';
|
import {Dialog} from './Dialog.js';
|
||||||
import {EmulationManager} from './EmulationManager.js';
|
import {EmulationManager} from './EmulationManager.js';
|
||||||
import {FileChooser} from './FileChooser.js';
|
import {FileChooser} from './FileChooser.js';
|
||||||
@ -1647,6 +1648,35 @@ export class CDPPage extends Page {
|
|||||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||||
return this.mainFrame().waitForFunction(pageFunction, options, ...args);
|
return this.mainFrame().waitForFunction(pageFunction, options, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is typically coupled with an action that triggers a device
|
||||||
|
* request from an api such as WebBluetooth.
|
||||||
|
*
|
||||||
|
* :::caution
|
||||||
|
*
|
||||||
|
* This must be called before the device request is made. It will not return a
|
||||||
|
* currently active device prompt.
|
||||||
|
*
|
||||||
|
* :::
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* const [devicePrompt] = Promise.all([
|
||||||
|
* page.waitForDevicePrompt(),
|
||||||
|
* page.click('#connect-bluetooth'),
|
||||||
|
* ]);
|
||||||
|
* await devicePrompt.select(
|
||||||
|
* await devicePrompt.waitForDevice(({name}) => name.includes('My Device'))
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
override waitForDevicePrompt(
|
||||||
|
options: WaitTimeoutOptions = {}
|
||||||
|
): Promise<DeviceRequestPrompt> {
|
||||||
|
return this.mainFrame().waitForDevicePrompt(options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportedMetrics = new Set<string>([
|
const supportedMetrics = new Set<string>([
|
||||||
|
457
test/src/DeviceRequestPrompt.spec.ts
Normal file
457
test/src/DeviceRequestPrompt.spec.ts
Normal file
@ -0,0 +1,457 @@
|
|||||||
|
import expect from 'expect';
|
||||||
|
import {TimeoutError} from 'puppeteer';
|
||||||
|
import {
|
||||||
|
DeviceRequestPrompt,
|
||||||
|
DeviceRequestPromptDevice,
|
||||||
|
DeviceRequestPromptManager,
|
||||||
|
} from 'puppeteer-core/internal/common/DeviceRequestPrompt.js';
|
||||||
|
import {EventEmitter} from 'puppeteer-core/internal/common/EventEmitter.js';
|
||||||
|
import {TimeoutSettings} from 'puppeteer-core/internal/common/TimeoutSettings.js';
|
||||||
|
|
||||||
|
class MockCDPSession extends EventEmitter {
|
||||||
|
async send(): Promise<any> {}
|
||||||
|
connection() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
async detach() {}
|
||||||
|
id() {
|
||||||
|
return '1';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DeviceRequestPrompt', function () {
|
||||||
|
describe('waitForDevicePrompt', function () {
|
||||||
|
it('should return prompt', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const manager = new DeviceRequestPromptManager(client, timeoutSettings);
|
||||||
|
|
||||||
|
const [prompt] = await Promise.all([
|
||||||
|
manager.waitForDevicePrompt(),
|
||||||
|
(() => {
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
})(),
|
||||||
|
]);
|
||||||
|
expect(prompt).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect timeout', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const manager = new DeviceRequestPromptManager(client, timeoutSettings);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
manager.waitForDevicePrompt({timeout: 1})
|
||||||
|
).rejects.toBeInstanceOf(TimeoutError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect default timeout when there is no custom timeout', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const manager = new DeviceRequestPromptManager(client, timeoutSettings);
|
||||||
|
|
||||||
|
timeoutSettings.setDefaultTimeout(1);
|
||||||
|
await expect(manager.waitForDevicePrompt()).rejects.toBeInstanceOf(
|
||||||
|
TimeoutError
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prioritize exact timeout over default timeout', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const manager = new DeviceRequestPromptManager(client, timeoutSettings);
|
||||||
|
|
||||||
|
timeoutSettings.setDefaultTimeout(0);
|
||||||
|
await expect(
|
||||||
|
manager.waitForDevicePrompt({timeout: 1})
|
||||||
|
).rejects.toBeInstanceOf(TimeoutError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with no timeout', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const manager = new DeviceRequestPromptManager(client, timeoutSettings);
|
||||||
|
|
||||||
|
const [prompt] = await Promise.all([
|
||||||
|
manager.waitForDevicePrompt({timeout: 0}),
|
||||||
|
(async () => {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
setTimeout(resolve, 50);
|
||||||
|
});
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
})(),
|
||||||
|
]);
|
||||||
|
expect(prompt).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the same prompt when there are many watchdogs simultaneously', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const manager = new DeviceRequestPromptManager(client, timeoutSettings);
|
||||||
|
|
||||||
|
const [prompt1, prompt2] = await Promise.all([
|
||||||
|
manager.waitForDevicePrompt(),
|
||||||
|
manager.waitForDevicePrompt(),
|
||||||
|
(() => {
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
})(),
|
||||||
|
]);
|
||||||
|
expect(prompt1 === prompt2).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should listen and shortcut when there are no watchdogs', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const manager = new DeviceRequestPromptManager(client, timeoutSettings);
|
||||||
|
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(manager).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DeviceRequestPrompt.devices', function () {
|
||||||
|
it('lists devices as they arrive', function () {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const prompt = new DeviceRequestPrompt(client, timeoutSettings, {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(prompt.devices).toHaveLength(0);
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [{id: '00000000', name: 'Device 0'}],
|
||||||
|
});
|
||||||
|
expect(prompt.devices).toHaveLength(1);
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [
|
||||||
|
{id: '00000000', name: 'Device 0'},
|
||||||
|
{id: '11111111', name: 'Device 1'},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(prompt.devices).toHaveLength(2);
|
||||||
|
expect(prompt.devices[0]).toBeInstanceOf(DeviceRequestPromptDevice);
|
||||||
|
expect(prompt.devices[1]).toBeInstanceOf(DeviceRequestPromptDevice);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not list devices from events of another prompt', function () {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const prompt = new DeviceRequestPrompt(client, timeoutSettings, {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(prompt.devices).toHaveLength(0);
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '88888888888888888888888888888888',
|
||||||
|
devices: [
|
||||||
|
{id: '00000000', name: 'Device 0'},
|
||||||
|
{id: '11111111', name: 'Device 1'},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(prompt.devices).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DeviceRequestPrompt.waitForDevice', function () {
|
||||||
|
it('should return first matching device', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const prompt = new DeviceRequestPrompt(client, timeoutSettings, {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [device] = await Promise.all([
|
||||||
|
prompt.waitForDevice(({name}) => {
|
||||||
|
return name.includes('1');
|
||||||
|
}),
|
||||||
|
(() => {
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [{id: '00000000', name: 'Device 0'}],
|
||||||
|
});
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [
|
||||||
|
{id: '00000000', name: 'Device 0'},
|
||||||
|
{id: '11111111', name: 'Device 1'},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
})(),
|
||||||
|
]);
|
||||||
|
expect(device).toBeInstanceOf(DeviceRequestPromptDevice);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return first matching device from already known devices', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const prompt = new DeviceRequestPrompt(client, timeoutSettings, {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [
|
||||||
|
{id: '00000000', name: 'Device 0'},
|
||||||
|
{id: '11111111', name: 'Device 1'},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const device = await prompt.waitForDevice(({name}) => {
|
||||||
|
return name.includes('1');
|
||||||
|
});
|
||||||
|
expect(device).toBeInstanceOf(DeviceRequestPromptDevice);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return device in the devices list', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const prompt = new DeviceRequestPrompt(client, timeoutSettings, {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [device] = await Promise.all([
|
||||||
|
prompt.waitForDevice(({name}) => {
|
||||||
|
return name.includes('1');
|
||||||
|
}),
|
||||||
|
(() => {
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [
|
||||||
|
{id: '00000000', name: 'Device 0'},
|
||||||
|
{id: '11111111', name: 'Device 1'},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
})(),
|
||||||
|
]);
|
||||||
|
expect(prompt.devices).toContain(device);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect timeout', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const prompt = new DeviceRequestPrompt(client, timeoutSettings, {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
prompt.waitForDevice(
|
||||||
|
({name}) => {
|
||||||
|
return name.includes('Device');
|
||||||
|
},
|
||||||
|
{timeout: 1}
|
||||||
|
)
|
||||||
|
).rejects.toBeInstanceOf(TimeoutError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect default timeout when there is no custom timeout', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const prompt = new DeviceRequestPrompt(client, timeoutSettings, {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
timeoutSettings.setDefaultTimeout(1);
|
||||||
|
await expect(
|
||||||
|
prompt.waitForDevice(
|
||||||
|
({name}) => {
|
||||||
|
return name.includes('Device');
|
||||||
|
},
|
||||||
|
{timeout: 1}
|
||||||
|
)
|
||||||
|
).rejects.toBeInstanceOf(TimeoutError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prioritize exact timeout over default timeout', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const prompt = new DeviceRequestPrompt(client, timeoutSettings, {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
timeoutSettings.setDefaultTimeout(0);
|
||||||
|
await expect(
|
||||||
|
prompt.waitForDevice(
|
||||||
|
({name}) => {
|
||||||
|
return name.includes('Device');
|
||||||
|
},
|
||||||
|
{timeout: 1}
|
||||||
|
)
|
||||||
|
).rejects.toBeInstanceOf(TimeoutError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with no timeout', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const prompt = new DeviceRequestPrompt(client, timeoutSettings, {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [device] = await Promise.all([
|
||||||
|
prompt.waitForDevice(
|
||||||
|
({name}) => {
|
||||||
|
return name.includes('1');
|
||||||
|
},
|
||||||
|
{timeout: 0}
|
||||||
|
),
|
||||||
|
(() => {
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [{id: '00000000', name: 'Device 0'}],
|
||||||
|
});
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [
|
||||||
|
{id: '00000000', name: 'Device 0'},
|
||||||
|
{id: '11111111', name: 'Device 1'},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
})(),
|
||||||
|
]);
|
||||||
|
expect(device).toBeInstanceOf(DeviceRequestPromptDevice);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return same device from multiple watchdogs', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const prompt = new DeviceRequestPrompt(client, timeoutSettings, {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [device1, device2] = await Promise.all([
|
||||||
|
prompt.waitForDevice(({name}) => {
|
||||||
|
return name.includes('1');
|
||||||
|
}),
|
||||||
|
prompt.waitForDevice(({name}) => {
|
||||||
|
return name.includes('1');
|
||||||
|
}),
|
||||||
|
(() => {
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [{id: '00000000', name: 'Device 0'}],
|
||||||
|
});
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [
|
||||||
|
{id: '00000000', name: 'Device 0'},
|
||||||
|
{id: '11111111', name: 'Device 1'},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
})(),
|
||||||
|
]);
|
||||||
|
expect(device1 === device2).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DeviceRequestPrompt.select', function () {
|
||||||
|
it('should succeed with listed device', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const prompt = new DeviceRequestPrompt(client, timeoutSettings, {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [device] = await Promise.all([
|
||||||
|
prompt.waitForDevice(({name}) => {
|
||||||
|
return name.includes('1');
|
||||||
|
}),
|
||||||
|
(() => {
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [
|
||||||
|
{id: '00000000', name: 'Device 0'},
|
||||||
|
{id: '11111111', name: 'Device 1'},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
})(),
|
||||||
|
]);
|
||||||
|
await prompt.select(device);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error for device not listed in devices', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const prompt = new DeviceRequestPrompt(client, timeoutSettings, {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
prompt.select(new DeviceRequestPromptDevice('11111111', 'Device 1'))
|
||||||
|
).rejects.toThrowError('Cannot select unknown device!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when selecting prompt twice', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const prompt = new DeviceRequestPrompt(client, timeoutSettings, {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [device] = await Promise.all([
|
||||||
|
prompt.waitForDevice(({name}) => {
|
||||||
|
return name.includes('1');
|
||||||
|
}),
|
||||||
|
(() => {
|
||||||
|
client.emit('DeviceAccess.deviceRequestPrompted', {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [
|
||||||
|
{id: '00000000', name: 'Device 0'},
|
||||||
|
{id: '11111111', name: 'Device 1'},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
})(),
|
||||||
|
]);
|
||||||
|
await prompt.select(device);
|
||||||
|
await expect(prompt.select(device)).rejects.toThrowError(
|
||||||
|
'Cannot select DeviceRequestPrompt which is already handled!'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DeviceRequestPrompt.cancel', function () {
|
||||||
|
it('should succeed on first call', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const prompt = new DeviceRequestPrompt(client, timeoutSettings, {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
await prompt.cancel();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when canceling prompt twice', async () => {
|
||||||
|
const client = new MockCDPSession();
|
||||||
|
const timeoutSettings = new TimeoutSettings();
|
||||||
|
const prompt = new DeviceRequestPrompt(client, timeoutSettings, {
|
||||||
|
id: '00000000000000000000000000000000',
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
await prompt.cancel();
|
||||||
|
await expect(prompt.cancel()).rejects.toThrowError(
|
||||||
|
'Cannot cancel DeviceRequestPrompt which is already handled!'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user